From c99dcd2e93a045701278d924470e10a4a180bc39 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Thu, 19 Apr 2012 15:32:41 -0700 Subject: [PATCH 1/4] Add Travis CI Support Continous integration is nice, fixes #3155 Signed-off-by: Chris Aniszczyk --- .gitignore | 3 ++- .travis.yml | 4 ++++ README.md | 2 +- package.json | 22 ++++++++++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .travis.yml create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 4086ceec52..2b1ffbfeb8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ nbproject .hg .svn .CVS -.idea \ No newline at end of file +.idea +node_modules diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..037e4e797d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 +script: "make build" diff --git a/README.md b/README.md index cab1d7afde..f1a66eac39 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[Twitter Bootstrap](http://twitter.github.com/bootstrap) +[Twitter Bootstrap](http://twitter.github.com/bootstrap) [![Build Status](https://secure.travis-ci.org/twitter/bootstrap.png)](http://travis-ci.org/twitter/bootstrap) ================= Bootstrap provides simple and flexible HTML, CSS, and Javascript for popular user interface components and interactions. In other words, it's a front-end toolkit for faster, more beautiful web development. It's created and maintained by [Mark Otto](http://twitter.com/mdo) and [Jacob Thornton](http://twitter.com/fat) at Twitter. diff --git a/package.json b/package.json new file mode 100644 index 0000000000..0c74adcd02 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "bootstrap" + , "description": "HTML, CSS, and JS toolkit from Twitter." + , "version": "2.0.3" + , "keywords": ["bootstrap", "css"] + , "homepage": "http://twitter.github.com/bootstrap/" + , "author": "Twitter Inc." + , "repository": { + "type": "git" + , "url": "https://github.com/twitter/bootstrap.git" + } + , "licenses": [ + { "type": "Apache-2.0" + , "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ] + , "devDependencies": { + "uglify-js": "*" + , "jshint": "*" + , "recess": "*" + } +} From 1160935446e06339d8c7e09415d57086746fbce5 Mon Sep 17 00:00:00 2001 From: Jacob Thornton Date: Thu, 19 Apr 2012 16:38:43 -0700 Subject: [PATCH 2/4] make a few changes to package.json (add make test to makefile) --- .travis.yml | 3 +-- Makefile | 8 ++++++++ package.json | 12 +++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 037e4e797d..b8e1f17207 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ language: node_js node_js: - - 0.6 -script: "make build" + - 0.6 \ No newline at end of file diff --git a/Makefile b/Makefile index f99b2303f5..e7a0f035f0 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,14 @@ build: @echo "Thanks for using Bootstrap," @echo "<3 @mdo and @fat\n" +# +# RUN JSHINT +# TODO: run unittests +# + +test: + jshint js/*.js --config js/.jshintrc + jshint js/tests/unit/*.js --config js/.jshintrc # # BUILD SIMPLE BOOTSTRAP DIRECTORY diff --git a/package.json b/package.json index 0c74adcd02..c32979e53d 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,24 @@ { - "name": "bootstrap" + "name": "bootstrap" , "description": "HTML, CSS, and JS toolkit from Twitter." , "version": "2.0.3" , "keywords": ["bootstrap", "css"] , "homepage": "http://twitter.github.com/bootstrap/" , "author": "Twitter Inc." + , "scripts": { "test": "make test" } , "repository": { - "type": "git" + "type": "git" , "url": "https://github.com/twitter/bootstrap.git" } , "licenses": [ - { "type": "Apache-2.0" + { + "type": "Apache-2.0" , "url": "http://www.apache.org/licenses/LICENSE-2.0" } ] , "devDependencies": { - "uglify-js": "*" + "uglify-js": "*" , "jshint": "*" , "recess": "*" } -} +} \ No newline at end of file From 03f78f7a60a631c01c1fb085b01d98bee61d7bb6 Mon Sep 17 00:00:00 2001 From: Jacob Thornton Date: Thu, 19 Apr 2012 17:34:07 -0700 Subject: [PATCH 3/4] run qunit tests in phantomjs for travis-ci --- Makefile | 8 +++- README.md | 3 ++ js/tests/index.html | 3 ++ js/tests/phantom.js | 65 ++++++++++++++++++++++++++++++ js/tests/server.js | 18 +++++++++ js/tests/unit/bootstrap-phantom.js | 21 ++++++++++ package.json | 1 + 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 js/tests/phantom.js create mode 100644 js/tests/server.js create mode 100644 js/tests/unit/bootstrap-phantom.js diff --git a/Makefile b/Makefile index e7a0f035f0..9a4ffa4536 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ BOOTSTRAP_RESPONSIVE_LESS = ./less/responsive.less DATE=$(shell date +%I:%M%p) CHECK=\033[32m✔\033[39m + # # BUILD DOCS # @@ -37,13 +38,16 @@ build: @echo "<3 @mdo and @fat\n" # -# RUN JSHINT -# TODO: run unittests +# RUN JSHINT & QUNIT TESTS IN PHANTOMJS # test: jshint js/*.js --config js/.jshintrc jshint js/tests/unit/*.js --config js/.jshintrc + node js/tests/server.js & + phantomjs js/tests/phantom.js "http://localhost:3000/js/tests" + kill -9 `cat js/tests/pid.txt` + rm js/tests/pid.txt # # BUILD SIMPLE BOOTSTRAP DIRECTORY diff --git a/README.md b/README.md index f1a66eac39..54ea40577f 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ $ npm install recess uglify-js jshint -g + **build** - `make` Runs the recess compiler to rebuild the `/less` files and compiles the docs pages. Requires recess and uglify-js. Read more in our docs » ++ **test** - `make test` +Runs jshint and qunit tests headlessly in phantom js (used for ci). Depends on having phatomjs installed. + + **watch** - `make watch` This is a convenience method for watching just Less files and automatically building them whenever you save. Requires the Watchr gem. diff --git a/js/tests/index.html b/js/tests/index.html index 3e6cb9777c..2f8f71b12e 100644 --- a/js/tests/index.html +++ b/js/tests/index.html @@ -11,6 +11,9 @@ + + + diff --git a/js/tests/phantom.js b/js/tests/phantom.js new file mode 100644 index 0000000000..f172e7cbc3 --- /dev/null +++ b/js/tests/phantom.js @@ -0,0 +1,65 @@ +/* + * Simple phantom.js integration script + * Adapted from Modernizr + */ + +function waitFor(testFx, onReady, timeOutMillis) { + var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s + start = new Date().getTime(), + condition = false, + interval = setInterval(function() { + if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) { + // If not time-out yet and condition not yet fulfilled + condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code + } else { + if(!condition) { + // If condition still not fulfilled (timeout but condition is 'false') + console.log("'waitFor()' timeout"); + phantom.exit(1); + } else { + // Condition fulfilled (timeout and/or condition is 'true') + typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled + clearInterval(interval); //< Stop this interval + } + } + }, 100); //< repeat check every 250ms +}; + + +if (phantom.args.length === 0 || phantom.args.length > 2) { + console.log('Usage: phantom.js URL'); + phantom.exit(); +} + +var page = new WebPage(); + +// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") +page.onConsoleMessage = function(msg) { + console.log(msg); +}; + +page.open(phantom.args[0], function(status){ + if (status !== "success") { + console.log("Unable to access network"); + phantom.exit(); + } else { + waitFor(function(){ + return page.evaluate(function(){ + var el = document.getElementById('qunit-testresult'); + if (el && el.innerText.match('completed')) { + return true; + } + return false; + }); + }, function(){ + var failedNum = page.evaluate(function(){ + var el = document.getElementById('qunit-testresult'); + try { + return el.getElementsByClassName('failed')[0].innerHTML; + } catch (e) { } + return 10000; + }); + phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0); + }); + } +}); \ No newline at end of file diff --git a/js/tests/server.js b/js/tests/server.js new file mode 100644 index 0000000000..27d1bf24b3 --- /dev/null +++ b/js/tests/server.js @@ -0,0 +1,18 @@ +/* + * Simple connect server for phantom.js + * Adapted from Modernizr + */ + +var connect = require('connect') + , args = process.argv.slice(2) + , fs = require('fs') + , folder = '/../../' + , port = '3000' + +var server = connect.createServer( + connect.static(__dirname + folder) +).listen(port) + +fs.writeFileSync(__dirname + '/pid.txt', process.pid, 'utf-8') + +console.log("Server started on port %s in %s", port, folder) \ No newline at end of file diff --git a/js/tests/unit/bootstrap-phantom.js b/js/tests/unit/bootstrap-phantom.js new file mode 100644 index 0000000000..8bed5f6035 --- /dev/null +++ b/js/tests/unit/bootstrap-phantom.js @@ -0,0 +1,21 @@ +// Logging setup for phantom integration +// adapted from Modernizr + +QUnit.begin = function() { + console.log("Starting test suite"); + console.log("================================================\n"); +}; + +QUnit.moduleDone = function(opts) { + if(opts.failed === 0) { + console.log("\u2714 All tests passed in '"+opts.name+"' module"); + } else { + console.log("\u2716 "+ opts.failed +" tests failed in '"+opts.name+"' module"); + } +}; + +QUnit.done = function(opts) { + console.log("\n================================================"); + console.log("Tests completed in "+opts.runtime+" milliseconds"); + console.log(opts.passed + " tests of "+opts.total+" passed, "+opts.failed+" failed."); +}; \ No newline at end of file diff --git a/package.json b/package.json index c32979e53d..5f54d5c78a 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,6 @@ "uglify-js": "*" , "jshint": "*" , "recess": "*" + , "connect": "*" } } \ No newline at end of file From f84882122ef98500af143ddaaf055262cfbdf0c3 Mon Sep 17 00:00:00 2001 From: Jacob Thornton Date: Thu, 19 Apr 2012 17:40:58 -0700 Subject: [PATCH 4/4] style changes to phantom integration --- js/tests/phantom.js | 102 ++++++++++++++--------------- js/tests/unit/bootstrap-phantom.js | 32 ++++----- 2 files changed, 66 insertions(+), 68 deletions(-) diff --git a/js/tests/phantom.js b/js/tests/phantom.js index f172e7cbc3..5173ba9a19 100644 --- a/js/tests/phantom.js +++ b/js/tests/phantom.js @@ -1,65 +1,63 @@ -/* - * Simple phantom.js integration script - * Adapted from Modernizr - */ +// Simple phantom.js integration script +// Adapted from Modernizr function waitFor(testFx, onReady, timeOutMillis) { - var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s - start = new Date().getTime(), - condition = false, - interval = setInterval(function() { - if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) { - // If not time-out yet and condition not yet fulfilled - condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code - } else { - if(!condition) { - // If condition still not fulfilled (timeout but condition is 'false') - console.log("'waitFor()' timeout"); - phantom.exit(1); - } else { - // Condition fulfilled (timeout and/or condition is 'true') - typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled - clearInterval(interval); //< Stop this interval - } - } - }, 100); //< repeat check every 250ms -}; + var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001 //< Default Max Timout is 3s + , start = new Date().getTime() + , condition = false + , interval = setInterval(function() { + if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) { + // If not time-out yet and condition not yet fulfilled + condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()) //< defensive code + } else { + if(!condition) { + // If condition still not fulfilled (timeout but condition is 'false') + console.log("'waitFor()' timeout") + phantom.exit(1) + } else { + // Condition fulfilled (timeout and/or condition is 'true') + typeof(onReady) === "string" ? eval(onReady) : onReady() //< Do what it's supposed to do once the condition is fulfilled + clearInterval(interval) //< Stop this interval + } + } + }, 100) //< repeat check every 100ms +} if (phantom.args.length === 0 || phantom.args.length > 2) { - console.log('Usage: phantom.js URL'); - phantom.exit(); + console.log('Usage: phantom.js URL') + phantom.exit() } -var page = new WebPage(); +var page = new WebPage() // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) { - console.log(msg); + console.log(msg) }; page.open(phantom.args[0], function(status){ - if (status !== "success") { - console.log("Unable to access network"); - phantom.exit(); - } else { - waitFor(function(){ - return page.evaluate(function(){ - var el = document.getElementById('qunit-testresult'); - if (el && el.innerText.match('completed')) { - return true; - } - return false; - }); - }, function(){ - var failedNum = page.evaluate(function(){ - var el = document.getElementById('qunit-testresult'); - try { - return el.getElementsByClassName('failed')[0].innerHTML; - } catch (e) { } - return 10000; - }); - phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0); - }); - } -}); \ No newline at end of file + if (status !== "success") { + console.log("Unable to access network") + phantom.exit() + } else { + waitFor(function(){ + return page.evaluate(function(){ + var el = document.getElementById('qunit-testresult') + if (el && el.innerText.match('completed')) { + return true + } + return false + }) + }, function(){ + var failedNum = page.evaluate(function(){ + var el = document.getElementById('qunit-testresult') + try { + return el.getElementsByClassName('failed')[0].innerHTML + } catch (e) { } + return 10000 + }); + phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0) + }) + } +}) \ No newline at end of file diff --git a/js/tests/unit/bootstrap-phantom.js b/js/tests/unit/bootstrap-phantom.js index 8bed5f6035..a04aeaa878 100644 --- a/js/tests/unit/bootstrap-phantom.js +++ b/js/tests/unit/bootstrap-phantom.js @@ -1,21 +1,21 @@ // Logging setup for phantom integration // adapted from Modernizr -QUnit.begin = function() { - console.log("Starting test suite"); - console.log("================================================\n"); -}; +QUnit.begin = function () { + console.log("Starting test suite") + console.log("================================================\n") +} -QUnit.moduleDone = function(opts) { - if(opts.failed === 0) { - console.log("\u2714 All tests passed in '"+opts.name+"' module"); - } else { - console.log("\u2716 "+ opts.failed +" tests failed in '"+opts.name+"' module"); - } -}; +QUnit.moduleDone = function (opts) { + if (opts.failed === 0) { + console.log("\u2714 All tests passed in '" + opts.name + "' module") + } else { + console.log("\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module") + } +} -QUnit.done = function(opts) { - console.log("\n================================================"); - console.log("Tests completed in "+opts.runtime+" milliseconds"); - console.log(opts.passed + " tests of "+opts.total+" passed, "+opts.failed+" failed."); -}; \ No newline at end of file +QUnit.done = function (opts) { + console.log("\n================================================") + console.log("Tests completed in " + opts.runtime + " milliseconds") + console.log(opts.passed + " tests of " + opts.total + " passed, " + opts.failed + " failed.") +} \ No newline at end of file