├── .gitignore ├── MIT-LICENSE ├── README.mdown ├── Rakefile ├── TODO.txt ├── about.yml ├── init.rb ├── install.rb ├── lib └── qunit_for_rails.rb ├── public ├── images │ ├── bg_diagonalDarkBlue.gif │ ├── bg_secondaryNav_left.gif │ ├── bg_secondaryNav_right.gif │ ├── i_loading_bar.gif │ └── l_qunit.png ├── javascripts │ ├── qunit.js │ ├── qunit_for_rails.js │ └── tests │ │ └── test.js └── stylesheets │ └── qunit_for_rails.css ├── tasks └── qunit_for_rails_tasks.rake ├── test ├── qunit_for_rails_test.rb └── test_helper.rb └── uninstall.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 [name of plugin creator] 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | QunitForRails 2 | ============= 3 | 4 | QUnit for Rails provides an easy way to run JavaScript integration testing 5 | against production rendered HTML using the QUnit testing framework. 6 | QUnit for Rails simply adds an overlay menu at the top of an app's pages, 7 | allowing developers to run JavaScript tests and view the results right in the 8 | browser. 9 | 10 | To read more about QUnit: 11 | [http://docs.jquery.com/QUnit][] 12 | 13 | The source for this plugin is located on Github: 14 | [http://github.com/mkrisher/qunit_for_rails/][] 15 | 16 | The plugin can be installed using: 17 | `script/plugin install git@github.com:mkrisher/qunit_for_rails.git` 18 | 19 | Details 20 | ======= 21 | 22 | QUnit for Rails provides an easy way for developers to write JavaScript tests 23 | and run those tests against HTML output in the browser. QUnit for Rails does 24 | not run via the command line, it is run in the browser against the real HTML 25 | output. 26 | 27 | QUnit for Rails provides some necessary JavaScript and CSS files and places 28 | them in your app's public directory. These files simply create the overlay 29 | menu that gets added to your pages. The plugin also provides the latest QUnit 30 | file. In case you ever want to change the version of QUnit, you would simply 31 | replace the file in your public/javascript directory. 32 | 33 | The overlay provides a number of things. First, all your JavaScript tests are 34 | stored in /public/javascript/tests. Any JavaScript test added to the directory 35 | will appear in a dropdown in the overlay. Simply selecting a test from the 36 | dropdown runs it against the current page. Or there is an option to run all 37 | tests against a page. 38 | 39 | In much the same way Rails unit testing and functional testing work, you could 40 | easily create a JavaScript test for each controller and action of your app. 41 | 42 | During the plugin install process, the tests directory is created and an 43 | example test file is included. Follow the test file's formatting to create 44 | additional tests. 45 | 46 | The plugin also provides some handy keypress functionality. For example, hiding 47 | the overlay you simply press 'shift+h'. All keypress options are 48 | shown by press 'shift+?'. 49 | 50 | The QUnit overlay looks like this in the browser: 51 | 52 | [![](http://farm3.static.flickr.com/2603/4014493975_cfc0c2af3d_o.png)](http://farm3.static.flickr.com/2603/4014493975_cfc0c2af3d_o.png) 53 | 54 | Running a test pushes the page down and reveals the test results: 55 | 56 | [![](http://farm3.static.flickr.com/2464/4015257542_d6c759682f_o.png)](http://farm3.static.flickr.com/2464/4015257542_d6c759682f_o.png) 57 | 58 | Requirements 59 | ======= 60 | 61 | The QUnit for Rails plugin requires jQuery at this time. You can include 62 | jQuery easily by adding to the head of your document layout: 63 | 64 | 65 | 68 | 69 | 70 | Usage 71 | ===== 72 | 73 | To include the QUnit testing overlay on your page simply add 74 | `<%= include_qunit %>` to the head of your document layout. 75 | 76 | If you would like to include qunit_for_rails but not have it show on the top 77 | of the page automatically, you can hide it on load using: 78 | `<%= include_qunit({:autohide => true}) %>`. 79 | 80 | After adding the `<%= include_qunit %>` to the head of your document layout, 81 | you simply need to add some test files to the 82 | `/public/javascripts/tests directory`. 83 | 84 | The tests are written in JSON and follow a pattern such as: 85 | 86 | var localtests = { 87 | test_another_example : function() 88 | { 89 | module("Module Example Tests"); 90 | test("simple passing example", function() 91 | { 92 | var value = "foo"; 93 | equals( "foo", value, "We expect value to be foo" ); 94 | }); 95 | } 96 | }; 97 | $().extend(tests, localtests); 98 | 99 | 100 | Copyright (c) 2009 Michael Krisher, released under the MIT license 101 | 102 | [http://docs.jquery.com/QUnit]: http://docs.jquery.com/QUnit 103 | [http://github.com/mkrisher/qunit_for_rails/]: http://github.com/mkrisher/qunit_for_rails/ -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'rake/rdoctask' 4 | 5 | desc 'Default: run unit tests.' 6 | task :default => :test 7 | 8 | desc 'Test the qunit_for_rails plugin.' 9 | Rake::TestTask.new(:test) do |t| 10 | t.libs << 'lib' 11 | t.libs << 'test' 12 | t.pattern = 'test/**/*_test.rb' 13 | t.verbose = true 14 | end 15 | 16 | desc 'Generate documentation for the qunit_for_rails plugin.' 17 | Rake::RDocTask.new(:rdoc) do |rdoc| 18 | rdoc.rdoc_dir = 'rdoc' 19 | rdoc.title = 'QunitForRails' 20 | rdoc.options << '--line-numbers' << '--inline-source' 21 | rdoc.rdoc_files.include('README') 22 | rdoc.rdoc_files.include('lib/**/*.rb') 23 | end 24 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | TODO: convert to gem/plugin -------------------------------------------------------------------------------- /about.yml: -------------------------------------------------------------------------------- 1 | name: QUnit for Rails 2 | author: Michael Krisher 3 | version: 1.0 4 | description: Helper to integrate QUnit JavaScript testing into any Rails app 5 | url: http://github.com/mkrisher/qunit_for_rails/ 6 | install: git@github.com:mkrisher/qunit_for_rails.git 7 | license: MIT 8 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'qunit_for_rails' 2 | 3 | ActionView::Base.send :include, QunitForRails 4 | 5 | 6 | -------------------------------------------------------------------------------- /install.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | # Install all the needed support files (CSS and JavaScript) 4 | js_dir = File.dirname(__FILE__) + '/../../../public/javascripts/' 5 | css_dir = File.dirname(__FILE__) + '/../../../public/stylesheets/' 6 | images_dir = File.dirname(__FILE__) + '/../../../public/images/' 7 | 8 | # install all javascript files 9 | FileUtils.cp File.dirname(__FILE__) + '/public/javascripts/qunit.js', js_dir unless File.exists?(js_dir + "qunit.js") 10 | FileUtils.cp File.dirname(__FILE__) + '/public/javascripts/qunit_for_rails.js', js_dir unless File.exists?(js_dir + "qunit_for_rails.js") 11 | 12 | # install all needed CSS 13 | FileUtils.cp File.dirname(__FILE__) + '/public/stylesheets/qunit_for_rails.css', css_dir unless File.exists?(css_dir + "qunit_for_rails.css") 14 | 15 | # install all image files 16 | FileUtils.cp File.dirname(__FILE__) + '/public/images/l_qunit.png', images_dir unless File.exists?(images_dir + "l_qunit.png") 17 | FileUtils.cp File.dirname(__FILE__) + '/public/images/i_loading_bar.gif', images_dir unless File.exists?(images_dir + "i_loading_bar.gif") 18 | FileUtils.cp File.dirname(__FILE__) + '/public/images/bg_secondaryNav_right.gif', images_dir unless File.exists?(images_dir + "bg_secondaryNav_right.gif") 19 | FileUtils.cp File.dirname(__FILE__) + '/public/images/bg_secondaryNav_left.gif', images_dir unless File.exists?(images_dir + "bg_secondaryNav_left.gif") 20 | FileUtils.cp File.dirname(__FILE__) + '/public/images/bg_diagonalDarkBlue.gif', images_dir unless File.exists?(images_dir + "bg_diagonalDarkBlue.gif") 21 | 22 | # create the tests directory 23 | FileUtils.mkdir js_dir + 'tests' 24 | FileUtils.cp File.dirname(__FILE__) + '/public/javascripts/tests/test.js', js_dir + 'tests' unless File.exists?(js_dir + 'tests/' + "test.js") 25 | 26 | -------------------------------------------------------------------------------- /lib/qunit_for_rails.rb: -------------------------------------------------------------------------------- 1 | module QunitForRails 2 | def include_qunit(options = { :autohide => false }) 3 | # write to the head of application.html.erb 4 | # include the js and css files required, if RAILS_ENV = development 5 | str = " 6 | 12 | 13 | 14 | 15 | " 16 | end 17 | 18 | def collect_tests 19 | response = "" 24 | end 25 | 26 | def list_tests 27 | files = Dir.entries(File.dirname(__FILE__) + "/../../../../public/javascripts/tests") 28 | files.delete(".") 29 | files.delete("..") 30 | files.join(",").to_s 31 | end 32 | 33 | end -------------------------------------------------------------------------------- /public/images/bg_diagonalDarkBlue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrisher/qunit_for_rails/b1c726cbcca0762d3804be9db09db257ac9bec0b/public/images/bg_diagonalDarkBlue.gif -------------------------------------------------------------------------------- /public/images/bg_secondaryNav_left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrisher/qunit_for_rails/b1c726cbcca0762d3804be9db09db257ac9bec0b/public/images/bg_secondaryNav_left.gif -------------------------------------------------------------------------------- /public/images/bg_secondaryNav_right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrisher/qunit_for_rails/b1c726cbcca0762d3804be9db09db257ac9bec0b/public/images/bg_secondaryNav_right.gif -------------------------------------------------------------------------------- /public/images/i_loading_bar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrisher/qunit_for_rails/b1c726cbcca0762d3804be9db09db257ac9bec0b/public/images/i_loading_bar.gif -------------------------------------------------------------------------------- /public/images/l_qunit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrisher/qunit_for_rails/b1c726cbcca0762d3804be9db09db257ac9bec0b/public/images/l_qunit.png -------------------------------------------------------------------------------- /public/javascripts/qunit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var QUnit = { 14 | 15 | // Initialize the configuration options 16 | init: function init() { 17 | config = { 18 | stats: { all: 0, bad: 0 }, 19 | moduleStats: { all: 0, bad: 0 }, 20 | started: +new Date, 21 | blocking: false, 22 | autorun: false, 23 | assertions: [], 24 | filters: [], 25 | queue: [] 26 | }; 27 | 28 | var tests = id("qunit-tests"), 29 | banner = id("qunit-banner"), 30 | result = id("qunit-testresult"); 31 | 32 | if ( tests ) { 33 | tests.innerHTML = ""; 34 | } 35 | 36 | if ( banner ) { 37 | banner.className = ""; 38 | } 39 | 40 | if ( result ) { 41 | result.parentNode.removeChild( result ); 42 | } 43 | }, 44 | 45 | // call on start of module test to prepend name to all tests 46 | module: function module(name, testEnvironment) { 47 | 48 | synchronize(function() { 49 | if ( config.currentModule ) { 50 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 51 | } 52 | 53 | config.currentModule = name; 54 | config.moduleTestEnvironment = testEnvironment; 55 | config.moduleStats = { all: 0, bad: 0 }; 56 | 57 | QUnit.moduleStart( name, testEnvironment ); 58 | }); 59 | }, 60 | 61 | asyncTest: function asyncTest(testName, expected, callback) { 62 | if ( arguments.length === 2 ) { 63 | callback = expected; 64 | expected = 0; 65 | } 66 | 67 | QUnit.test(testName, expected, callback, true); 68 | }, 69 | 70 | test: function test(testName, expected, callback, async) { 71 | var name = testName, testEnvironment = {}; 72 | 73 | if ( arguments.length === 2 ) { 74 | callback = expected; 75 | expected = null; 76 | } 77 | 78 | if ( config.currentModule ) { 79 | name = config.currentModule + " module: " + name; 80 | } 81 | 82 | if ( !validTest(name) ) { 83 | return; 84 | } 85 | 86 | synchronize(function() { 87 | QUnit.testStart( testName ); 88 | 89 | testEnvironment = extend({ 90 | setup: function() {}, 91 | teardown: function() {} 92 | }, config.moduleTestEnvironment); 93 | 94 | config.assertions = []; 95 | config.expected = null; 96 | 97 | if ( arguments.length >= 3 ) { 98 | config.expected = callback; 99 | callback = arguments[2]; 100 | } 101 | 102 | try { 103 | if ( !config.pollution ) { 104 | saveGlobal(); 105 | } 106 | 107 | testEnvironment.setup.call(testEnvironment); 108 | } catch(e) { 109 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 110 | } 111 | 112 | if ( async ) { 113 | QUnit.stop(); 114 | } 115 | 116 | try { 117 | callback.call(testEnvironment); 118 | } catch(e) { 119 | fail("Test " + name + " died, exception and test follows", e, callback); 120 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 121 | // else next test will carry the responsibility 122 | saveGlobal(); 123 | 124 | // Restart the tests if they're blocking 125 | if ( config.blocking ) { 126 | start(); 127 | } 128 | } 129 | }); 130 | 131 | synchronize(function() { 132 | try { 133 | checkPollution(); 134 | testEnvironment.teardown.call(testEnvironment); 135 | } catch(e) { 136 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 137 | } 138 | 139 | try { 140 | QUnit.reset(); 141 | } catch(e) { 142 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 143 | } 144 | 145 | if ( config.expected && config.expected != config.assertions.length ) { 146 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 147 | } 148 | 149 | var good = 0, bad = 0, 150 | tests = id("qunit-tests"); 151 | 152 | config.stats.all += config.assertions.length; 153 | config.moduleStats.all += config.assertions.length; 154 | 155 | if ( tests ) { 156 | var ol = document.createElement("ol"); 157 | ol.style.display = "none"; 158 | 159 | for ( var i = 0; i < config.assertions.length; i++ ) { 160 | var assertion = config.assertions[i]; 161 | 162 | var li = document.createElement("li"); 163 | li.className = assertion.result ? "pass" : "fail"; 164 | li.innerHTML = assertion.message || "(no message)"; 165 | ol.appendChild( li ); 166 | 167 | if ( assertion.result ) { 168 | good++; 169 | } else { 170 | bad++; 171 | config.stats.bad++; 172 | config.moduleStats.bad++; 173 | } 174 | } 175 | 176 | var b = document.createElement("strong"); 177 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 178 | 179 | addEvent(b, "click", function() { 180 | var next = b.nextSibling, display = next.style.display; 181 | next.style.display = display === "none" ? "block" : "none"; 182 | }); 183 | 184 | addEvent(b, "dblclick", function(e) { 185 | var target = (e || window.event).target; 186 | if ( target.nodeName.toLowerCase() === "strong" ) { 187 | var text = "", node = target.firstChild; 188 | 189 | while ( node.nodeType === 3 ) { 190 | text += node.nodeValue; 191 | node = node.nextSibling; 192 | } 193 | 194 | text = text.replace(/(^\s*|\s*$)/g, ""); 195 | 196 | if ( window.location ) { 197 | window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); 198 | } 199 | } 200 | }); 201 | 202 | var li = document.createElement("li"); 203 | li.className = bad ? "fail" : "pass"; 204 | li.appendChild( b ); 205 | li.appendChild( ol ); 206 | tests.appendChild( li ); 207 | 208 | if ( bad ) { 209 | var toolbar = id("qunit-testrunner-toolbar"); 210 | if ( toolbar ) { 211 | toolbar.style.display = "block"; 212 | id("qunit-filter-pass").disabled = null; 213 | id("qunit-filter-missing").disabled = null; 214 | } 215 | } 216 | 217 | } else { 218 | for ( var i = 0; i < config.assertions.length; i++ ) { 219 | if ( !config.assertions[i].result ) { 220 | bad++; 221 | config.stats.bad++; 222 | config.moduleStats.bad++; 223 | } 224 | } 225 | } 226 | 227 | QUnit.testDone( testName, bad, config.assertions.length ); 228 | 229 | if ( !window.setTimeout && !config.queue.length ) { 230 | done(); 231 | } 232 | }); 233 | 234 | if ( window.setTimeout && !config.doneTimer ) { 235 | config.doneTimer = window.setTimeout(function(){ 236 | if ( !config.queue.length ) { 237 | done(); 238 | } else { 239 | synchronize( done ); 240 | } 241 | }, 13); 242 | } 243 | }, 244 | 245 | /** 246 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 247 | */ 248 | expect: function expect(asserts) { 249 | config.expected = asserts; 250 | }, 251 | 252 | /** 253 | * Asserts true. 254 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 255 | */ 256 | ok: function ok(a, msg) { 257 | QUnit.log(a, msg); 258 | 259 | config.assertions.push({ 260 | result: !!a, 261 | message: msg 262 | }); 263 | }, 264 | 265 | /** 266 | * Checks that the first two arguments are equal, with an optional message. 267 | * Prints out both actual and expected values. 268 | * 269 | * Prefered to ok( actual == expected, message ) 270 | * 271 | * @example equals( format("Received {0} bytes.", 2), "Received 2 bytes." ); 272 | * 273 | * @param Object actual 274 | * @param Object expected 275 | * @param String message (optional) 276 | */ 277 | equals: function equals(actual, expected, message) { 278 | push(expected == actual, actual, expected, message); 279 | }, 280 | 281 | same: function(a, b, message) { 282 | push(QUnit.equiv(a, b), a, b, message); 283 | }, 284 | 285 | start: function start() { 286 | // A slight delay, to avoid any current callbacks 287 | if ( window.setTimeout ) { 288 | window.setTimeout(function() { 289 | if ( config.timeout ) { 290 | clearTimeout(config.timeout); 291 | } 292 | 293 | config.blocking = false; 294 | process(); 295 | }, 13); 296 | } else { 297 | config.blocking = false; 298 | process(); 299 | } 300 | }, 301 | 302 | stop: function stop(timeout) { 303 | config.blocking = true; 304 | 305 | if ( timeout && window.setTimeout ) { 306 | config.timeout = window.setTimeout(function() { 307 | QUnit.ok( false, "Test timed out" ); 308 | QUnit.start(); 309 | }, timeout); 310 | } 311 | }, 312 | 313 | /** 314 | * Resets the test setup. Useful for tests that modify the DOM. 315 | */ 316 | reset: function reset() { 317 | if ( window.jQuery ) { 318 | jQuery("#main").html( config.fixture ); 319 | jQuery.event.global = {}; 320 | jQuery.ajaxSettings = extend({}, config.ajaxSettings); 321 | } 322 | }, 323 | 324 | /** 325 | * Trigger an event on an element. 326 | * 327 | * @example triggerEvent( document.body, "click" ); 328 | * 329 | * @param DOMElement elem 330 | * @param String type 331 | */ 332 | triggerEvent: function triggerEvent( elem, type, event ) { 333 | if ( document.createEvent ) { 334 | event = document.createEvent("MouseEvents"); 335 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 336 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 337 | elem.dispatchEvent( event ); 338 | 339 | } else if ( elem.fireEvent ) { 340 | elem.fireEvent("on"+type); 341 | } 342 | }, 343 | 344 | // Logging callbacks 345 | done: function done(failures, total) {}, 346 | log: function log(result, message) {}, 347 | testStart: function testStart(name) {}, 348 | testDone: function testDone(name, failures, total) {}, 349 | moduleStart: function moduleStart(name, testEnvironment) {}, 350 | moduleDone: function moduleDone(name, failures, total) {} 351 | }; 352 | 353 | // Maintain internal state 354 | var config = { 355 | // The queue of tests to run 356 | queue: [], 357 | 358 | // block until document ready 359 | blocking: true 360 | }; 361 | 362 | // Load paramaters 363 | (function() { 364 | var location = window.location || { search: "", protocol: "file:" }, 365 | GETParams = location.search.slice(1).split('&'); 366 | 367 | for ( var i = 0; i < GETParams.length; i++ ) { 368 | GETParams[i] = decodeURIComponent( GETParams[i] ); 369 | if ( GETParams[i] === "noglobals" ) { 370 | GETParams.splice( i, 1 ); 371 | i--; 372 | config.noglobals = true; 373 | } 374 | } 375 | 376 | // restrict modules/tests by get parameters 377 | config.filters = GETParams; 378 | 379 | // Figure out if we're running the tests from a server or not 380 | QUnit.isLocal = !!(location.protocol === 'file:'); 381 | })(); 382 | 383 | // Expose the API as global variables, unless an 'exports' 384 | // object exists, in that case we assume we're in CommonJS 385 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 386 | extend(window, QUnit); 387 | window.QUnit = QUnit; 388 | } else { 389 | extend(exports, QUnit); 390 | exports.QUnit = QUnit; 391 | } 392 | 393 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 394 | config.autorun = true; 395 | } 396 | 397 | addEvent(window, "load", function() { 398 | // Initialize the config, saving the execution queue 399 | var oldconfig = extend({}, config); 400 | QUnit.init(); 401 | extend(config, oldconfig); 402 | 403 | config.blocking = false; 404 | 405 | var userAgent = id("qunit-userAgent"); 406 | if ( userAgent ) { 407 | userAgent.innerHTML = navigator.userAgent; 408 | } 409 | 410 | var toolbar = id("qunit-testrunner-toolbar"); 411 | if ( toolbar ) { 412 | toolbar.style.display = "none"; 413 | 414 | var filter = document.createElement("input"); 415 | filter.type = "checkbox"; 416 | filter.id = "qunit-filter-pass"; 417 | filter.disabled = true; 418 | addEvent( filter, "click", function() { 419 | var li = document.getElementsByTagName("li"); 420 | for ( var i = 0; i < li.length; i++ ) { 421 | if ( li[i].className.indexOf("pass") > -1 ) { 422 | li[i].style.display = filter.checked ? "none" : "block"; 423 | } 424 | } 425 | }); 426 | toolbar.appendChild( filter ); 427 | 428 | var label = document.createElement("label"); 429 | label.setAttribute("for", "filter-pass"); 430 | label.innerHTML = "Hide passed tests"; 431 | toolbar.appendChild( label ); 432 | 433 | var missing = document.createElement("input"); 434 | missing.type = "checkbox"; 435 | missing.id = "qunit-filter-missing"; 436 | missing.disabled = true; 437 | addEvent( missing, "click", function() { 438 | var li = document.getElementsByTagName("li"); 439 | for ( var i = 0; i < li.length; i++ ) { 440 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 441 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 442 | } 443 | } 444 | }); 445 | toolbar.appendChild( missing ); 446 | 447 | label = document.createElement("label"); 448 | label.setAttribute("for", "filter-missing"); 449 | label.innerHTML = "Hide missing tests (untested code is broken code)"; 450 | toolbar.appendChild( label ); 451 | } 452 | 453 | var main = id('main'); 454 | if ( main ) { 455 | config.fixture = main.innerHTML; 456 | } 457 | 458 | if ( window.jQuery ) { 459 | config.ajaxSettings = window.jQuery.ajaxSettings; 460 | } 461 | 462 | QUnit.start(); 463 | }); 464 | 465 | function done() { 466 | if ( config.doneTimer && window.clearTimeout ) { 467 | window.clearTimeout( config.doneTimer ); 468 | config.doneTimer = null; 469 | } 470 | 471 | if ( config.queue.length ) { 472 | config.doneTimer = window.setTimeout(function(){ 473 | if ( !config.queue.length ) { 474 | done(); 475 | } else { 476 | synchronize( done ); 477 | } 478 | }, 13); 479 | 480 | return; 481 | } 482 | 483 | config.autorun = true; 484 | 485 | // Log the last module results 486 | if ( config.currentModule ) { 487 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 488 | } 489 | 490 | var banner = id("qunit-banner"), 491 | tests = id("qunit-tests"), 492 | html = ['Tests completed in ', 493 | +new Date - config.started, ' milliseconds.
', 494 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); 495 | 496 | if ( banner ) { 497 | banner.className += " " + (config.stats.bad ? "fail" : "pass"); 498 | } 499 | 500 | if ( tests ) { 501 | var result = id("qunit-testresult"); 502 | 503 | if ( !result ) { 504 | result = document.createElement("p"); 505 | result.id = "qunit-testresult"; 506 | result.className = "result"; 507 | tests.parentNode.insertBefore( result, tests.nextSibling ); 508 | } 509 | 510 | result.innerHTML = html; 511 | } 512 | 513 | QUnit.done( config.stats.bad, config.stats.all ); 514 | } 515 | 516 | function validTest( name ) { 517 | var i = config.filters.length, 518 | run = false; 519 | 520 | if ( !i ) { 521 | return true; 522 | } 523 | 524 | while ( i-- ) { 525 | var filter = config.filters[i], 526 | not = filter.charAt(0) == '!'; 527 | 528 | if ( not ) { 529 | filter = filter.slice(1); 530 | } 531 | 532 | if ( name.indexOf(filter) !== -1 ) { 533 | return !not; 534 | } 535 | 536 | if ( not ) { 537 | run = true; 538 | } 539 | } 540 | 541 | return run; 542 | } 543 | 544 | function push(result, actual, expected, message) { 545 | message = message || (result ? "okay" : "failed"); 546 | QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); 547 | } 548 | 549 | function synchronize( callback ) { 550 | config.queue.push( callback ); 551 | 552 | if ( config.autorun && !config.blocking ) { 553 | process(); 554 | } 555 | } 556 | 557 | function process() { 558 | while ( config.queue.length && !config.blocking ) { 559 | config.queue.shift()(); 560 | } 561 | } 562 | 563 | function saveGlobal() { 564 | config.pollution = []; 565 | 566 | if ( config.noglobals ) { 567 | for ( var key in window ) { 568 | config.pollution.push( key ); 569 | } 570 | } 571 | } 572 | 573 | function checkPollution( name ) { 574 | var old = config.pollution; 575 | saveGlobal(); 576 | 577 | var newGlobals = diff( old, config.pollution ); 578 | if ( newGlobals.length > 0 ) { 579 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 580 | config.expected++; 581 | } 582 | 583 | var deletedGlobals = diff( config.pollution, old ); 584 | if ( deletedGlobals.length > 0 ) { 585 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 586 | config.expected++; 587 | } 588 | } 589 | 590 | // returns a new Array with the elements that are in a but not in b 591 | function diff( a, b ) { 592 | var result = a.slice(); 593 | for ( var i = 0; i < result.length; i++ ) { 594 | for ( var j = 0; j < b.length; j++ ) { 595 | if ( result[i] === b[j] ) { 596 | result.splice(i, 1); 597 | i--; 598 | break; 599 | } 600 | } 601 | } 602 | return result; 603 | } 604 | 605 | function fail(message, exception, callback) { 606 | if ( typeof console !== "undefined" && console.error && console.warn ) { 607 | console.error(message); 608 | console.error(exception); 609 | console.warn(callback.toString()); 610 | 611 | } else if ( window.opera && opera.postError ) { 612 | opera.postError(message, exception, callback.toString); 613 | } 614 | } 615 | 616 | function extend(a, b) { 617 | for ( var prop in b ) { 618 | a[prop] = b[prop]; 619 | } 620 | 621 | return a; 622 | } 623 | 624 | function addEvent(elem, type, fn) { 625 | if ( elem.addEventListener ) { 626 | elem.addEventListener( type, fn, false ); 627 | } else if ( elem.attachEvent ) { 628 | elem.attachEvent( "on" + type, fn ); 629 | } else { 630 | fn(); 631 | } 632 | } 633 | 634 | function id(name) { 635 | return !!(typeof document !== "undefined" && document && document.getElementById) && 636 | document.getElementById( name ); 637 | } 638 | 639 | // Test for equality any JavaScript type. 640 | // Discussions and reference: http://philrathe.com/articles/equiv 641 | // Test suites: http://philrathe.com/tests/equiv 642 | // Author: Philippe Rathé 643 | QUnit.equiv = function () { 644 | 645 | var innerEquiv; // the real equiv function 646 | var callers = []; // stack to decide between skip/abort functions 647 | 648 | 649 | // Determine what is o. 650 | function hoozit(o) { 651 | if (o.constructor === String) { 652 | return "string"; 653 | 654 | } else if (o.constructor === Boolean) { 655 | return "boolean"; 656 | 657 | } else if (o.constructor === Number) { 658 | 659 | if (isNaN(o)) { 660 | return "nan"; 661 | } else { 662 | return "number"; 663 | } 664 | 665 | } else if (typeof o === "undefined") { 666 | return "undefined"; 667 | 668 | // consider: typeof null === object 669 | } else if (o === null) { 670 | return "null"; 671 | 672 | // consider: typeof [] === object 673 | } else if (o instanceof Array) { 674 | return "array"; 675 | 676 | // consider: typeof new Date() === object 677 | } else if (o instanceof Date) { 678 | return "date"; 679 | 680 | // consider: /./ instanceof Object; 681 | // /./ instanceof RegExp; 682 | // typeof /./ === "function"; // => false in IE and Opera, 683 | // true in FF and Safari 684 | } else if (o instanceof RegExp) { 685 | return "regexp"; 686 | 687 | } else if (typeof o === "object") { 688 | return "object"; 689 | 690 | } else if (o instanceof Function) { 691 | return "function"; 692 | } else { 693 | return undefined; 694 | } 695 | } 696 | 697 | // Call the o related callback with the given arguments. 698 | function bindCallbacks(o, callbacks, args) { 699 | var prop = hoozit(o); 700 | if (prop) { 701 | if (hoozit(callbacks[prop]) === "function") { 702 | return callbacks[prop].apply(callbacks, args); 703 | } else { 704 | return callbacks[prop]; // or undefined 705 | } 706 | } 707 | } 708 | 709 | var callbacks = function () { 710 | 711 | // for string, boolean, number and null 712 | function useStrictEquality(b, a) { 713 | if (b instanceof a.constructor || a instanceof b.constructor) { 714 | // to catch short annotaion VS 'new' annotation of a declaration 715 | // e.g. var i = 1; 716 | // var j = new Number(1); 717 | return a == b; 718 | } else { 719 | return a === b; 720 | } 721 | } 722 | 723 | return { 724 | "string": useStrictEquality, 725 | "boolean": useStrictEquality, 726 | "number": useStrictEquality, 727 | "null": useStrictEquality, 728 | "undefined": useStrictEquality, 729 | 730 | "nan": function (b) { 731 | return isNaN(b); 732 | }, 733 | 734 | "date": function (b, a) { 735 | return hoozit(b) === "date" && a.valueOf() === b.valueOf(); 736 | }, 737 | 738 | "regexp": function (b, a) { 739 | return hoozit(b) === "regexp" && 740 | a.source === b.source && // the regex itself 741 | a.global === b.global && // and its modifers (gmi) ... 742 | a.ignoreCase === b.ignoreCase && 743 | a.multiline === b.multiline; 744 | }, 745 | 746 | // - skip when the property is a method of an instance (OOP) 747 | // - abort otherwise, 748 | // initial === would have catch identical references anyway 749 | "function": function () { 750 | var caller = callers[callers.length - 1]; 751 | return caller !== Object && 752 | typeof caller !== "undefined"; 753 | }, 754 | 755 | "array": function (b, a) { 756 | var i; 757 | var len; 758 | 759 | // b could be an object literal here 760 | if ( ! (hoozit(b) === "array")) { 761 | return false; 762 | } 763 | 764 | len = a.length; 765 | if (len !== b.length) { // safe and faster 766 | return false; 767 | } 768 | for (i = 0; i < len; i++) { 769 | if ( ! innerEquiv(a[i], b[i])) { 770 | return false; 771 | } 772 | } 773 | return true; 774 | }, 775 | 776 | "object": function (b, a) { 777 | var i; 778 | var eq = true; // unless we can proove it 779 | var aProperties = [], bProperties = []; // collection of strings 780 | 781 | // comparing constructors is more strict than using instanceof 782 | if ( a.constructor !== b.constructor) { 783 | return false; 784 | } 785 | 786 | // stack constructor before traversing properties 787 | callers.push(a.constructor); 788 | 789 | for (i in a) { // be strict: don't ensures hasOwnProperty and go deep 790 | 791 | aProperties.push(i); // collect a's properties 792 | 793 | if ( ! innerEquiv(a[i], b[i])) { 794 | eq = false; 795 | } 796 | } 797 | 798 | callers.pop(); // unstack, we are done 799 | 800 | for (i in b) { 801 | bProperties.push(i); // collect b's properties 802 | } 803 | 804 | // Ensures identical properties name 805 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 806 | } 807 | }; 808 | }(); 809 | 810 | innerEquiv = function () { // can take multiple arguments 811 | var args = Array.prototype.slice.apply(arguments); 812 | if (args.length < 2) { 813 | return true; // end transition 814 | } 815 | 816 | return (function (a, b) { 817 | if (a === b) { 818 | return true; // catch the most you can 819 | } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { 820 | return false; // don't lose time with error prone cases 821 | } else { 822 | return bindCallbacks(a, callbacks, [b, a]); 823 | } 824 | 825 | // apply transition with (1..n) arguments 826 | })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); 827 | }; 828 | 829 | return innerEquiv; 830 | 831 | }(); 832 | 833 | /** 834 | * jsDump 835 | * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 836 | * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) 837 | * Date: 5/15/2008 838 | * @projectDescription Advanced and extensible data dumping for Javascript. 839 | * @version 1.0.0 840 | * @author Ariel Flesler 841 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 842 | */ 843 | QUnit.jsDump = (function() { 844 | function quote( str ) { 845 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 846 | }; 847 | function literal( o ) { 848 | return o + ''; 849 | }; 850 | function join( pre, arr, post ) { 851 | var s = jsDump.separator(), 852 | base = jsDump.indent(), 853 | inner = jsDump.indent(1); 854 | if ( arr.join ) 855 | arr = arr.join( ',' + s + inner ); 856 | if ( !arr ) 857 | return pre + post; 858 | return [ pre, inner + arr, base + post ].join(s); 859 | }; 860 | function array( arr ) { 861 | var i = arr.length, ret = Array(i); 862 | this.up(); 863 | while ( i-- ) 864 | ret[i] = this.parse( arr[i] ); 865 | this.down(); 866 | return join( '[', ret, ']' ); 867 | }; 868 | 869 | var reName = /^function (\w+)/; 870 | 871 | var jsDump = { 872 | parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance 873 | var parser = this.parsers[ type || this.typeOf(obj) ]; 874 | type = typeof parser; 875 | 876 | return type == 'function' ? parser.call( this, obj ) : 877 | type == 'string' ? parser : 878 | this.parsers.error; 879 | }, 880 | typeOf:function( obj ) { 881 | var type = typeof obj, 882 | f = 'function';//we'll use it 3 times, save it 883 | return type != 'object' && type != f ? type : 884 | !obj ? 'null' : 885 | obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions 886 | obj.getHours ? 'date' : 887 | obj.scrollBy ? 'window' : 888 | obj.nodeName == '#document' ? 'document' : 889 | obj.nodeName ? 'node' : 890 | obj.item ? 'nodelist' : // Safari reports nodelists as functions 891 | obj.callee ? 'arguments' : 892 | obj.call || obj.constructor != Array && //an array would also fall on this hack 893 | (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects 894 | 'length' in obj ? 'array' : 895 | type; 896 | }, 897 | separator:function() { 898 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; 899 | }, 900 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 901 | if ( !this.multiline ) 902 | return ''; 903 | var chr = this.indentChar; 904 | if ( this.HTML ) 905 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 906 | return Array( this._depth_ + (extra||0) ).join(chr); 907 | }, 908 | up:function( a ) { 909 | this._depth_ += a || 1; 910 | }, 911 | down:function( a ) { 912 | this._depth_ -= a || 1; 913 | }, 914 | setParser:function( name, parser ) { 915 | this.parsers[name] = parser; 916 | }, 917 | // The next 3 are exposed so you can use them 918 | quote:quote, 919 | literal:literal, 920 | join:join, 921 | // 922 | _depth_: 1, 923 | // This is the list of parsers, to modify them, use jsDump.setParser 924 | parsers:{ 925 | window: '[Window]', 926 | document: '[Document]', 927 | error:'[ERROR]', //when no parser is found, shouldn't happen 928 | unknown: '[Unknown]', 929 | 'null':'null', 930 | undefined:'undefined', 931 | 'function':function( fn ) { 932 | var ret = 'function', 933 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 934 | if ( name ) 935 | ret += ' ' + name; 936 | ret += '('; 937 | 938 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 939 | return join( ret, this.parse(fn,'functionCode'), '}' ); 940 | }, 941 | array: array, 942 | nodelist: array, 943 | arguments: array, 944 | object:function( map ) { 945 | var ret = [ ]; 946 | this.up(); 947 | for ( var key in map ) 948 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 949 | this.down(); 950 | return join( '{', ret, '}' ); 951 | }, 952 | node:function( node ) { 953 | var open = this.HTML ? '<' : '<', 954 | close = this.HTML ? '>' : '>'; 955 | 956 | var tag = node.nodeName.toLowerCase(), 957 | ret = open + tag; 958 | 959 | for ( var a in this.DOMAttrs ) { 960 | var val = node[this.DOMAttrs[a]]; 961 | if ( val ) 962 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 963 | } 964 | return ret + close + open + '/' + tag + close; 965 | }, 966 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 967 | var l = fn.length; 968 | if ( !l ) return ''; 969 | 970 | var args = Array(l); 971 | while ( l-- ) 972 | args[l] = String.fromCharCode(97+l);//97 is 'a' 973 | return ' ' + args.join(', ') + ' '; 974 | }, 975 | key:quote, //object calls it internally, the key part of an item in a map 976 | functionCode:'[code]', //function calls it internally, it's the content of the function 977 | attribute:quote, //node calls it internally, it's an html attribute value 978 | string:quote, 979 | date:quote, 980 | regexp:literal, //regex 981 | number:literal, 982 | 'boolean':literal 983 | }, 984 | DOMAttrs:{//attributes to dump from nodes, name=>realName 985 | id:'id', 986 | name:'name', 987 | 'class':'className' 988 | }, 989 | HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) 990 | indentChar:' ',//indentation unit 991 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 992 | }; 993 | 994 | return jsDump; 995 | })(); 996 | 997 | })(this); -------------------------------------------------------------------------------- /public/javascripts/qunit_for_rails.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | var QUnit_For_Rails = { 4 | 5 | init: function init() 6 | { 7 | // define qunit menu 8 | var qm = ""; 9 | qm += "
"; 10 | qm += ""; 18 | qm += "
"; 19 | 20 | // define qunit results 21 | var qr = ""; 22 | qr += "
"; 23 | qr += "

Results

"; 24 | qr += "

"; 25 | qr += "

"; 26 | qr += "
\"loading\""; 27 | qr += "
    "; 28 | qr += "
    "; 29 | 30 | // define qunit-overlay 31 | var qo = ""; 32 | qo += "
    "; 33 | qo += "
    "; 34 | qo += "
      "; 35 | qo += "
    • Run All Tests
    • "; 36 | qo += "
    • Or Choose A Test: " + $collection; 37 | qo += "
    "; 38 | qo += ""; 39 | qo += "
    "; 40 | qo += qm; 41 | qo += "
    "; 42 | qo += "JavaScript testing powered by: "; 43 | qo += "\"qunit\"
    "; 44 | qo += "
    "; 45 | qo += qr; 46 | qo += "
    "; 47 | 48 | $("body").prepend(qo); 49 | }, 50 | 51 | respond_to_key: function respond_to_key(e) 52 | { 53 | if (document.activeElement['nodeName'] == "INPUT" || document.activeElement['nodeName'] == "TEXTAREA") { 54 | // escape if in a textfield 55 | } else { 56 | if (e.shiftKey) { 57 | var unicode = e.keyCode? e.keyCode : e.charCode; 58 | switch (unicode) { 59 | case 83: case 115: // s keypress 60 | $('#qunit-overlay').show(); 61 | break; 62 | case 72: case 104: // h keypress 63 | if($("#qunit-results").height() > 0) 64 | $("#qunit-results").animate({ height: "0px"}, 500 ); 65 | $('#qunit-overlay').hide(); 66 | break; 67 | case 191: case 47: // ? keypress 68 | $('#qunit-overlay').show(); 69 | $('#qunit-menu').toggle(); 70 | break; 71 | case 65: case 97: // a keypress 72 | $("#qunit-all-tests").click(); 73 | break; 74 | case 84: case 116: // t keypress 75 | if($("#qunit-results").height() > 0) { 76 | $("#qunit-results").animate({ height: "0px"}, 500 ); 77 | } else { 78 | $('#qunit-overlay').show(); 79 | $("#qunit-results").animate({ height: "400px"}, 500 ); 80 | } 81 | break; 82 | default: 83 | break; 84 | } 85 | } 86 | } 87 | }, 88 | 89 | load_js_file: function load_js_file(filename) 90 | { 91 | var fileref=document.createElement('script'); 92 | fileref.setAttribute("type","text/javascript"); 93 | fileref.setAttribute("src", "/javascripts/tests/" + filename + "?" + Math.floor(Math.random()*11)); 94 | if (typeof fileref!="undefined") 95 | document.getElementsByTagName("head")[0].appendChild(fileref); 96 | }, 97 | 98 | unload_js_file: function unload_js_file(filename) 99 | { 100 | var targetelement="script"; 101 | var targetattr="src"; 102 | var allsuspects=document.getElementsByTagName(targetelement); 103 | for (var i=allsuspects.length; i>=0; i--) { 104 | if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null && allsuspects[i].getAttribute(targetattr).indexOf(filename)!=-1) 105 | allsuspects[i].parentNode.removeChild(allsuspects[i]); 106 | } 107 | }, 108 | 109 | show_tests: function show_tests() 110 | { 111 | $('#qunit-overlay').show(); 112 | // clear out any previous results 113 | $("#qunit-tests li").remove(); 114 | $("#qunit-testresult").remove(); 115 | $("#qunit-banner").attr("class", ""); 116 | $("#qunit-results").show(); 117 | $("#qunit-results").animate({ height: "400px"}, 500 ); 118 | $("#qunit-loading").show(); 119 | }, 120 | 121 | run_tests: function run_tests() 122 | { 123 | init(); 124 | for (var i in tests) { 125 | var test = eval(tests[i]); 126 | test(test); 127 | } 128 | $("#qunit-loading").hide(); 129 | start(); 130 | } 131 | }; 132 | 133 | $(document).ready(function() 134 | { 135 | QUnit_For_Rails.init(); 136 | 137 | $("#qunit-results").hide(); 138 | $("#qunit-loading").hide(); 139 | $("#qunit-results").animate({ height: "0px"}, 1 ); 140 | $('#qunit-menu').toggle(); 141 | 142 | if ($.browser.mozilla) { 143 | $(document).keypress (QUnit_For_Rails.respond_to_key); 144 | } else { 145 | $(document).keydown (QUnit_For_Rails.respond_to_key); 146 | } 147 | 148 | $("#qunit-test-select").change( function() { 149 | tests = {}; 150 | for(var i in $list) { 151 | QUnit_For_Rails.unload_js_file($list[i]); 152 | } 153 | if ($(this).val() != "all") { 154 | QUnit_For_Rails.load_js_file($(this).val()); 155 | } else { 156 | for(var j in $list) { 157 | QUnit_For_Rails.load_js_file($list[j]); 158 | } 159 | } 160 | QUnit_For_Rails.show_tests(); 161 | setTimeout(QUnit_For_Rails.run_tests, 2000); 162 | }); 163 | 164 | $("#qunit-all-tests").click( function() { 165 | $("#qunit-test-select option:contains(all)").attr("selected", true); 166 | $("#qunit-test-select").change(); 167 | }); 168 | 169 | if ($autohide == "true") { 170 | $('#qunit-overlay').toggle(); 171 | } 172 | }); 173 | 174 | })(); -------------------------------------------------------------------------------- /public/javascripts/tests/test.js: -------------------------------------------------------------------------------- 1 | var localtests = { 2 | test_another_example : function() 3 | { 4 | module("Module Test"); 5 | test("simple passing example", function() 6 | { 7 | var value = "foo"; 8 | equals( "foo", value, "We expect value to be foo" ); 9 | }); 10 | } 11 | }; 12 | 13 | $().extend(tests, localtests); -------------------------------------------------------------------------------- /public/stylesheets/qunit_for_rails.css: -------------------------------------------------------------------------------- 1 | h1#qunit-header 2 | { 3 | padding: 5px; 4 | padding-left: 30px; 5 | font-size: large; 6 | background-color: #0f1924; 7 | color: yellow; 8 | font-family: 'trebuchet ms', verdana, arial; 9 | margin: 0; 10 | } 11 | 12 | h1#qunit-header a 13 | { 14 | color: white; 15 | } 16 | 17 | h2#qunit-banner 18 | { 19 | height: 8px; 20 | border-bottom: 1px solid white; 21 | background-color: #eee; 22 | margin: 0; 23 | font-family: 'trebuchet ms', verdana, arial; 24 | } 25 | 26 | h2#qunit-banner.pass 27 | { 28 | background-color: green; 29 | } 30 | 31 | h2#qunit-banner.fail 32 | { 33 | background-color: red; 34 | } 35 | 36 | h2#qunit-userAgent 37 | { 38 | padding: 10px; 39 | padding-left: 30px; 40 | background-color: #eee; 41 | color: black; 42 | margin: 0; 43 | font-size: small; 44 | font-weight: normal; 45 | font-family: 'trebuchet ms', verdana, arial; 46 | font-size: 10pt; 47 | } 48 | 49 | div#qunit-testrunner-toolbar 50 | { 51 | padding-left: 30px; 52 | background: #eee; 53 | border-top: 1px solid black; 54 | padding: 10px; 55 | font-family: 'trebuchet ms', verdana, arial; 56 | margin: 0; 57 | font-size: 10pt; 58 | } 59 | 60 | ol#qunit-tests 61 | { 62 | margin-left: 30px; 63 | font-family: 'trebuchet ms', verdana, arial; 64 | font-size: 10pt; 65 | } 66 | 67 | ol#qunit-tests li strong 68 | { 69 | cursor:pointer; 70 | } 71 | 72 | ol#qunit-tests .pass 73 | { 74 | color: green; 75 | } 76 | 77 | ol#qunit-tests .fail 78 | { 79 | color: red; 80 | } 81 | 82 | p#qunit-testresult 83 | { 84 | padding-left: 30px; 85 | margin-left: 1em; 86 | font-size: 10pt; 87 | font-family: 'trebuchet ms', verdana, arial; 88 | } 89 | 90 | ul.qunit 91 | { 92 | list-style: none; 93 | height: 34px; 94 | background: url(/images/bg_secondaryNav_left.gif) no-repeat; 95 | float: left; 96 | margin: 0px; 97 | padding: 0px 0px 0px 3px; 98 | } 99 | 100 | ul.qunit li 101 | { 102 | float: left; 103 | height: 18px; 104 | padding: 8px; 105 | } 106 | 107 | ul.qunit li a 108 | { 109 | position: relative; 110 | top: 0px; 111 | color: white; 112 | text-decoration: none; 113 | } 114 | 115 | ul.qunit li.first 116 | { 117 | border-right: 1px solid rgb(13, 89, 149); 118 | } 119 | 120 | ul.qunit li.last 121 | { 122 | margin-top: 6px; 123 | padding-top: 0px; 124 | border-left: 1px solid rgb(17, 117, 174); 125 | } 126 | 127 | #qunit-overlay 128 | { 129 | position: relative; 130 | top: 0px; 131 | left: 0px; 132 | width: 100%; 133 | height: 46px; 134 | color: white; 135 | margin: 0px; 136 | padding: 0px; 137 | background-color: #0f1924; 138 | font-family: 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif; 139 | } 140 | 141 | #qunit-test-options 142 | { 143 | float: left; 144 | position: relative; 145 | top: 5px; 146 | left: 30px; 147 | height: 34px; 148 | } 149 | 150 | #qunit-logo 151 | { 152 | position: absolute; 153 | top: 6px; 154 | right: 5px; 155 | width: 265px; 156 | height: 40px; 157 | font-size: 10px; 158 | color: #DEDEDE; 159 | font-style: italic; 160 | } 161 | 162 | #qunit-menu 163 | { 164 | float: left; 165 | width: 690px; 166 | height: 30px; 167 | background: url(/images/bg_diagonalDarkBlue.gif) rgb(51, 51, 51); 168 | padding: 2px; 169 | margin-top: 6px; 170 | margin-left: 40px; 171 | font-family: 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif; 172 | color: yellow; 173 | } 174 | 175 | ul.qunit-menu 176 | { 177 | list-style: none; 178 | float: left; 179 | margin: 0px; 180 | padding: 0px 0px 0px 0px; 181 | font-size: 11px; 182 | margin-bottom: -22px; 183 | } 184 | 185 | ul.qunit-menu li 186 | { 187 | vertical-align: baseline; 188 | padding: 6px; 189 | float: left; 190 | font-weight: normal; 191 | } 192 | 193 | #qunit-loading 194 | { 195 | padding-left: 30px; 196 | } 197 | 198 | #qunit-results 199 | { 200 | position: relative; 201 | top: 30; 202 | width: 100%; 203 | height: 200px; 204 | background-color: #eee; 205 | color: #000; 206 | padding: 0px; 207 | overflow: hidden; 208 | } -------------------------------------------------------------------------------- /tasks/qunit_for_rails_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :qunit_for_rails do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /test/qunit_for_rails_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'test/unit' 3 | require File.dirname(__FILE__) + '/../lib/qunit_for_rails' 4 | 5 | class QunitForRailsTest < ActiveSupport::TestCase 6 | include QunitForRails 7 | 8 | def setup 9 | arr = Dir.entries(File.dirname(__FILE__) + "/../../../../public/javascripts/tests") 10 | @arr_size = arr.size - 2 # removing . and .. 11 | end 12 | 13 | # include_qunit includes string containing qunit js files 14 | test "will insert javascript files into the head" do 15 | assert include_qunit.scan(/javascripts\/qunit.js/) 16 | assert include_qunit.scan(/javascripts\/qunit_for_rails.js/) 17 | assert include_qunit.scan(/stylesheets\/qunit_for_rails.css/) 18 | end 19 | 20 | # list_tasks will return a list of the test files as a string representing an array 21 | test "can list files in a directory" do 22 | assert @arr_size, list_tests.to_a.size 23 | assert_not_nil list_tests.scan(/../) 24 | end 25 | 26 | # collect_tasks loads test files from directory and creates select block 27 | test "can create dropdown of tests" do 28 | # number of file options match arr_size of files in directory 29 | num = collect_tests.scan(/