diff options
author | Scott González <scott.gonzalez@gmail.com> | 2015-04-03 15:21:16 -0400 |
---|---|---|
committer | Scott González <scott.gonzalez@gmail.com> | 2015-04-09 09:21:06 -0400 |
commit | 7c896ddb8563e1f4fc655904614cf72d010e2ecb (patch) | |
tree | 160c3d2c1c4aa7e89f38f8371558f3fccd8e8d8a /tests/lib | |
parent | d0ea32e3ad613eaaa523d0c88c776dab2b26b25f (diff) | |
download | jquery-ui-7c896ddb8563e1f4fc655904614cf72d010e2ecb.tar.gz jquery-ui-7c896ddb8563e1f4fc655904614cf72d010e2ecb.zip |
Tests: Change test infrastructure to use AMD and reduce boilerplate
Ref #10119
Ref gh-1528
* Adds RequireJS and relies on AMD for loading dependencies.
* Updates to grunt-contrib-qunit 0.6.0.
* Convert `domEqual()` to a proper QUnit assertion.
* Introduces two bootstrap files (JS and CSS) which use `data-` attributes to
reduce the amount of boilerplate needed in each test
Diffstat (limited to 'tests/lib')
-rw-r--r-- | tests/lib/bootstrap.js | 146 | ||||
-rw-r--r-- | tests/lib/common.js | 133 | ||||
-rw-r--r-- | tests/lib/css.js | 23 | ||||
-rw-r--r-- | tests/lib/helper.js | 33 | ||||
-rw-r--r-- | tests/lib/qunit-assert-domequal.js | 122 | ||||
-rw-r--r-- | tests/lib/qunit.js | 45 |
6 files changed, 502 insertions, 0 deletions
diff --git a/tests/lib/bootstrap.js b/tests/lib/bootstrap.js new file mode 100644 index 000000000..c5e52057e --- /dev/null +++ b/tests/lib/bootstrap.js @@ -0,0 +1,146 @@ +( function() { + +window.requirejs = { + paths: { + "jquery": jqueryUrl(), + "jquery-simulate": "../../../external/jquery-simulate/jquery.simulate", + "jshint": "../../../external/jshint/jshint", + "lib": "../../lib", + "phantom-bridge": "../../../node_modules/grunt-contrib-qunit/phantomjs/bridge", + "qunit-assert-classes": "../../../external/qunit-assert-classes/qunit-assert-classes", + "qunit": "../../../external/qunit/qunit", + "ui": "../../../ui" + }, + shim: { + "jquery-simulate": [ "jquery" ], + "qunit-assert-classes": [ "qunit" ] + } +}; + +// Load all modules in series +function requireModules( dependencies, callback, modules ) { + if ( !dependencies.length ) { + if ( callback ) { + callback.apply( null, modules ); + } + return; + } + + if ( !modules ) { + modules = []; + } + + var dependency = dependencies.shift(); + require( [ dependency ], function( module ) { + modules.push( module ); + requireModules( dependencies, callback, modules ); + } ); +} + +// Load a set of test file along with the required test infrastructure +function requireTests( dependencies, callback ) { + dependencies = [ + "../../lib/qunit", + "jquery", + "jquery-simulate", + "qunit-assert-classes", + "../../lib/qunit-assert-domequal" + ].concat( dependencies ); + + requireModules( dependencies, function( QUnit ) { + swarmInject(); + QUnit.start(); + } ); +} + +// Parse the URL into key/value pairs +function parseUrl() { + var data = {}; + var parts = document.location.search.slice( 1 ).split( "&" ); + var length = parts.length; + var i = 0; + var current; + + for ( ; i < length; i++ ) { + current = parts[ i ].split( "=" ); + data[ current[ 0 ] ] = current[ 1 ]; + } + + return data; +} + +function jqueryUrl() { + var version = parseUrl().jquery; + var url; + + if ( version === "git" || version === "git1" ) { + url = "http://code.jquery.com/jquery-" + version; + } else { + url = "../../../external/jquery-" + ( version || "1.11.2" ) + "/jquery"; + } + + return url; +}; + +function swarmInject() { + var url = parseUrl().swarmURL; + + if ( !url || url.indexOf( "http" ) !== 0 ) { + return; + } + + document.write( "<script src='http://swarm.jquery.org/js/inject.js?" + + (new Date()).getTime() + "'></script>" ); +} + +// Load test modules based on data attributes +// - data-modules: list of test modules to load +// - data-widget: A widget to load test modules for +// - Automatically loads common, core, events, methods, and options +// - data-deprecated: Loads the deprecated test modules for a widget +(function() { + + // Find the script element + var scripts = document.getElementsByTagName( "script" ); + var script = scripts[ scripts.length - 1 ]; + + // Read the modules + var modules = script.getAttribute( "data-modules" ); + if ( modules ) { + modules = modules + .replace( /^\s+|\s+$/g, "" ) + .split( /\s+/ ); + } else { + modules = []; + } + var widget = script.getAttribute( "data-widget" ); + var deprecated = script.getAttribute( "data-deprecated" ); + if ( widget ) { + modules = modules.concat([ + widget + ( deprecated ? "_common_deprecated" : "_common" ), + widget + "_core", + widget + "_events", + widget + "_methods", + widget + "_options" + ]); + if ( deprecated ) { + modules = modules.concat( widget + "_deprecated" ); + } + } + + // Load requirejs, then load the tests + script = document.createElement( "script" ); + script.src = "../../../external/requirejs/require.js"; + script.onload = function() { + + // Create a dummy bridge if we're not actually testing in PhantomJS + if ( !/PhantomJS/.test( navigator.userAgent ) ) { + define( "phantom-bridge", function() {} ); + } + + requireTests( modules ); + }; + document.documentElement.appendChild( script ); +} )(); + +} )(); diff --git a/tests/lib/common.js b/tests/lib/common.js new file mode 100644 index 000000000..9faa4e246 --- /dev/null +++ b/tests/lib/common.js @@ -0,0 +1,133 @@ +define([ + "jquery" +], function( $ ) { + +var exports = {}; + +function testWidgetDefaults( widget, defaults ) { + var pluginDefaults = $.ui[ widget ].prototype.options; + + // Ensure that all defaults have the correct value + test( "defined defaults", function() { + var count = 0; + $.each( defaults, function( key, val ) { + expect( ++count ); + if ( $.isFunction( val ) ) { + ok( $.isFunction( pluginDefaults[ key ] ), key ); + return; + } + deepEqual( pluginDefaults[ key ], val, key ); + }); + }); + + // Ensure that all defaults were tested + test( "tested defaults", function() { + var count = 0; + $.each( pluginDefaults, function( key ) { + expect( ++count ); + ok( key in defaults, key ); + }); + }); +} + +function testWidgetOverrides( widget ) { + if ( $.uiBackCompat === false ) { + test( "$.widget overrides", function() { + expect( 4 ); + $.each([ + "_createWidget", + "destroy", + "option", + "_trigger" + ], function( i, method ) { + strictEqual( $.ui[ widget ].prototype[ method ], + $.Widget.prototype[ method ], "should not override " + method ); + }); + }); + } +} + +function testBasicUsage( widget ) { + test( "basic usage", function() { + expect( 3 ); + + var defaultElement = $.ui[ widget ].prototype.defaultElement; + $( defaultElement ).appendTo( "body" )[ widget ]().remove(); + ok( true, "initialized on element" ); + + $( defaultElement )[ widget ]().remove(); + ok( true, "initialized on disconnected DOMElement - never connected" ); + + // Ensure manipulating removed elements works (#3664) + $( defaultElement ).appendTo( "body" ).remove()[ widget ]().remove(); + ok( true, "initialized on disconnected DOMElement - removed" ); + }); +} + +exports.testWidget = function( widget, settings ) { + module( widget + ": common widget" ); + + exports.testJshint( widget ); + testWidgetDefaults( widget, settings.defaults ); + testWidgetOverrides( widget ); + testBasicUsage( widget ); + test( "version", function() { + expect( 1 ); + ok( "version" in $.ui[ widget ].prototype, "version property exists" ); + }); +}; + +exports.testJshint = function( module ) { + + // Function.prototype.bind check is needed because JSHint doesn't work in ES3 browsers anymore + // https://github.com/jshint/jshint/issues/1384 + if ( QUnit.urlParams.nojshint || !Function.prototype.bind ) { + return; + } + + asyncTest( "JSHint", function() { + require( [ "jshint" ], function() { + expect( 1 ); + + $.when( + $.ajax( { + url: "../../../ui/.jshintrc", + dataType: "json" + } ), + $.ajax( { + url: "../../../ui/" + module + ".js", + dataType: "text" + } ) + ) + .done( function( hintArgs, srcArgs ) { + var globals, passed, errors, + jshintrc = hintArgs[ 0 ], + source = srcArgs[ 0 ]; + + globals = jshintrc.globals || {}; + delete jshintrc.globals; + passed = JSHINT( source, jshintrc, globals ); + errors = $.map( JSHINT.errors, function( error ) { + + // JSHINT may report null if there are too many errors + if ( !error ) { + return; + } + + return "[L" + error.line + ":C" + error.character + "] " + + error.reason + "\n" + error.evidence + "\n"; + } ).join( "\n" ); + ok( passed, errors ); + start(); + } ) + .fail(function( hintError, srcError ) { + ok( false, "error loading source: " + ( hintError || srcError ).statusText ); + start(); + } ); + }); + }); +}; + +return exports; + +}); diff --git a/tests/lib/css.js b/tests/lib/css.js new file mode 100644 index 000000000..37353e912 --- /dev/null +++ b/tests/lib/css.js @@ -0,0 +1,23 @@ +(function() { + +function includeStyle( url ) { + document.write( "<link rel='stylesheet' href='../../../" + url + "'>" ); +} + +// Find the script element +var scripts = document.getElementsByTagName( "script" ); +var script = scripts[ scripts.length - 1 ]; + +// Load the modules +var modules = script.getAttribute( "data-modules" ); +if ( modules ) { + modules = modules.split( /\s+/ ); + for ( var i = 0; i < modules.length; i++ ) { + includeStyle( "themes/base/" + modules[ i ] + ".css" ); + } +} + +// Load the QUnit stylesheet +includeStyle( "external/qunit/qunit.css" ); + +} )(); diff --git a/tests/lib/helper.js b/tests/lib/helper.js new file mode 100644 index 000000000..80cb27274 --- /dev/null +++ b/tests/lib/helper.js @@ -0,0 +1,33 @@ +define([ + "jquery" +], function( $ ) { + +var exports = {}; + +exports.forceScrollableWindow = function( appendTo ) { + + // The main testable area is 10000x10000 so to enforce scrolling, + // this DIV must be greater than 10000 to work + return $( "<div>" ) + .css({ + height: "11000px", + width: "11000px" + }) + .appendTo( appendTo || "#qunit-fixture" ); +}; + +exports.onFocus = function( element, onFocus ) { + var fn = function( event ) { + if ( !event.originalEvent ) { + return; + } + element.unbind( "focus", fn ); + onFocus(); + }; + + element.bind( "focus", fn )[ 0 ].focus(); +}; + +return exports; + +}); diff --git a/tests/lib/qunit-assert-domequal.js b/tests/lib/qunit-assert-domequal.js new file mode 100644 index 000000000..49c8f6714 --- /dev/null +++ b/tests/lib/qunit-assert-domequal.js @@ -0,0 +1,122 @@ +/* + * Experimental assertion for comparing DOM objects. + * + * Serializes an element and some properties and attributes and its children if any, + * otherwise the text. Then compares the result using deepEqual(). + */ +define( [ + "qunit", + "jquery" +], function( QUnit, $ ) { + +var domEqual = QUnit.assert.domEqual = function( selector, modifier, message ) { + + var assert = this; + + // Get current state prior to modifier + var expected = extract( $( selector ) ); + + function done() { + var actual = extract( $( selector ) ); + assert.push( QUnit.equiv( actual, expected ), actual, expected, message ); + } + + // Run modifier (async or sync), then compare state via done() + if ( modifier.length ) { + modifier( done ); + } else { + modifier(); + done(); + } +}; + +domEqual.properties = [ + "disabled", + "readOnly" +]; + +domEqual.attributes = [ + "autocomplete", + "aria-activedescendant", + "aria-controls", + "aria-describedby", + "aria-disabled", + "aria-expanded", + "aria-haspopup", + "aria-hidden", + "aria-labelledby", + "aria-pressed", + "aria-selected", + "aria-valuemax", + "aria-valuemin", + "aria-valuenow", + "class", + "href", + "id", + "nodeName", + "role", + "tabIndex", + "title" +]; + +function getElementStyles( elem ) { + var styles = {}; + var style = elem.ownerDocument.defaultView ? + elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : + elem.currentStyle; + var key, len; + + if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { + len = style.length; + while ( len-- ) { + key = style[ len ]; + if ( typeof style[ key ] === "string" ) { + styles[ $.camelCase( key ) ] = style[ key ]; + } + } + + // Support: Opera, IE <9 + } else { + for ( key in style ) { + if ( typeof style[ key ] === "string" ) { + styles[ key ] = style[ key ]; + } + } + } + + return styles; +} + +function extract( elem ) { + if ( !elem || !elem.length ) { + QUnit.push( false, actual, expected, + "domEqual failed, can't extract " + selector + ", message was: " + message ); + return; + } + + var result = {}; + var children; + $.each( domEqual.properties, function( index, attr ) { + var value = elem.prop( attr ); + result[ attr ] = value != null ? value : ""; + }); + $.each( domEqual.attributes, function( index, attr ) { + var value = elem.attr( attr ); + result[ attr ] = value != null ? value : ""; + }); + result.style = getElementStyles( elem[ 0 ] ); + result.events = $._data( elem[ 0 ], "events" ); + result.data = $.extend( {}, elem.data() ); + delete result.data[ $.expando ]; + children = elem.children(); + if ( children.length ) { + result.children = elem.children().map(function() { + return extract( $( this ) ); + }).get(); + } else { + result.text = elem.text(); + } + return result; +} + +} ); diff --git a/tests/lib/qunit.js b/tests/lib/qunit.js new file mode 100644 index 000000000..fc119a47d --- /dev/null +++ b/tests/lib/qunit.js @@ -0,0 +1,45 @@ +define( [ + "qunit", + "jquery", + "phantom-bridge" +], function( QUnit, $ ) { + +QUnit.config.autostart = false; +QUnit.config.requireExpects = true; + +QUnit.config.urlConfig.push({ + id: "nojshint", + label: "Skip JSHint", + tooltip: "Skip running JSHint, e.g., within TestSwarm, where Jenkins runs it already" +}); + +QUnit.config.urlConfig.push({ + id: "jquery", + label: "jQuery version", + value: [ + "1.7.0", "1.7.1", "1.7.2", + "1.8.0", "1.8.1", "1.8.2", "1.8.3", + "1.9.0", "1.9.1", + "1.10.0", "1.10.1", "1.10.2", + "1.11.0", "1.11.1", "1.11.2", + "2.0.0", "2.0.1", "2.0.2", "2.0.3", + "2.1.0", "2.1.1", "2.1.2", "2.1.3", + "git1", "git" + ], + tooltip: "Which jQuery Core version to test against" +}); + +QUnit.reset = ( function( reset ) { + return function() { + + // Ensure jQuery events and data on the fixture are properly removed + $( "#qunit-fixture" ).empty(); + + // Let QUnit reset the fixture + reset.apply( this, arguments ); + }; +} )( QUnit.reset ); + +return QUnit; + +} ); |