Ticket #37117: 37117.diff
File 37117.diff, 408.9 KB (added by , 5 years ago) |
---|
-
package.json
63 63 "jquery-migrate": "1.4.1", 64 64 "matchdep": "~2.0.0", 65 65 "node-sass": "~4.13.1", 66 "qunit": "~2.9.0", 67 "sinon": "~9.0.0", 68 "sinon-test": "~3.0.0", 66 69 "source-map-loader": "^0.2.4", 67 70 "uglify-js": "^3.6.0", 68 71 "uglifyjs-webpack-plugin": "2.2.0", -
tests/qunit/index.html
56 56 <script src="../../build/wp-includes/js/mce-view.js"></script> 57 57 58 58 <!-- QUnit --> 59 <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" /> 60 <script src="vendor/qunit.js"></script> 61 <script src="vendor/sinon.js"></script> 62 <script src="vendor/sinon-qunit.js"></script> 63 <script>QUnit.config.hidepassed = false;</script> 59 <link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css" type="text/css" media="screen" /> 60 <script src="../../node_modules/qunit/qunit/qunit.js"></script> 61 <script src="../../node_modules/sinon/pkg/sinon.js"></script> 62 <script src="../../node_modules/sinon-test/dist/sinon-test.js"></script> 63 <script> 64 var qTest = QUnit.test, sTest = sinonTest( sinon ); 65 66 QUnit.config.hidepassed = false; 67 68 QUnit.test = function( testName, callback ) { 69 return qTest( testName, sTest(callback) ); 70 }; 71 </script> 64 72 </head> 65 73 <body> 66 74 <div id="qunit"></div> -
tests/qunit/vendor/qunit.css
1 /*!2 * QUnit 1.18.03 * http://qunitjs.com/4 *5 * Copyright jQuery Foundation and other contributors6 * Released under the MIT license7 * http://jquery.org/license8 *9 * Date: 2015-04-03T10:23Z10 */11 12 /** Font Family and Sizes */13 14 #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {15 font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;16 }17 18 #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }19 #qunit-tests { font-size: smaller; }20 21 22 /** Resets */23 24 #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {25 margin: 0;26 padding: 0;27 }28 29 30 /** Header */31 32 #qunit-header {33 padding: 0.5em 0 0.5em 1em;34 35 color: #8699A4;36 background-color: #0D3349;37 38 font-size: 1.5em;39 line-height: 1em;40 font-weight: 400;41 42 border-radius: 5px 5px 0 0;43 }44 45 #qunit-header a {46 text-decoration: none;47 color: #C2CCD1;48 }49 50 #qunit-header a:hover,51 #qunit-header a:focus {52 color: #FFF;53 }54 55 #qunit-testrunner-toolbar label {56 display: inline-block;57 padding: 0 0.5em 0 0.1em;58 }59 60 #qunit-banner {61 height: 5px;62 }63 64 #qunit-testrunner-toolbar {65 padding: 0.5em 1em 0.5em 1em;66 color: #5E740B;67 background-color: #EEE;68 overflow: hidden;69 }70 71 #qunit-userAgent {72 padding: 0.5em 1em 0.5em 1em;73 background-color: #2B81AF;74 color: #FFF;75 text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;76 }77 78 #qunit-modulefilter-container {79 float: right;80 padding: 0.2em;81 }82 83 .qunit-url-config {84 display: inline-block;85 padding: 0.1em;86 }87 88 .qunit-filter {89 display: block;90 float: right;91 margin-left: 1em;92 }93 94 /** Tests: Pass/Fail */95 96 #qunit-tests {97 list-style-position: inside;98 }99 100 #qunit-tests li {101 padding: 0.4em 1em 0.4em 1em;102 border-bottom: 1px solid #FFF;103 list-style-position: inside;104 }105 106 #qunit-tests > li {107 display: none;108 }109 110 #qunit-tests li.running,111 #qunit-tests li.pass,112 #qunit-tests li.fail,113 #qunit-tests li.skipped {114 display: list-item;115 }116 117 #qunit-tests.hidepass li.running,118 #qunit-tests.hidepass li.pass {119 visibility: hidden;120 position: absolute;121 width: 0px;122 height: 0px;123 padding: 0;124 border: 0;125 margin: 0;126 }127 128 #qunit-tests li strong {129 cursor: pointer;130 }131 132 #qunit-tests li.skipped strong {133 cursor: default;134 }135 136 #qunit-tests li a {137 padding: 0.5em;138 color: #C2CCD1;139 text-decoration: none;140 }141 142 #qunit-tests li p a {143 padding: 0.25em;144 color: #6B6464;145 }146 #qunit-tests li a:hover,147 #qunit-tests li a:focus {148 color: #000;149 }150 151 #qunit-tests li .runtime {152 float: right;153 font-size: smaller;154 }155 156 .qunit-assert-list {157 margin-top: 0.5em;158 padding: 0.5em;159 160 background-color: #FFF;161 162 border-radius: 5px;163 }164 165 .qunit-collapsed {166 display: none;167 }168 169 #qunit-tests table {170 border-collapse: collapse;171 margin-top: 0.2em;172 }173 174 #qunit-tests th {175 text-align: right;176 vertical-align: top;177 padding: 0 0.5em 0 0;178 }179 180 #qunit-tests td {181 vertical-align: top;182 }183 184 #qunit-tests pre {185 margin: 0;186 white-space: pre-wrap;187 word-wrap: break-word;188 }189 190 #qunit-tests del {191 background-color: #E0F2BE;192 color: #374E0C;193 text-decoration: none;194 }195 196 #qunit-tests ins {197 background-color: #FFCACA;198 color: #500;199 text-decoration: none;200 }201 202 /*** Test Counts */203 204 #qunit-tests b.counts { color: #000; }205 #qunit-tests b.passed { color: #5E740B; }206 #qunit-tests b.failed { color: #710909; }207 208 #qunit-tests li li {209 padding: 5px;210 background-color: #FFF;211 border-bottom: none;212 list-style-position: inside;213 }214 215 /*** Passing Styles */216 217 #qunit-tests li li.pass {218 color: #3C510C;219 background-color: #FFF;220 border-left: 10px solid #C6E746;221 }222 223 #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }224 #qunit-tests .pass .test-name { color: #366097; }225 226 #qunit-tests .pass .test-actual,227 #qunit-tests .pass .test-expected { color: #999; }228 229 #qunit-banner.qunit-pass { background-color: #C6E746; }230 231 /*** Failing Styles */232 233 #qunit-tests li li.fail {234 color: #710909;235 background-color: #FFF;236 border-left: 10px solid #EE5757;237 white-space: pre;238 }239 240 #qunit-tests > li:last-child {241 border-radius: 0 0 5px 5px;242 }243 244 #qunit-tests .fail { color: #000; background-color: #EE5757; }245 #qunit-tests .fail .test-name,246 #qunit-tests .fail .module-name { color: #000; }247 248 #qunit-tests .fail .test-actual { color: #EE5757; }249 #qunit-tests .fail .test-expected { color: #008000; }250 251 #qunit-banner.qunit-fail { background-color: #EE5757; }252 253 /*** Skipped tests */254 255 #qunit-tests .skipped {256 background-color: #EBECE9;257 }258 259 #qunit-tests .qunit-skipped-label {260 background-color: #F4FF77;261 display: inline-block;262 font-style: normal;263 color: #366097;264 line-height: 1.8em;265 padding: 0 0.5em;266 margin: -0.4em 0.4em -0.4em 0;267 }268 269 /** Result */270 271 #qunit-testresult {272 padding: 0.5em 1em 0.5em 1em;273 274 color: #2B81AF;275 background-color: #D2E0E6;276 277 border-bottom: 1px solid #FFF;278 }279 #qunit-testresult .module-name {280 font-weight: 700;281 }282 283 /** Fixture */284 285 #qunit-fixture {286 position: absolute;287 top: -10000px;288 left: -10000px;289 width: 1000px;290 height: 1000px;291 } -
tests/qunit/vendor/qunit.js
Property changes on: tests/qunit/vendor/qunit.css ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 /*!2 * QUnit 1.18.03 * http://qunitjs.com/4 *5 * Copyright jQuery Foundation and other contributors6 * Released under the MIT license7 * http://jquery.org/license8 *9 * Date: 2015-04-03T10:23Z10 */11 12 (function( window ) {13 14 var QUnit,15 config,16 onErrorFnPrev,17 loggingCallbacks = {},18 fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),19 toString = Object.prototype.toString,20 hasOwn = Object.prototype.hasOwnProperty,21 // Keep a local reference to Date (GH-283)22 Date = window.Date,23 now = Date.now || function() {24 return new Date().getTime();25 },26 globalStartCalled = false,27 runStarted = false,28 setTimeout = window.setTimeout,29 clearTimeout = window.clearTimeout,30 defined = {31 document: window.document !== undefined,32 setTimeout: window.setTimeout !== undefined,33 sessionStorage: (function() {34 var x = "qunit-test-string";35 try {36 sessionStorage.setItem( x, x );37 sessionStorage.removeItem( x );38 return true;39 } catch ( e ) {40 return false;41 }42 }())43 },44 /**45 * Provides a normalized error string, correcting an issue46 * with IE 7 (and prior) where Error.prototype.toString is47 * not properly implemented48 *49 * Based on http://es5.github.com/#x15.11.4.450 *51 * @param {String|Error} error52 * @return {String} error message53 */54 errorString = function( error ) {55 var name, message,56 errorString = error.toString();57 if ( errorString.substring( 0, 7 ) === "[object" ) {58 name = error.name ? error.name.toString() : "Error";59 message = error.message ? error.message.toString() : "";60 if ( name && message ) {61 return name + ": " + message;62 } else if ( name ) {63 return name;64 } else if ( message ) {65 return message;66 } else {67 return "Error";68 }69 } else {70 return errorString;71 }72 },73 /**74 * Makes a clone of an object using only Array or Object as base,75 * and copies over the own enumerable properties.76 *77 * @param {Object} obj78 * @return {Object} New object with only the own properties (recursively).79 */80 objectValues = function( obj ) {81 var key, val,82 vals = QUnit.is( "array", obj ) ? [] : {};83 for ( key in obj ) {84 if ( hasOwn.call( obj, key ) ) {85 val = obj[ key ];86 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;87 }88 }89 return vals;90 };91 92 QUnit = {};93 94 /**95 * Config object: Maintain internal state96 * Later exposed as QUnit.config97 * `config` initialized at top of scope98 */99 config = {100 // The queue of tests to run101 queue: [],102 103 // block until document ready104 blocking: true,105 106 // by default, run previously failed tests first107 // very useful in combination with "Hide passed tests" checked108 reorder: true,109 110 // by default, modify document.title when suite is done111 altertitle: true,112 113 // by default, scroll to top of the page when suite is done114 scrolltop: true,115 116 // when enabled, all tests must call expect()117 requireExpects: false,118 119 // depth up-to which object will be dumped120 maxDepth: 5,121 122 // add checkboxes that are persisted in the query-string123 // when enabled, the id is set to `true` as a `QUnit.config` property124 urlConfig: [125 {126 id: "hidepassed",127 label: "Hide passed tests",128 tooltip: "Only show tests and assertions that fail. Stored as query-strings."129 },130 {131 id: "noglobals",132 label: "Check for Globals",133 tooltip: "Enabling this will test if any test introduces new properties on the " +134 "`window` object. Stored as query-strings."135 },136 {137 id: "notrycatch",138 label: "No try-catch",139 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +140 "exceptions in IE reasonable. Stored as query-strings."141 }142 ],143 144 // Set of all modules.145 modules: [],146 147 // The first unnamed module148 currentModule: {149 name: "",150 tests: []151 },152 153 callbacks: {}154 };155 156 // Push a loose unnamed module to the modules collection157 config.modules.push( config.currentModule );158 159 // Initialize more QUnit.config and QUnit.urlParams160 (function() {161 var i, current,162 location = window.location || { search: "", protocol: "file:" },163 params = location.search.slice( 1 ).split( "&" ),164 length = params.length,165 urlParams = {};166 167 if ( params[ 0 ] ) {168 for ( i = 0; i < length; i++ ) {169 current = params[ i ].split( "=" );170 current[ 0 ] = decodeURIComponent( current[ 0 ] );171 172 // allow just a key to turn on a flag, e.g., test.html?noglobals173 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;174 if ( urlParams[ current[ 0 ] ] ) {175 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );176 } else {177 urlParams[ current[ 0 ] ] = current[ 1 ];178 }179 }180 }181 182 if ( urlParams.filter === true ) {183 delete urlParams.filter;184 }185 186 QUnit.urlParams = urlParams;187 188 // String search anywhere in moduleName+testName189 config.filter = urlParams.filter;190 191 if ( urlParams.maxDepth ) {192 config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?193 Number.POSITIVE_INFINITY :194 urlParams.maxDepth;195 }196 197 config.testId = [];198 if ( urlParams.testId ) {199 200 // Ensure that urlParams.testId is an array201 urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );202 for ( i = 0; i < urlParams.testId.length; i++ ) {203 config.testId.push( urlParams.testId[ i ] );204 }205 }206 207 // Figure out if we're running the tests from a server or not208 QUnit.isLocal = location.protocol === "file:";209 210 // Expose the current QUnit version211 QUnit.version = "1.18.0";212 }());213 214 // Root QUnit object.215 // `QUnit` initialized at top of scope216 extend( QUnit, {217 218 // call on start of module test to prepend name to all tests219 module: function( name, testEnvironment ) {220 var currentModule = {221 name: name,222 testEnvironment: testEnvironment,223 tests: []224 };225 226 // DEPRECATED: handles setup/teardown functions,227 // beforeEach and afterEach should be used instead228 if ( testEnvironment && testEnvironment.setup ) {229 testEnvironment.beforeEach = testEnvironment.setup;230 delete testEnvironment.setup;231 }232 if ( testEnvironment && testEnvironment.teardown ) {233 testEnvironment.afterEach = testEnvironment.teardown;234 delete testEnvironment.teardown;235 }236 237 config.modules.push( currentModule );238 config.currentModule = currentModule;239 },240 241 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.242 asyncTest: function( testName, expected, callback ) {243 if ( arguments.length === 2 ) {244 callback = expected;245 expected = null;246 }247 248 QUnit.test( testName, expected, callback, true );249 },250 251 test: function( testName, expected, callback, async ) {252 var test;253 254 if ( arguments.length === 2 ) {255 callback = expected;256 expected = null;257 }258 259 test = new Test({260 testName: testName,261 expected: expected,262 async: async,263 callback: callback264 });265 266 test.queue();267 },268 269 skip: function( testName ) {270 var test = new Test({271 testName: testName,272 skip: true273 });274 275 test.queue();276 },277 278 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.279 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.280 start: function( count ) {281 var globalStartAlreadyCalled = globalStartCalled;282 283 if ( !config.current ) {284 globalStartCalled = true;285 286 if ( runStarted ) {287 throw new Error( "Called start() outside of a test context while already started" );288 } else if ( globalStartAlreadyCalled || count > 1 ) {289 throw new Error( "Called start() outside of a test context too many times" );290 } else if ( config.autostart ) {291 throw new Error( "Called start() outside of a test context when " +292 "QUnit.config.autostart was true" );293 } else if ( !config.pageLoaded ) {294 295 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it296 config.autostart = true;297 return;298 }299 } else {300 301 // If a test is running, adjust its semaphore302 config.current.semaphore -= count || 1;303 304 // Don't start until equal number of stop-calls305 if ( config.current.semaphore > 0 ) {306 return;307 }308 309 // throw an Error if start is called more often than stop310 if ( config.current.semaphore < 0 ) {311 config.current.semaphore = 0;312 313 QUnit.pushFailure(314 "Called start() while already started (test's semaphore was 0 already)",315 sourceFromStacktrace( 2 )316 );317 return;318 }319 }320 321 resumeProcessing();322 },323 324 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.325 stop: function( count ) {326 327 // If there isn't a test running, don't allow QUnit.stop() to be called328 if ( !config.current ) {329 throw new Error( "Called stop() outside of a test context" );330 }331 332 // If a test is running, adjust its semaphore333 config.current.semaphore += count || 1;334 335 pauseProcessing();336 },337 338 config: config,339 340 // Safe object type checking341 is: function( type, obj ) {342 return QUnit.objectType( obj ) === type;343 },344 345 objectType: function( obj ) {346 if ( typeof obj === "undefined" ) {347 return "undefined";348 }349 350 // Consider: typeof null === object351 if ( obj === null ) {352 return "null";353 }354 355 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),356 type = match && match[ 1 ] || "";357 358 switch ( type ) {359 case "Number":360 if ( isNaN( obj ) ) {361 return "nan";362 }363 return "number";364 case "String":365 case "Boolean":366 case "Array":367 case "Date":368 case "RegExp":369 case "Function":370 return type.toLowerCase();371 }372 if ( typeof obj === "object" ) {373 return "object";374 }375 return undefined;376 },377 378 extend: extend,379 380 load: function() {381 config.pageLoaded = true;382 383 // Initialize the configuration options384 extend( config, {385 stats: { all: 0, bad: 0 },386 moduleStats: { all: 0, bad: 0 },387 started: 0,388 updateRate: 1000,389 autostart: true,390 filter: ""391 }, true );392 393 config.blocking = false;394 395 if ( config.autostart ) {396 resumeProcessing();397 }398 }399 });400 401 // Register logging callbacks402 (function() {403 var i, l, key,404 callbacks = [ "begin", "done", "log", "testStart", "testDone",405 "moduleStart", "moduleDone" ];406 407 function registerLoggingCallback( key ) {408 var loggingCallback = function( callback ) {409 if ( QUnit.objectType( callback ) !== "function" ) {410 throw new Error(411 "QUnit logging methods require a callback function as their first parameters."412 );413 }414 415 config.callbacks[ key ].push( callback );416 };417 418 // DEPRECATED: This will be removed on QUnit 2.0.0+419 // Stores the registered functions allowing restoring420 // at verifyLoggingCallbacks() if modified421 loggingCallbacks[ key ] = loggingCallback;422 423 return loggingCallback;424 }425 426 for ( i = 0, l = callbacks.length; i < l; i++ ) {427 key = callbacks[ i ];428 429 // Initialize key collection of logging callback430 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {431 config.callbacks[ key ] = [];432 }433 434 QUnit[ key ] = registerLoggingCallback( key );435 }436 })();437 438 // `onErrorFnPrev` initialized at top of scope439 // Preserve other handlers440 onErrorFnPrev = window.onerror;441 442 // Cover uncaught exceptions443 // Returning true will suppress the default browser handler,444 // returning false will let it run.445 window.onerror = function( error, filePath, linerNr ) {446 var ret = false;447 if ( onErrorFnPrev ) {448 ret = onErrorFnPrev( error, filePath, linerNr );449 }450 451 // Treat return value as window.onerror itself does,452 // Only do our handling if not suppressed.453 if ( ret !== true ) {454 if ( QUnit.config.current ) {455 if ( QUnit.config.current.ignoreGlobalErrors ) {456 return true;457 }458 QUnit.pushFailure( error, filePath + ":" + linerNr );459 } else {460 QUnit.test( "global failure", extend(function() {461 QUnit.pushFailure( error, filePath + ":" + linerNr );462 }, { validTest: true } ) );463 }464 return false;465 }466 467 return ret;468 };469 470 function done() {471 var runtime, passed;472 473 config.autorun = true;474 475 // Log the last module results476 if ( config.previousModule ) {477 runLoggingCallbacks( "moduleDone", {478 name: config.previousModule.name,479 tests: config.previousModule.tests,480 failed: config.moduleStats.bad,481 passed: config.moduleStats.all - config.moduleStats.bad,482 total: config.moduleStats.all,483 runtime: now() - config.moduleStats.started484 });485 }486 delete config.previousModule;487 488 runtime = now() - config.started;489 passed = config.stats.all - config.stats.bad;490 491 runLoggingCallbacks( "done", {492 failed: config.stats.bad,493 passed: passed,494 total: config.stats.all,495 runtime: runtime496 });497 }498 499 // Doesn't support IE6 to IE9, it will return undefined on these browsers500 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack501 function extractStacktrace( e, offset ) {502 offset = offset === undefined ? 4 : offset;503 504 var stack, include, i;505 506 if ( e.stack ) {507 stack = e.stack.split( "\n" );508 if ( /^error$/i.test( stack[ 0 ] ) ) {509 stack.shift();510 }511 if ( fileName ) {512 include = [];513 for ( i = offset; i < stack.length; i++ ) {514 if ( stack[ i ].indexOf( fileName ) !== -1 ) {515 break;516 }517 include.push( stack[ i ] );518 }519 if ( include.length ) {520 return include.join( "\n" );521 }522 }523 return stack[ offset ];524 525 // Support: Safari <=6 only526 } else if ( e.sourceURL ) {527 528 // exclude useless self-reference for generated Error objects529 if ( /qunit.js$/.test( e.sourceURL ) ) {530 return;531 }532 533 // for actual exceptions, this is useful534 return e.sourceURL + ":" + e.line;535 }536 }537 538 function sourceFromStacktrace( offset ) {539 var error = new Error();540 541 // Support: Safari <=7 only, IE <=10 - 11 only542 // Not all browsers generate the `stack` property for `new Error()`, see also #636543 if ( !error.stack ) {544 try {545 throw error;546 } catch ( err ) {547 error = err;548 }549 }550 551 return extractStacktrace( error, offset );552 }553 554 function synchronize( callback, last ) {555 if ( QUnit.objectType( callback ) === "array" ) {556 while ( callback.length ) {557 synchronize( callback.shift() );558 }559 return;560 }561 config.queue.push( callback );562 563 if ( config.autorun && !config.blocking ) {564 process( last );565 }566 }567 568 function process( last ) {569 function next() {570 process( last );571 }572 var start = now();573 config.depth = ( config.depth || 0 ) + 1;574 575 while ( config.queue.length && !config.blocking ) {576 if ( !defined.setTimeout || config.updateRate <= 0 ||577 ( ( now() - start ) < config.updateRate ) ) {578 if ( config.current ) {579 580 // Reset async tracking for each phase of the Test lifecycle581 config.current.usedAsync = false;582 }583 config.queue.shift()();584 } else {585 setTimeout( next, 13 );586 break;587 }588 }589 config.depth--;590 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {591 done();592 }593 }594 595 function begin() {596 var i, l,597 modulesLog = [];598 599 // If the test run hasn't officially begun yet600 if ( !config.started ) {601 602 // Record the time of the test run's beginning603 config.started = now();604 605 verifyLoggingCallbacks();606 607 // Delete the loose unnamed module if unused.608 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {609 config.modules.shift();610 }611 612 // Avoid unnecessary information by not logging modules' test environments613 for ( i = 0, l = config.modules.length; i < l; i++ ) {614 modulesLog.push({615 name: config.modules[ i ].name,616 tests: config.modules[ i ].tests617 });618 }619 620 // The test run is officially beginning now621 runLoggingCallbacks( "begin", {622 totalTests: Test.count,623 modules: modulesLog624 });625 }626 627 config.blocking = false;628 process( true );629 }630 631 function resumeProcessing() {632 runStarted = true;633 634 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)635 if ( defined.setTimeout ) {636 setTimeout(function() {637 if ( config.current && config.current.semaphore > 0 ) {638 return;639 }640 if ( config.timeout ) {641 clearTimeout( config.timeout );642 }643 644 begin();645 }, 13 );646 } else {647 begin();648 }649 }650 651 function pauseProcessing() {652 config.blocking = true;653 654 if ( config.testTimeout && defined.setTimeout ) {655 clearTimeout( config.timeout );656 config.timeout = setTimeout(function() {657 if ( config.current ) {658 config.current.semaphore = 0;659 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );660 } else {661 throw new Error( "Test timed out" );662 }663 resumeProcessing();664 }, config.testTimeout );665 }666 }667 668 function saveGlobal() {669 config.pollution = [];670 671 if ( config.noglobals ) {672 for ( var key in window ) {673 if ( hasOwn.call( window, key ) ) {674 // in Opera sometimes DOM element ids show up here, ignore them675 if ( /^qunit-test-output/.test( key ) ) {676 continue;677 }678 config.pollution.push( key );679 }680 }681 }682 }683 684 function checkPollution() {685 var newGlobals,686 deletedGlobals,687 old = config.pollution;688 689 saveGlobal();690 691 newGlobals = diff( config.pollution, old );692 if ( newGlobals.length > 0 ) {693 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );694 }695 696 deletedGlobals = diff( old, config.pollution );697 if ( deletedGlobals.length > 0 ) {698 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );699 }700 }701 702 // returns a new Array with the elements that are in a but not in b703 function diff( a, b ) {704 var i, j,705 result = a.slice();706 707 for ( i = 0; i < result.length; i++ ) {708 for ( j = 0; j < b.length; j++ ) {709 if ( result[ i ] === b[ j ] ) {710 result.splice( i, 1 );711 i--;712 break;713 }714 }715 }716 return result;717 }718 719 function extend( a, b, undefOnly ) {720 for ( var prop in b ) {721 if ( hasOwn.call( b, prop ) ) {722 723 // Avoid "Member not found" error in IE8 caused by messing with window.constructor724 if ( !( prop === "constructor" && a === window ) ) {725 if ( b[ prop ] === undefined ) {726 delete a[ prop ];727 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {728 a[ prop ] = b[ prop ];729 }730 }731 }732 }733 734 return a;735 }736 737 function runLoggingCallbacks( key, args ) {738 var i, l, callbacks;739 740 callbacks = config.callbacks[ key ];741 for ( i = 0, l = callbacks.length; i < l; i++ ) {742 callbacks[ i ]( args );743 }744 }745 746 // DEPRECATED: This will be removed on 2.0.0+747 // This function verifies if the loggingCallbacks were modified by the user748 // If so, it will restore it, assign the given callback and print a console warning749 function verifyLoggingCallbacks() {750 var loggingCallback, userCallback;751 752 for ( loggingCallback in loggingCallbacks ) {753 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {754 755 userCallback = QUnit[ loggingCallback ];756 757 // Restore the callback function758 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];759 760 // Assign the deprecated given callback761 QUnit[ loggingCallback ]( userCallback );762 763 if ( window.console && window.console.warn ) {764 window.console.warn(765 "QUnit." + loggingCallback + " was replaced with a new value.\n" +766 "Please, check out the documentation on how to apply logging callbacks.\n" +767 "Reference: http://api.qunitjs.com/category/callbacks/"768 );769 }770 }771 }772 }773 774 // from jquery.js775 function inArray( elem, array ) {776 if ( array.indexOf ) {777 return array.indexOf( elem );778 }779 780 for ( var i = 0, length = array.length; i < length; i++ ) {781 if ( array[ i ] === elem ) {782 return i;783 }784 }785 786 return -1;787 }788 789 function Test( settings ) {790 var i, l;791 792 ++Test.count;793 794 extend( this, settings );795 this.assertions = [];796 this.semaphore = 0;797 this.usedAsync = false;798 this.module = config.currentModule;799 this.stack = sourceFromStacktrace( 3 );800 801 // Register unique strings802 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {803 if ( this.module.tests[ i ].name === this.testName ) {804 this.testName += " ";805 }806 }807 808 this.testId = generateHash( this.module.name, this.testName );809 810 this.module.tests.push({811 name: this.testName,812 testId: this.testId813 });814 815 if ( settings.skip ) {816 817 // Skipped tests will fully ignore any sent callback818 this.callback = function() {};819 this.async = false;820 this.expected = 0;821 } else {822 this.assert = new Assert( this );823 }824 }825 826 Test.count = 0;827 828 Test.prototype = {829 before: function() {830 if (831 832 // Emit moduleStart when we're switching from one module to another833 this.module !== config.previousModule ||834 835 // They could be equal (both undefined) but if the previousModule property doesn't836 // yet exist it means this is the first test in a suite that isn't wrapped in a837 // module, in which case we'll just emit a moduleStart event for 'undefined'.838 // Without this, reporters can get testStart before moduleStart which is a problem.839 !hasOwn.call( config, "previousModule" )840 ) {841 if ( hasOwn.call( config, "previousModule" ) ) {842 runLoggingCallbacks( "moduleDone", {843 name: config.previousModule.name,844 tests: config.previousModule.tests,845 failed: config.moduleStats.bad,846 passed: config.moduleStats.all - config.moduleStats.bad,847 total: config.moduleStats.all,848 runtime: now() - config.moduleStats.started849 });850 }851 config.previousModule = this.module;852 config.moduleStats = { all: 0, bad: 0, started: now() };853 runLoggingCallbacks( "moduleStart", {854 name: this.module.name,855 tests: this.module.tests856 });857 }858 859 config.current = this;860 861 this.testEnvironment = extend( {}, this.module.testEnvironment );862 delete this.testEnvironment.beforeEach;863 delete this.testEnvironment.afterEach;864 865 this.started = now();866 runLoggingCallbacks( "testStart", {867 name: this.testName,868 module: this.module.name,869 testId: this.testId870 });871 872 if ( !config.pollution ) {873 saveGlobal();874 }875 },876 877 run: function() {878 var promise;879 880 config.current = this;881 882 if ( this.async ) {883 QUnit.stop();884 }885 886 this.callbackStarted = now();887 888 if ( config.notrycatch ) {889 promise = this.callback.call( this.testEnvironment, this.assert );890 this.resolvePromise( promise );891 return;892 }893 894 try {895 promise = this.callback.call( this.testEnvironment, this.assert );896 this.resolvePromise( promise );897 } catch ( e ) {898 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +899 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );900 901 // else next test will carry the responsibility902 saveGlobal();903 904 // Restart the tests if they're blocking905 if ( config.blocking ) {906 QUnit.start();907 }908 }909 },910 911 after: function() {912 checkPollution();913 },914 915 queueHook: function( hook, hookName ) {916 var promise,917 test = this;918 return function runHook() {919 config.current = test;920 if ( config.notrycatch ) {921 promise = hook.call( test.testEnvironment, test.assert );922 test.resolvePromise( promise, hookName );923 return;924 }925 try {926 promise = hook.call( test.testEnvironment, test.assert );927 test.resolvePromise( promise, hookName );928 } catch ( error ) {929 test.pushFailure( hookName + " failed on " + test.testName + ": " +930 ( error.message || error ), extractStacktrace( error, 0 ) );931 }932 };933 },934 935 // Currently only used for module level hooks, can be used to add global level ones936 hooks: function( handler ) {937 var hooks = [];938 939 // Hooks are ignored on skipped tests940 if ( this.skip ) {941 return hooks;942 }943 944 if ( this.module.testEnvironment &&945 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {946 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );947 }948 949 return hooks;950 },951 952 finish: function() {953 config.current = this;954 if ( config.requireExpects && this.expected === null ) {955 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +956 "not called.", this.stack );957 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {958 this.pushFailure( "Expected " + this.expected + " assertions, but " +959 this.assertions.length + " were run", this.stack );960 } else if ( this.expected === null && !this.assertions.length ) {961 this.pushFailure( "Expected at least one assertion, but none were run - call " +962 "expect(0) to accept zero assertions.", this.stack );963 }964 965 var i,966 bad = 0;967 968 this.runtime = now() - this.started;969 config.stats.all += this.assertions.length;970 config.moduleStats.all += this.assertions.length;971 972 for ( i = 0; i < this.assertions.length; i++ ) {973 if ( !this.assertions[ i ].result ) {974 bad++;975 config.stats.bad++;976 config.moduleStats.bad++;977 }978 }979 980 runLoggingCallbacks( "testDone", {981 name: this.testName,982 module: this.module.name,983 skipped: !!this.skip,984 failed: bad,985 passed: this.assertions.length - bad,986 total: this.assertions.length,987 runtime: this.runtime,988 989 // HTML Reporter use990 assertions: this.assertions,991 testId: this.testId,992 993 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead994 duration: this.runtime995 });996 997 // QUnit.reset() is deprecated and will be replaced for a new998 // fixture reset function on QUnit 2.0/2.1.999 // It's still called here for backwards compatibility handling1000 QUnit.reset();1001 1002 config.current = undefined;1003 },1004 1005 queue: function() {1006 var bad,1007 test = this;1008 1009 if ( !this.valid() ) {1010 return;1011 }1012 1013 function run() {1014 1015 // each of these can by async1016 synchronize([1017 function() {1018 test.before();1019 },1020 1021 test.hooks( "beforeEach" ),1022 1023 function() {1024 test.run();1025 },1026 1027 test.hooks( "afterEach" ).reverse(),1028 1029 function() {1030 test.after();1031 },1032 function() {1033 test.finish();1034 }1035 ]);1036 }1037 1038 // `bad` initialized at top of scope1039 // defer when previous test run passed, if storage is available1040 bad = QUnit.config.reorder && defined.sessionStorage &&1041 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );1042 1043 if ( bad ) {1044 run();1045 } else {1046 synchronize( run, true );1047 }1048 },1049 1050 push: function( result, actual, expected, message ) {1051 var source,1052 details = {1053 module: this.module.name,1054 name: this.testName,1055 result: result,1056 message: message,1057 actual: actual,1058 expected: expected,1059 testId: this.testId,1060 runtime: now() - this.started1061 };1062 1063 if ( !result ) {1064 source = sourceFromStacktrace();1065 1066 if ( source ) {1067 details.source = source;1068 }1069 }1070 1071 runLoggingCallbacks( "log", details );1072 1073 this.assertions.push({1074 result: !!result,1075 message: message1076 });1077 },1078 1079 pushFailure: function( message, source, actual ) {1080 if ( !this instanceof Test ) {1081 throw new Error( "pushFailure() assertion outside test context, was " +1082 sourceFromStacktrace( 2 ) );1083 }1084 1085 var details = {1086 module: this.module.name,1087 name: this.testName,1088 result: false,1089 message: message || "error",1090 actual: actual || null,1091 testId: this.testId,1092 runtime: now() - this.started1093 };1094 1095 if ( source ) {1096 details.source = source;1097 }1098 1099 runLoggingCallbacks( "log", details );1100 1101 this.assertions.push({1102 result: false,1103 message: message1104 });1105 },1106 1107 resolvePromise: function( promise, phase ) {1108 var then, message,1109 test = this;1110 if ( promise != null ) {1111 then = promise.then;1112 if ( QUnit.objectType( then ) === "function" ) {1113 QUnit.stop();1114 then.call(1115 promise,1116 QUnit.start,1117 function( error ) {1118 message = "Promise rejected " +1119 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +1120 " " + test.testName + ": " + ( error.message || error );1121 test.pushFailure( message, extractStacktrace( error, 0 ) );1122 1123 // else next test will carry the responsibility1124 saveGlobal();1125 1126 // Unblock1127 QUnit.start();1128 }1129 );1130 }1131 }1132 },1133 1134 valid: function() {1135 var include,1136 filter = config.filter && config.filter.toLowerCase(),1137 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),1138 fullName = ( this.module.name + ": " + this.testName ).toLowerCase();1139 1140 // Internally-generated tests are always valid1141 if ( this.callback && this.callback.validTest ) {1142 return true;1143 }1144 1145 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {1146 return false;1147 }1148 1149 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {1150 return false;1151 }1152 1153 if ( !filter ) {1154 return true;1155 }1156 1157 include = filter.charAt( 0 ) !== "!";1158 if ( !include ) {1159 filter = filter.slice( 1 );1160 }1161 1162 // If the filter matches, we need to honour include1163 if ( fullName.indexOf( filter ) !== -1 ) {1164 return include;1165 }1166 1167 // Otherwise, do the opposite1168 return !include;1169 }1170 1171 };1172 1173 // Resets the test setup. Useful for tests that modify the DOM.1174 /*1175 DEPRECATED: Use multiple tests instead of resetting inside a test.1176 Use testStart or testDone for custom cleanup.1177 This method will throw an error in 2.0, and will be removed in 2.11178 */1179 QUnit.reset = function() {1180 1181 // Return on non-browser environments1182 // This is necessary to not break on node tests1183 if ( typeof window === "undefined" ) {1184 return;1185 }1186 1187 var fixture = defined.document && document.getElementById &&1188 document.getElementById( "qunit-fixture" );1189 1190 if ( fixture ) {1191 fixture.innerHTML = config.fixture;1192 }1193 };1194 1195 QUnit.pushFailure = function() {1196 if ( !QUnit.config.current ) {1197 throw new Error( "pushFailure() assertion outside test context, in " +1198 sourceFromStacktrace( 2 ) );1199 }1200 1201 // Gets current test obj1202 var currentTest = QUnit.config.current;1203 1204 return currentTest.pushFailure.apply( currentTest, arguments );1205 };1206 1207 // Based on Java's String.hashCode, a simple but not1208 // rigorously collision resistant hashing function1209 function generateHash( module, testName ) {1210 var hex,1211 i = 0,1212 hash = 0,1213 str = module + "\x1C" + testName,1214 len = str.length;1215 1216 for ( ; i < len; i++ ) {1217 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );1218 hash |= 0;1219 }1220 1221 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't1222 // strictly necessary but increases user understanding that the id is a SHA-like hash1223 hex = ( 0x100000000 + hash ).toString( 16 );1224 if ( hex.length < 8 ) {1225 hex = "0000000" + hex;1226 }1227 1228 return hex.slice( -8 );1229 }1230 1231 function Assert( testContext ) {1232 this.test = testContext;1233 }1234 1235 // Assert helpers1236 QUnit.assert = Assert.prototype = {1237 1238 // Specify the number of expected assertions to guarantee that failed test1239 // (no assertions are run at all) don't slip through.1240 expect: function( asserts ) {1241 if ( arguments.length === 1 ) {1242 this.test.expected = asserts;1243 } else {1244 return this.test.expected;1245 }1246 },1247 1248 // Increment this Test's semaphore counter, then return a single-use function that1249 // decrements that counter a maximum of once.1250 async: function() {1251 var test = this.test,1252 popped = false;1253 1254 test.semaphore += 1;1255 test.usedAsync = true;1256 pauseProcessing();1257 1258 return function done() {1259 if ( !popped ) {1260 test.semaphore -= 1;1261 popped = true;1262 resumeProcessing();1263 } else {1264 test.pushFailure( "Called the callback returned from `assert.async` more than once",1265 sourceFromStacktrace( 2 ) );1266 }1267 };1268 },1269 1270 // Exports test.push() to the user API1271 push: function( /* result, actual, expected, message */ ) {1272 var assert = this,1273 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;1274 1275 // Backwards compatibility fix.1276 // Allows the direct use of global exported assertions and QUnit.assert.*1277 // Although, it's use is not recommended as it can leak assertions1278 // to other tests from async tests, because we only get a reference to the current test,1279 // not exactly the test where assertion were intended to be called.1280 if ( !currentTest ) {1281 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );1282 }1283 1284 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {1285 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",1286 sourceFromStacktrace( 2 ) );1287 1288 // Allow this assertion to continue running anyway...1289 }1290 1291 if ( !( assert instanceof Assert ) ) {1292 assert = currentTest.assert;1293 }1294 return assert.test.push.apply( assert.test, arguments );1295 },1296 1297 ok: function( result, message ) {1298 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +1299 QUnit.dump.parse( result ) );1300 this.push( !!result, result, true, message );1301 },1302 1303 notOk: function( result, message ) {1304 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +1305 QUnit.dump.parse( result ) );1306 this.push( !result, result, false, message );1307 },1308 1309 equal: function( actual, expected, message ) {1310 /*jshint eqeqeq:false */1311 this.push( expected == actual, actual, expected, message );1312 },1313 1314 notEqual: function( actual, expected, message ) {1315 /*jshint eqeqeq:false */1316 this.push( expected != actual, actual, expected, message );1317 },1318 1319 propEqual: function( actual, expected, message ) {1320 actual = objectValues( actual );1321 expected = objectValues( expected );1322 this.push( QUnit.equiv( actual, expected ), actual, expected, message );1323 },1324 1325 notPropEqual: function( actual, expected, message ) {1326 actual = objectValues( actual );1327 expected = objectValues( expected );1328 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );1329 },1330 1331 deepEqual: function( actual, expected, message ) {1332 this.push( QUnit.equiv( actual, expected ), actual, expected, message );1333 },1334 1335 notDeepEqual: function( actual, expected, message ) {1336 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );1337 },1338 1339 strictEqual: function( actual, expected, message ) {1340 this.push( expected === actual, actual, expected, message );1341 },1342 1343 notStrictEqual: function( actual, expected, message ) {1344 this.push( expected !== actual, actual, expected, message );1345 },1346 1347 "throws": function( block, expected, message ) {1348 var actual, expectedType,1349 expectedOutput = expected,1350 ok = false,1351 currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;1352 1353 // 'expected' is optional unless doing string comparison1354 if ( message == null && typeof expected === "string" ) {1355 message = expected;1356 expected = null;1357 }1358 1359 currentTest.ignoreGlobalErrors = true;1360 try {1361 block.call( currentTest.testEnvironment );1362 } catch (e) {1363 actual = e;1364 }1365 currentTest.ignoreGlobalErrors = false;1366 1367 if ( actual ) {1368 expectedType = QUnit.objectType( expected );1369 1370 // we don't want to validate thrown error1371 if ( !expected ) {1372 ok = true;1373 expectedOutput = null;1374 1375 // expected is a regexp1376 } else if ( expectedType === "regexp" ) {1377 ok = expected.test( errorString( actual ) );1378 1379 // expected is a string1380 } else if ( expectedType === "string" ) {1381 ok = expected === errorString( actual );1382 1383 // expected is a constructor, maybe an Error constructor1384 } else if ( expectedType === "function" && actual instanceof expected ) {1385 ok = true;1386 1387 // expected is an Error object1388 } else if ( expectedType === "object" ) {1389 ok = actual instanceof expected.constructor &&1390 actual.name === expected.name &&1391 actual.message === expected.message;1392 1393 // expected is a validation function which returns true if validation passed1394 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {1395 expectedOutput = null;1396 ok = true;1397 }1398 }1399 1400 currentTest.assert.push( ok, actual, expectedOutput, message );1401 }1402 };1403 1404 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word1405 // Known to us are: Closure Compiler, Narwhal1406 (function() {1407 /*jshint sub:true */1408 Assert.prototype.raises = Assert.prototype[ "throws" ];1409 }());1410 1411 // Test for equality any JavaScript type.1412 // Author: Philippe Rathé <prathe@gmail.com>1413 QUnit.equiv = (function() {1414 1415 // Call the o related callback with the given arguments.1416 function bindCallbacks( o, callbacks, args ) {1417 var prop = QUnit.objectType( o );1418 if ( prop ) {1419 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {1420 return callbacks[ prop ].apply( callbacks, args );1421 } else {1422 return callbacks[ prop ]; // or undefined1423 }1424 }1425 }1426 1427 // the real equiv function1428 var innerEquiv,1429 1430 // stack to decide between skip/abort functions1431 callers = [],1432 1433 // stack to avoiding loops from circular referencing1434 parents = [],1435 parentsB = [],1436 1437 getProto = Object.getPrototypeOf || function( obj ) {1438 /* jshint camelcase: false, proto: true */1439 return obj.__proto__;1440 },1441 callbacks = (function() {1442 1443 // for string, boolean, number and null1444 function useStrictEquality( b, a ) {1445 1446 /*jshint eqeqeq:false */1447 if ( b instanceof a.constructor || a instanceof b.constructor ) {1448 1449 // to catch short annotation VS 'new' annotation of a1450 // declaration1451 // e.g. var i = 1;1452 // var j = new Number(1);1453 return a == b;1454 } else {1455 return a === b;1456 }1457 }1458 1459 return {1460 "string": useStrictEquality,1461 "boolean": useStrictEquality,1462 "number": useStrictEquality,1463 "null": useStrictEquality,1464 "undefined": useStrictEquality,1465 1466 "nan": function( b ) {1467 return isNaN( b );1468 },1469 1470 "date": function( b, a ) {1471 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();1472 },1473 1474 "regexp": function( b, a ) {1475 return QUnit.objectType( b ) === "regexp" &&1476 1477 // the regex itself1478 a.source === b.source &&1479 1480 // and its modifiers1481 a.global === b.global &&1482 1483 // (gmi) ...1484 a.ignoreCase === b.ignoreCase &&1485 a.multiline === b.multiline &&1486 a.sticky === b.sticky;1487 },1488 1489 // - skip when the property is a method of an instance (OOP)1490 // - abort otherwise,1491 // initial === would have catch identical references anyway1492 "function": function() {1493 var caller = callers[ callers.length - 1 ];1494 return caller !== Object && typeof caller !== "undefined";1495 },1496 1497 "array": function( b, a ) {1498 var i, j, len, loop, aCircular, bCircular;1499 1500 // b could be an object literal here1501 if ( QUnit.objectType( b ) !== "array" ) {1502 return false;1503 }1504 1505 len = a.length;1506 if ( len !== b.length ) {1507 // safe and faster1508 return false;1509 }1510 1511 // track reference to avoid circular references1512 parents.push( a );1513 parentsB.push( b );1514 for ( i = 0; i < len; i++ ) {1515 loop = false;1516 for ( j = 0; j < parents.length; j++ ) {1517 aCircular = parents[ j ] === a[ i ];1518 bCircular = parentsB[ j ] === b[ i ];1519 if ( aCircular || bCircular ) {1520 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {1521 loop = true;1522 } else {1523 parents.pop();1524 parentsB.pop();1525 return false;1526 }1527 }1528 }1529 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {1530 parents.pop();1531 parentsB.pop();1532 return false;1533 }1534 }1535 parents.pop();1536 parentsB.pop();1537 return true;1538 },1539 1540 "object": function( b, a ) {1541 1542 /*jshint forin:false */1543 var i, j, loop, aCircular, bCircular,1544 // Default to true1545 eq = true,1546 aProperties = [],1547 bProperties = [];1548 1549 // comparing constructors is more strict than using1550 // instanceof1551 if ( a.constructor !== b.constructor ) {1552 1553 // Allow objects with no prototype to be equivalent to1554 // objects with Object as their constructor.1555 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||1556 ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {1557 return false;1558 }1559 }1560 1561 // stack constructor before traversing properties1562 callers.push( a.constructor );1563 1564 // track reference to avoid circular references1565 parents.push( a );1566 parentsB.push( b );1567 1568 // be strict: don't ensure hasOwnProperty and go deep1569 for ( i in a ) {1570 loop = false;1571 for ( j = 0; j < parents.length; j++ ) {1572 aCircular = parents[ j ] === a[ i ];1573 bCircular = parentsB[ j ] === b[ i ];1574 if ( aCircular || bCircular ) {1575 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {1576 loop = true;1577 } else {1578 eq = false;1579 break;1580 }1581 }1582 }1583 aProperties.push( i );1584 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {1585 eq = false;1586 break;1587 }1588 }1589 1590 parents.pop();1591 parentsB.pop();1592 callers.pop(); // unstack, we are done1593 1594 for ( i in b ) {1595 bProperties.push( i ); // collect b's properties1596 }1597 1598 // Ensures identical properties name1599 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );1600 }1601 };1602 }());1603 1604 innerEquiv = function() { // can take multiple arguments1605 var args = [].slice.apply( arguments );1606 if ( args.length < 2 ) {1607 return true; // end transition1608 }1609 1610 return ( (function( a, b ) {1611 if ( a === b ) {1612 return true; // catch the most you can1613 } else if ( a === null || b === null || typeof a === "undefined" ||1614 typeof b === "undefined" ||1615 QUnit.objectType( a ) !== QUnit.objectType( b ) ) {1616 1617 // don't lose time with error prone cases1618 return false;1619 } else {1620 return bindCallbacks( a, callbacks, [ b, a ] );1621 }1622 1623 // apply transition with (1..n) arguments1624 }( args[ 0 ], args[ 1 ] ) ) &&1625 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );1626 };1627 1628 return innerEquiv;1629 }());1630 1631 // Based on jsDump by Ariel Flesler1632 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html1633 QUnit.dump = (function() {1634 function quote( str ) {1635 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";1636 }1637 function literal( o ) {1638 return o + "";1639 }1640 function join( pre, arr, post ) {1641 var s = dump.separator(),1642 base = dump.indent(),1643 inner = dump.indent( 1 );1644 if ( arr.join ) {1645 arr = arr.join( "," + s + inner );1646 }1647 if ( !arr ) {1648 return pre + post;1649 }1650 return [ pre, inner + arr, base + post ].join( s );1651 }1652 function array( arr, stack ) {1653 var i = arr.length,1654 ret = new Array( i );1655 1656 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {1657 return "[object Array]";1658 }1659 1660 this.up();1661 while ( i-- ) {1662 ret[ i ] = this.parse( arr[ i ], undefined, stack );1663 }1664 this.down();1665 return join( "[", ret, "]" );1666 }1667 1668 var reName = /^function (\w+)/,1669 dump = {1670 1671 // objType is used mostly internally, you can fix a (custom) type in advance1672 parse: function( obj, objType, stack ) {1673 stack = stack || [];1674 var res, parser, parserType,1675 inStack = inArray( obj, stack );1676 1677 if ( inStack !== -1 ) {1678 return "recursion(" + ( inStack - stack.length ) + ")";1679 }1680 1681 objType = objType || this.typeOf( obj );1682 parser = this.parsers[ objType ];1683 parserType = typeof parser;1684 1685 if ( parserType === "function" ) {1686 stack.push( obj );1687 res = parser.call( this, obj, stack );1688 stack.pop();1689 return res;1690 }1691 return ( parserType === "string" ) ? parser : this.parsers.error;1692 },1693 typeOf: function( obj ) {1694 var type;1695 if ( obj === null ) {1696 type = "null";1697 } else if ( typeof obj === "undefined" ) {1698 type = "undefined";1699 } else if ( QUnit.is( "regexp", obj ) ) {1700 type = "regexp";1701 } else if ( QUnit.is( "date", obj ) ) {1702 type = "date";1703 } else if ( QUnit.is( "function", obj ) ) {1704 type = "function";1705 } else if ( obj.setInterval !== undefined &&1706 obj.document !== undefined &&1707 obj.nodeType === undefined ) {1708 type = "window";1709 } else if ( obj.nodeType === 9 ) {1710 type = "document";1711 } else if ( obj.nodeType ) {1712 type = "node";1713 } else if (1714 1715 // native arrays1716 toString.call( obj ) === "[object Array]" ||1717 1718 // NodeList objects1719 ( typeof obj.length === "number" && obj.item !== undefined &&1720 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&1721 obj[ 0 ] === undefined ) ) )1722 ) {1723 type = "array";1724 } else if ( obj.constructor === Error.prototype.constructor ) {1725 type = "error";1726 } else {1727 type = typeof obj;1728 }1729 return type;1730 },1731 separator: function() {1732 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " ";1733 },1734 // extra can be a number, shortcut for increasing-calling-decreasing1735 indent: function( extra ) {1736 if ( !this.multiline ) {1737 return "";1738 }1739 var chr = this.indentChar;1740 if ( this.HTML ) {1741 chr = chr.replace( /\t/g, " " ).replace( / /g, " " );1742 }1743 return new Array( this.depth + ( extra || 0 ) ).join( chr );1744 },1745 up: function( a ) {1746 this.depth += a || 1;1747 },1748 down: function( a ) {1749 this.depth -= a || 1;1750 },1751 setParser: function( name, parser ) {1752 this.parsers[ name ] = parser;1753 },1754 // The next 3 are exposed so you can use them1755 quote: quote,1756 literal: literal,1757 join: join,1758 //1759 depth: 1,1760 maxDepth: QUnit.config.maxDepth,1761 1762 // This is the list of parsers, to modify them, use dump.setParser1763 parsers: {1764 window: "[Window]",1765 document: "[Document]",1766 error: function( error ) {1767 return "Error(\"" + error.message + "\")";1768 },1769 unknown: "[Unknown]",1770 "null": "null",1771 "undefined": "undefined",1772 "function": function( fn ) {1773 var ret = "function",1774 1775 // functions never have name in IE1776 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];1777 1778 if ( name ) {1779 ret += " " + name;1780 }1781 ret += "( ";1782 1783 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );1784 return join( ret, dump.parse( fn, "functionCode" ), "}" );1785 },1786 array: array,1787 nodelist: array,1788 "arguments": array,1789 object: function( map, stack ) {1790 var keys, key, val, i, nonEnumerableProperties,1791 ret = [];1792 1793 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {1794 return "[object Object]";1795 }1796 1797 dump.up();1798 keys = [];1799 for ( key in map ) {1800 keys.push( key );1801 }1802 1803 // Some properties are not always enumerable on Error objects.1804 nonEnumerableProperties = [ "message", "name" ];1805 for ( i in nonEnumerableProperties ) {1806 key = nonEnumerableProperties[ i ];1807 if ( key in map && inArray( key, keys ) < 0 ) {1808 keys.push( key );1809 }1810 }1811 keys.sort();1812 for ( i = 0; i < keys.length; i++ ) {1813 key = keys[ i ];1814 val = map[ key ];1815 ret.push( dump.parse( key, "key" ) + ": " +1816 dump.parse( val, undefined, stack ) );1817 }1818 dump.down();1819 return join( "{", ret, "}" );1820 },1821 node: function( node ) {1822 var len, i, val,1823 open = dump.HTML ? "<" : "<",1824 close = dump.HTML ? ">" : ">",1825 tag = node.nodeName.toLowerCase(),1826 ret = open + tag,1827 attrs = node.attributes;1828 1829 if ( attrs ) {1830 for ( i = 0, len = attrs.length; i < len; i++ ) {1831 val = attrs[ i ].nodeValue;1832 1833 // IE6 includes all attributes in .attributes, even ones not explicitly1834 // set. Those have values like undefined, null, 0, false, "" or1835 // "inherit".1836 if ( val && val !== "inherit" ) {1837 ret += " " + attrs[ i ].nodeName + "=" +1838 dump.parse( val, "attribute" );1839 }1840 }1841 }1842 ret += close;1843 1844 // Show content of TextNode or CDATASection1845 if ( node.nodeType === 3 || node.nodeType === 4 ) {1846 ret += node.nodeValue;1847 }1848 1849 return ret + open + "/" + tag + close;1850 },1851 1852 // function calls it internally, it's the arguments part of the function1853 functionArgs: function( fn ) {1854 var args,1855 l = fn.length;1856 1857 if ( !l ) {1858 return "";1859 }1860 1861 args = new Array( l );1862 while ( l-- ) {1863 1864 // 97 is 'a'1865 args[ l ] = String.fromCharCode( 97 + l );1866 }1867 return " " + args.join( ", " ) + " ";1868 },1869 // object calls it internally, the key part of an item in a map1870 key: quote,1871 // function calls it internally, it's the content of the function1872 functionCode: "[code]",1873 // node calls it internally, it's an html attribute value1874 attribute: quote,1875 string: quote,1876 date: quote,1877 regexp: literal,1878 number: literal,1879 "boolean": literal1880 },1881 // if true, entities are escaped ( <, >, \t, space and \n )1882 HTML: false,1883 // indentation unit1884 indentChar: " ",1885 // if true, items in a collection, are separated by a \n, else just a space.1886 multiline: true1887 };1888 1889 return dump;1890 }());1891 1892 // back compat1893 QUnit.jsDump = QUnit.dump;1894 1895 // For browser, export only select globals1896 if ( typeof window !== "undefined" ) {1897 1898 // Deprecated1899 // Extend assert methods to QUnit and Global scope through Backwards compatibility1900 (function() {1901 var i,1902 assertions = Assert.prototype;1903 1904 function applyCurrent( current ) {1905 return function() {1906 var assert = new Assert( QUnit.config.current );1907 current.apply( assert, arguments );1908 };1909 }1910 1911 for ( i in assertions ) {1912 QUnit[ i ] = applyCurrent( assertions[ i ] );1913 }1914 })();1915 1916 (function() {1917 var i, l,1918 keys = [1919 "test",1920 "module",1921 "expect",1922 "asyncTest",1923 "start",1924 "stop",1925 "ok",1926 "notOk",1927 "equal",1928 "notEqual",1929 "propEqual",1930 "notPropEqual",1931 "deepEqual",1932 "notDeepEqual",1933 "strictEqual",1934 "notStrictEqual",1935 "throws"1936 ];1937 1938 for ( i = 0, l = keys.length; i < l; i++ ) {1939 window[ keys[ i ] ] = QUnit[ keys[ i ] ];1940 }1941 })();1942 1943 window.QUnit = QUnit;1944 }1945 1946 // For nodejs1947 if ( typeof module !== "undefined" && module && module.exports ) {1948 module.exports = QUnit;1949 1950 // For consistency with CommonJS environments' exports1951 module.exports.QUnit = QUnit;1952 }1953 1954 // For CommonJS with exports, but without module.exports, like Rhino1955 if ( typeof exports !== "undefined" && exports ) {1956 exports.QUnit = QUnit;1957 }1958 1959 if ( typeof define === "function" && define.amd ) {1960 define( function() {1961 return QUnit;1962 } );1963 QUnit.config.autostart = false;1964 }1965 1966 // Get a reference to the global object, like window in browsers1967 }( (function() {1968 return this;1969 })() ));1970 1971 /*istanbul ignore next */1972 // jscs:disable maximumLineLength1973 /*1974 * This file is a modified version of google-diff-match-patch's JavaScript implementation1975 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),1976 * modifications are licensed as more fully set forth in LICENSE.txt.1977 *1978 * The original source of google-diff-match-patch is attributable and licensed as follows:1979 *1980 * Copyright 2006 Google Inc.1981 * http://code.google.com/p/google-diff-match-patch/1982 *1983 * Licensed under the Apache License, Version 2.0 (the "License");1984 * you may not use this file except in compliance with the License.1985 * You may obtain a copy of the License at1986 *1987 * http://www.apache.org/licenses/LICENSE-2.01988 *1989 * Unless required by applicable law or agreed to in writing, software1990 * distributed under the License is distributed on an "AS IS" BASIS,1991 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.1992 * See the License for the specific language governing permissions and1993 * limitations under the License.1994 *1995 * More Info:1996 * https://code.google.com/p/google-diff-match-patch/1997 *1998 * Usage: QUnit.diff(expected, actual)1999 *2000 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"2001 */2002 QUnit.diff = (function() {2003 2004 function DiffMatchPatch() {2005 2006 // Defaults.2007 // Redefine these in your program to override the defaults.2008 2009 // Number of seconds to map a diff before giving up (0 for infinity).2010 this.DiffTimeout = 1.0;2011 // Cost of an empty edit operation in terms of edit characters.2012 this.DiffEditCost = 4;2013 }2014 2015 // DIFF FUNCTIONS2016 2017 /**2018 * The data structure representing a diff is an array of tuples:2019 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]2020 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'2021 */2022 var DIFF_DELETE = -1,2023 DIFF_INSERT = 1,2024 DIFF_EQUAL = 0;2025 2026 /**2027 * Find the differences between two texts. Simplifies the problem by stripping2028 * any common prefix or suffix off the texts before diffing.2029 * @param {string} text1 Old string to be diffed.2030 * @param {string} text2 New string to be diffed.2031 * @param {boolean=} optChecklines Optional speedup flag. If present and false,2032 * then don't run a line-level diff first to identify the changed areas.2033 * Defaults to true, which does a faster, slightly less optimal diff.2034 * @param {number} optDeadline Optional time when the diff should be complete2035 * by. Used internally for recursive calls. Users should set DiffTimeout2036 * instead.2037 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.2038 */2039 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {2040 var deadline, checklines, commonlength,2041 commonprefix, commonsuffix, diffs;2042 // Set a deadline by which time the diff must be complete.2043 if ( typeof optDeadline === "undefined" ) {2044 if ( this.DiffTimeout <= 0 ) {2045 optDeadline = Number.MAX_VALUE;2046 } else {2047 optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;2048 }2049 }2050 deadline = optDeadline;2051 2052 // Check for null inputs.2053 if ( text1 === null || text2 === null ) {2054 throw new Error( "Null input. (DiffMain)" );2055 }2056 2057 // Check for equality (speedup).2058 if ( text1 === text2 ) {2059 if ( text1 ) {2060 return [2061 [ DIFF_EQUAL, text1 ]2062 ];2063 }2064 return [];2065 }2066 2067 if ( typeof optChecklines === "undefined" ) {2068 optChecklines = true;2069 }2070 2071 checklines = optChecklines;2072 2073 // Trim off common prefix (speedup).2074 commonlength = this.diffCommonPrefix( text1, text2 );2075 commonprefix = text1.substring( 0, commonlength );2076 text1 = text1.substring( commonlength );2077 text2 = text2.substring( commonlength );2078 2079 // Trim off common suffix (speedup).2080 /////////2081 commonlength = this.diffCommonSuffix( text1, text2 );2082 commonsuffix = text1.substring( text1.length - commonlength );2083 text1 = text1.substring( 0, text1.length - commonlength );2084 text2 = text2.substring( 0, text2.length - commonlength );2085 2086 // Compute the diff on the middle block.2087 diffs = this.diffCompute( text1, text2, checklines, deadline );2088 2089 // Restore the prefix and suffix.2090 if ( commonprefix ) {2091 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );2092 }2093 if ( commonsuffix ) {2094 diffs.push( [ DIFF_EQUAL, commonsuffix ] );2095 }2096 this.diffCleanupMerge( diffs );2097 return diffs;2098 };2099 2100 /**2101 * Reduce the number of edits by eliminating operationally trivial equalities.2102 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.2103 */2104 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {2105 var changes, equalities, equalitiesLength, lastequality,2106 pointer, preIns, preDel, postIns, postDel;2107 changes = false;2108 equalities = []; // Stack of indices where equalities are found.2109 equalitiesLength = 0; // Keeping our own length var is faster in JS.2110 /** @type {?string} */2111 lastequality = null;2112 // Always equal to diffs[equalities[equalitiesLength - 1]][1]2113 pointer = 0; // Index of current position.2114 // Is there an insertion operation before the last equality.2115 preIns = false;2116 // Is there a deletion operation before the last equality.2117 preDel = false;2118 // Is there an insertion operation after the last equality.2119 postIns = false;2120 // Is there a deletion operation after the last equality.2121 postDel = false;2122 while ( pointer < diffs.length ) {2123 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.2124 if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {2125 // Candidate found.2126 equalities[ equalitiesLength++ ] = pointer;2127 preIns = postIns;2128 preDel = postDel;2129 lastequality = diffs[ pointer ][ 1 ];2130 } else {2131 // Not a candidate, and can never become one.2132 equalitiesLength = 0;2133 lastequality = null;2134 }2135 postIns = postDel = false;2136 } else { // An insertion or deletion.2137 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {2138 postDel = true;2139 } else {2140 postIns = true;2141 }2142 /*2143 * Five types to be split:2144 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>2145 * <ins>A</ins>X<ins>C</ins><del>D</del>2146 * <ins>A</ins><del>B</del>X<ins>C</ins>2147 * <ins>A</del>X<ins>C</ins><del>D</del>2148 * <ins>A</ins><del>B</del>X<del>C</del>2149 */2150 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||2151 ( ( lastequality.length < this.DiffEditCost / 2 ) &&2152 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {2153 // Duplicate record.2154 diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );2155 // Change second copy to insert.2156 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;2157 equalitiesLength--; // Throw away the equality we just deleted;2158 lastequality = null;2159 if (preIns && preDel) {2160 // No changes made which could affect previous entry, keep going.2161 postIns = postDel = true;2162 equalitiesLength = 0;2163 } else {2164 equalitiesLength--; // Throw away the previous equality.2165 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;2166 postIns = postDel = false;2167 }2168 changes = true;2169 }2170 }2171 pointer++;2172 }2173 2174 if ( changes ) {2175 this.diffCleanupMerge( diffs );2176 }2177 };2178 2179 /**2180 * Convert a diff array into a pretty HTML report.2181 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.2182 * @param {integer} string to be beautified.2183 * @return {string} HTML representation.2184 */2185 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {2186 var op, data, x, html = [];2187 for ( x = 0; x < diffs.length; x++ ) {2188 op = diffs[x][0]; // Operation (insert, delete, equal)2189 data = diffs[x][1]; // Text of change.2190 switch ( op ) {2191 case DIFF_INSERT:2192 html[x] = "<ins>" + data + "</ins>";2193 break;2194 case DIFF_DELETE:2195 html[x] = "<del>" + data + "</del>";2196 break;2197 case DIFF_EQUAL:2198 html[x] = "<span>" + data + "</span>";2199 break;2200 }2201 }2202 return html.join("");2203 };2204 2205 /**2206 * Determine the common prefix of two strings.2207 * @param {string} text1 First string.2208 * @param {string} text2 Second string.2209 * @return {number} The number of characters common to the start of each2210 * string.2211 */2212 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {2213 var pointermid, pointermax, pointermin, pointerstart;2214 // Quick check for common null cases.2215 if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {2216 return 0;2217 }2218 // Binary search.2219 // Performance analysis: http://neil.fraser.name/news/2007/10/09/2220 pointermin = 0;2221 pointermax = Math.min( text1.length, text2.length );2222 pointermid = pointermax;2223 pointerstart = 0;2224 while ( pointermin < pointermid ) {2225 if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {2226 pointermin = pointermid;2227 pointerstart = pointermin;2228 } else {2229 pointermax = pointermid;2230 }2231 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );2232 }2233 return pointermid;2234 };2235 2236 /**2237 * Determine the common suffix of two strings.2238 * @param {string} text1 First string.2239 * @param {string} text2 Second string.2240 * @return {number} The number of characters common to the end of each string.2241 */2242 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {2243 var pointermid, pointermax, pointermin, pointerend;2244 // Quick check for common null cases.2245 if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {2246 return 0;2247 }2248 // Binary search.2249 // Performance analysis: http://neil.fraser.name/news/2007/10/09/2250 pointermin = 0;2251 pointermax = Math.min(text1.length, text2.length);2252 pointermid = pointermax;2253 pointerend = 0;2254 while ( pointermin < pointermid ) {2255 if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===2256 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {2257 pointermin = pointermid;2258 pointerend = pointermin;2259 } else {2260 pointermax = pointermid;2261 }2262 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );2263 }2264 return pointermid;2265 };2266 2267 /**2268 * Find the differences between two texts. Assumes that the texts do not2269 * have any common prefix or suffix.2270 * @param {string} text1 Old string to be diffed.2271 * @param {string} text2 New string to be diffed.2272 * @param {boolean} checklines Speedup flag. If false, then don't run a2273 * line-level diff first to identify the changed areas.2274 * If true, then run a faster, slightly less optimal diff.2275 * @param {number} deadline Time when the diff should be complete by.2276 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.2277 * @private2278 */2279 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {2280 var diffs, longtext, shorttext, i, hm,2281 text1A, text2A, text1B, text2B,2282 midCommon, diffsA, diffsB;2283 2284 if ( !text1 ) {2285 // Just add some text (speedup).2286 return [2287 [ DIFF_INSERT, text2 ]2288 ];2289 }2290 2291 if (!text2) {2292 // Just delete some text (speedup).2293 return [2294 [ DIFF_DELETE, text1 ]2295 ];2296 }2297 2298 longtext = text1.length > text2.length ? text1 : text2;2299 shorttext = text1.length > text2.length ? text2 : text1;2300 i = longtext.indexOf( shorttext );2301 if ( i !== -1 ) {2302 // Shorter text is inside the longer text (speedup).2303 diffs = [2304 [ DIFF_INSERT, longtext.substring( 0, i ) ],2305 [ DIFF_EQUAL, shorttext ],2306 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]2307 ];2308 // Swap insertions for deletions if diff is reversed.2309 if ( text1.length > text2.length ) {2310 diffs[0][0] = diffs[2][0] = DIFF_DELETE;2311 }2312 return diffs;2313 }2314 2315 if ( shorttext.length === 1 ) {2316 // Single character string.2317 // After the previous speedup, the character can't be an equality.2318 return [2319 [ DIFF_DELETE, text1 ],2320 [ DIFF_INSERT, text2 ]2321 ];2322 }2323 2324 // Check to see if the problem can be split in two.2325 hm = this.diffHalfMatch(text1, text2);2326 if (hm) {2327 // A half-match was found, sort out the return data.2328 text1A = hm[0];2329 text1B = hm[1];2330 text2A = hm[2];2331 text2B = hm[3];2332 midCommon = hm[4];2333 // Send both pairs off for separate processing.2334 diffsA = this.DiffMain(text1A, text2A, checklines, deadline);2335 diffsB = this.DiffMain(text1B, text2B, checklines, deadline);2336 // Merge the results.2337 return diffsA.concat([2338 [ DIFF_EQUAL, midCommon ]2339 ], diffsB);2340 }2341 2342 if (checklines && text1.length > 100 && text2.length > 100) {2343 return this.diffLineMode(text1, text2, deadline);2344 }2345 2346 return this.diffBisect(text1, text2, deadline);2347 };2348 2349 /**2350 * Do the two texts share a substring which is at least half the length of the2351 * longer text?2352 * This speedup can produce non-minimal diffs.2353 * @param {string} text1 First string.2354 * @param {string} text2 Second string.2355 * @return {Array.<string>} Five element Array, containing the prefix of2356 * text1, the suffix of text1, the prefix of text2, the suffix of2357 * text2 and the common middle. Or null if there was no match.2358 * @private2359 */2360 DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {2361 var longtext, shorttext, dmp,2362 text1A, text2B, text2A, text1B, midCommon,2363 hm1, hm2, hm;2364 if (this.DiffTimeout <= 0) {2365 // Don't risk returning a non-optimal diff if we have unlimited time.2366 return null;2367 }2368 longtext = text1.length > text2.length ? text1 : text2;2369 shorttext = text1.length > text2.length ? text2 : text1;2370 if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {2371 return null; // Pointless.2372 }2373 dmp = this; // 'this' becomes 'window' in a closure.2374 2375 /**2376 * Does a substring of shorttext exist within longtext such that the substring2377 * is at least half the length of longtext?2378 * Closure, but does not reference any external variables.2379 * @param {string} longtext Longer string.2380 * @param {string} shorttext Shorter string.2381 * @param {number} i Start index of quarter length substring within longtext.2382 * @return {Array.<string>} Five element Array, containing the prefix of2383 * longtext, the suffix of longtext, the prefix of shorttext, the suffix2384 * of shorttext and the common middle. Or null if there was no match.2385 * @private2386 */2387 function diffHalfMatchI(longtext, shorttext, i) {2388 var seed, j, bestCommon, prefixLength, suffixLength,2389 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;2390 // Start with a 1/4 length substring at position i as a seed.2391 seed = longtext.substring(i, i + Math.floor(longtext.length / 4));2392 j = -1;2393 bestCommon = "";2394 while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {2395 prefixLength = dmp.diffCommonPrefix(longtext.substring(i),2396 shorttext.substring(j));2397 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),2398 shorttext.substring(0, j));2399 if (bestCommon.length < suffixLength + prefixLength) {2400 bestCommon = shorttext.substring(j - suffixLength, j) +2401 shorttext.substring(j, j + prefixLength);2402 bestLongtextA = longtext.substring(0, i - suffixLength);2403 bestLongtextB = longtext.substring(i + prefixLength);2404 bestShorttextA = shorttext.substring(0, j - suffixLength);2405 bestShorttextB = shorttext.substring(j + prefixLength);2406 }2407 }2408 if (bestCommon.length * 2 >= longtext.length) {2409 return [ bestLongtextA, bestLongtextB,2410 bestShorttextA, bestShorttextB, bestCommon2411 ];2412 } else {2413 return null;2414 }2415 }2416 2417 // First check if the second quarter is the seed for a half-match.2418 hm1 = diffHalfMatchI(longtext, shorttext,2419 Math.ceil(longtext.length / 4));2420 // Check again based on the third quarter.2421 hm2 = diffHalfMatchI(longtext, shorttext,2422 Math.ceil(longtext.length / 2));2423 if (!hm1 && !hm2) {2424 return null;2425 } else if (!hm2) {2426 hm = hm1;2427 } else if (!hm1) {2428 hm = hm2;2429 } else {2430 // Both matched. Select the longest.2431 hm = hm1[4].length > hm2[4].length ? hm1 : hm2;2432 }2433 2434 // A half-match was found, sort out the return data.2435 text1A, text1B, text2A, text2B;2436 if (text1.length > text2.length) {2437 text1A = hm[0];2438 text1B = hm[1];2439 text2A = hm[2];2440 text2B = hm[3];2441 } else {2442 text2A = hm[0];2443 text2B = hm[1];2444 text1A = hm[2];2445 text1B = hm[3];2446 }2447 midCommon = hm[4];2448 return [ text1A, text1B, text2A, text2B, midCommon ];2449 };2450 2451 /**2452 * Do a quick line-level diff on both strings, then rediff the parts for2453 * greater accuracy.2454 * This speedup can produce non-minimal diffs.2455 * @param {string} text1 Old string to be diffed.2456 * @param {string} text2 New string to be diffed.2457 * @param {number} deadline Time when the diff should be complete by.2458 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.2459 * @private2460 */2461 DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {2462 var a, diffs, linearray, pointer, countInsert,2463 countDelete, textInsert, textDelete, j;2464 // Scan the text on a line-by-line basis first.2465 a = this.diffLinesToChars(text1, text2);2466 text1 = a.chars1;2467 text2 = a.chars2;2468 linearray = a.lineArray;2469 2470 diffs = this.DiffMain(text1, text2, false, deadline);2471 2472 // Convert the diff back to original text.2473 this.diffCharsToLines(diffs, linearray);2474 // Eliminate freak matches (e.g. blank lines)2475 this.diffCleanupSemantic(diffs);2476 2477 // Rediff any replacement blocks, this time character-by-character.2478 // Add a dummy entry at the end.2479 diffs.push( [ DIFF_EQUAL, "" ] );2480 pointer = 0;2481 countDelete = 0;2482 countInsert = 0;2483 textDelete = "";2484 textInsert = "";2485 while (pointer < diffs.length) {2486 switch ( diffs[pointer][0] ) {2487 case DIFF_INSERT:2488 countInsert++;2489 textInsert += diffs[pointer][1];2490 break;2491 case DIFF_DELETE:2492 countDelete++;2493 textDelete += diffs[pointer][1];2494 break;2495 case DIFF_EQUAL:2496 // Upon reaching an equality, check for prior redundancies.2497 if (countDelete >= 1 && countInsert >= 1) {2498 // Delete the offending records and add the merged ones.2499 diffs.splice(pointer - countDelete - countInsert,2500 countDelete + countInsert);2501 pointer = pointer - countDelete - countInsert;2502 a = this.DiffMain(textDelete, textInsert, false, deadline);2503 for (j = a.length - 1; j >= 0; j--) {2504 diffs.splice( pointer, 0, a[j] );2505 }2506 pointer = pointer + a.length;2507 }2508 countInsert = 0;2509 countDelete = 0;2510 textDelete = "";2511 textInsert = "";2512 break;2513 }2514 pointer++;2515 }2516 diffs.pop(); // Remove the dummy entry at the end.2517 2518 return diffs;2519 };2520 2521 /**2522 * Find the 'middle snake' of a diff, split the problem in two2523 * and return the recursively constructed diff.2524 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.2525 * @param {string} text1 Old string to be diffed.2526 * @param {string} text2 New string to be diffed.2527 * @param {number} deadline Time at which to bail if not yet complete.2528 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.2529 * @private2530 */2531 DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {2532 var text1Length, text2Length, maxD, vOffset, vLength,2533 v1, v2, x, delta, front, k1start, k1end, k2start,2534 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;2535 // Cache the text lengths to prevent multiple calls.2536 text1Length = text1.length;2537 text2Length = text2.length;2538 maxD = Math.ceil((text1Length + text2Length) / 2);2539 vOffset = maxD;2540 vLength = 2 * maxD;2541 v1 = new Array(vLength);2542 v2 = new Array(vLength);2543 // Setting all elements to -1 is faster in Chrome & Firefox than mixing2544 // integers and undefined.2545 for (x = 0; x < vLength; x++) {2546 v1[x] = -1;2547 v2[x] = -1;2548 }2549 v1[vOffset + 1] = 0;2550 v2[vOffset + 1] = 0;2551 delta = text1Length - text2Length;2552 // If the total number of characters is odd, then the front path will collide2553 // with the reverse path.2554 front = (delta % 2 !== 0);2555 // Offsets for start and end of k loop.2556 // Prevents mapping of space beyond the grid.2557 k1start = 0;2558 k1end = 0;2559 k2start = 0;2560 k2end = 0;2561 for (d = 0; d < maxD; d++) {2562 // Bail out if deadline is reached.2563 if ((new Date()).getTime() > deadline) {2564 break;2565 }2566 2567 // Walk the front path one step.2568 for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {2569 k1Offset = vOffset + k1;2570 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {2571 x1 = v1[k1Offset + 1];2572 } else {2573 x1 = v1[k1Offset - 1] + 1;2574 }2575 y1 = x1 - k1;2576 while (x1 < text1Length && y1 < text2Length &&2577 text1.charAt(x1) === text2.charAt(y1)) {2578 x1++;2579 y1++;2580 }2581 v1[k1Offset] = x1;2582 if (x1 > text1Length) {2583 // Ran off the right of the graph.2584 k1end += 2;2585 } else if (y1 > text2Length) {2586 // Ran off the bottom of the graph.2587 k1start += 2;2588 } else if (front) {2589 k2Offset = vOffset + delta - k1;2590 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {2591 // Mirror x2 onto top-left coordinate system.2592 x2 = text1Length - v2[k2Offset];2593 if (x1 >= x2) {2594 // Overlap detected.2595 return this.diffBisectSplit(text1, text2, x1, y1, deadline);2596 }2597 }2598 }2599 }2600 2601 // Walk the reverse path one step.2602 for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {2603 k2Offset = vOffset + k2;2604 if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {2605 x2 = v2[k2Offset + 1];2606 } else {2607 x2 = v2[k2Offset - 1] + 1;2608 }2609 y2 = x2 - k2;2610 while (x2 < text1Length && y2 < text2Length &&2611 text1.charAt(text1Length - x2 - 1) ===2612 text2.charAt(text2Length - y2 - 1)) {2613 x2++;2614 y2++;2615 }2616 v2[k2Offset] = x2;2617 if (x2 > text1Length) {2618 // Ran off the left of the graph.2619 k2end += 2;2620 } else if (y2 > text2Length) {2621 // Ran off the top of the graph.2622 k2start += 2;2623 } else if (!front) {2624 k1Offset = vOffset + delta - k2;2625 if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {2626 x1 = v1[k1Offset];2627 y1 = vOffset + x1 - k1Offset;2628 // Mirror x2 onto top-left coordinate system.2629 x2 = text1Length - x2;2630 if (x1 >= x2) {2631 // Overlap detected.2632 return this.diffBisectSplit(text1, text2, x1, y1, deadline);2633 }2634 }2635 }2636 }2637 }2638 // Diff took too long and hit the deadline or2639 // number of diffs equals number of characters, no commonality at all.2640 return [2641 [ DIFF_DELETE, text1 ],2642 [ DIFF_INSERT, text2 ]2643 ];2644 };2645 2646 /**2647 * Given the location of the 'middle snake', split the diff in two parts2648 * and recurse.2649 * @param {string} text1 Old string to be diffed.2650 * @param {string} text2 New string to be diffed.2651 * @param {number} x Index of split point in text1.2652 * @param {number} y Index of split point in text2.2653 * @param {number} deadline Time at which to bail if not yet complete.2654 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.2655 * @private2656 */2657 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {2658 var text1a, text1b, text2a, text2b, diffs, diffsb;2659 text1a = text1.substring(0, x);2660 text2a = text2.substring(0, y);2661 text1b = text1.substring(x);2662 text2b = text2.substring(y);2663 2664 // Compute both diffs serially.2665 diffs = this.DiffMain(text1a, text2a, false, deadline);2666 diffsb = this.DiffMain(text1b, text2b, false, deadline);2667 2668 return diffs.concat(diffsb);2669 };2670 2671 /**2672 * Reduce the number of edits by eliminating semantically trivial equalities.2673 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.2674 */2675 DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {2676 var changes, equalities, equalitiesLength, lastequality,2677 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,2678 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;2679 changes = false;2680 equalities = []; // Stack of indices where equalities are found.2681 equalitiesLength = 0; // Keeping our own length var is faster in JS.2682 /** @type {?string} */2683 lastequality = null;2684 // Always equal to diffs[equalities[equalitiesLength - 1]][1]2685 pointer = 0; // Index of current position.2686 // Number of characters that changed prior to the equality.2687 lengthInsertions1 = 0;2688 lengthDeletions1 = 0;2689 // Number of characters that changed after the equality.2690 lengthInsertions2 = 0;2691 lengthDeletions2 = 0;2692 while (pointer < diffs.length) {2693 if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.2694 equalities[equalitiesLength++] = pointer;2695 lengthInsertions1 = lengthInsertions2;2696 lengthDeletions1 = lengthDeletions2;2697 lengthInsertions2 = 0;2698 lengthDeletions2 = 0;2699 lastequality = diffs[pointer][1];2700 } else { // An insertion or deletion.2701 if (diffs[pointer][0] === DIFF_INSERT) {2702 lengthInsertions2 += diffs[pointer][1].length;2703 } else {2704 lengthDeletions2 += diffs[pointer][1].length;2705 }2706 // Eliminate an equality that is smaller or equal to the edits on both2707 // sides of it.2708 if (lastequality && (lastequality.length <=2709 Math.max(lengthInsertions1, lengthDeletions1)) &&2710 (lastequality.length <= Math.max(lengthInsertions2,2711 lengthDeletions2))) {2712 // Duplicate record.2713 diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );2714 // Change second copy to insert.2715 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;2716 // Throw away the equality we just deleted.2717 equalitiesLength--;2718 // Throw away the previous equality (it needs to be reevaluated).2719 equalitiesLength--;2720 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;2721 lengthInsertions1 = 0; // Reset the counters.2722 lengthDeletions1 = 0;2723 lengthInsertions2 = 0;2724 lengthDeletions2 = 0;2725 lastequality = null;2726 changes = true;2727 }2728 }2729 pointer++;2730 }2731 2732 // Normalize the diff.2733 if (changes) {2734 this.diffCleanupMerge(diffs);2735 }2736 2737 // Find any overlaps between deletions and insertions.2738 // e.g: <del>abcxxx</del><ins>xxxdef</ins>2739 // -> <del>abc</del>xxx<ins>def</ins>2740 // e.g: <del>xxxabc</del><ins>defxxx</ins>2741 // -> <ins>def</ins>xxx<del>abc</del>2742 // Only extract an overlap if it is as big as the edit ahead or behind it.2743 pointer = 1;2744 while (pointer < diffs.length) {2745 if (diffs[pointer - 1][0] === DIFF_DELETE &&2746 diffs[pointer][0] === DIFF_INSERT) {2747 deletion = diffs[pointer - 1][1];2748 insertion = diffs[pointer][1];2749 overlapLength1 = this.diffCommonOverlap(deletion, insertion);2750 overlapLength2 = this.diffCommonOverlap(insertion, deletion);2751 if (overlapLength1 >= overlapLength2) {2752 if (overlapLength1 >= deletion.length / 2 ||2753 overlapLength1 >= insertion.length / 2) {2754 // Overlap found. Insert an equality and trim the surrounding edits.2755 diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );2756 diffs[pointer - 1][1] =2757 deletion.substring(0, deletion.length - overlapLength1);2758 diffs[pointer + 1][1] = insertion.substring(overlapLength1);2759 pointer++;2760 }2761 } else {2762 if (overlapLength2 >= deletion.length / 2 ||2763 overlapLength2 >= insertion.length / 2) {2764 // Reverse overlap found.2765 // Insert an equality and swap and trim the surrounding edits.2766 diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );2767 diffs[pointer - 1][0] = DIFF_INSERT;2768 diffs[pointer - 1][1] =2769 insertion.substring(0, insertion.length - overlapLength2);2770 diffs[pointer + 1][0] = DIFF_DELETE;2771 diffs[pointer + 1][1] =2772 deletion.substring(overlapLength2);2773 pointer++;2774 }2775 }2776 pointer++;2777 }2778 pointer++;2779 }2780 };2781 2782 /**2783 * Determine if the suffix of one string is the prefix of another.2784 * @param {string} text1 First string.2785 * @param {string} text2 Second string.2786 * @return {number} The number of characters common to the end of the first2787 * string and the start of the second string.2788 * @private2789 */2790 DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {2791 var text1Length, text2Length, textLength,2792 best, length, pattern, found;2793 // Cache the text lengths to prevent multiple calls.2794 text1Length = text1.length;2795 text2Length = text2.length;2796 // Eliminate the null case.2797 if (text1Length === 0 || text2Length === 0) {2798 return 0;2799 }2800 // Truncate the longer string.2801 if (text1Length > text2Length) {2802 text1 = text1.substring(text1Length - text2Length);2803 } else if (text1Length < text2Length) {2804 text2 = text2.substring(0, text1Length);2805 }2806 textLength = Math.min(text1Length, text2Length);2807 // Quick check for the worst case.2808 if (text1 === text2) {2809 return textLength;2810 }2811 2812 // Start by looking for a single character match2813 // and increase length until no match is found.2814 // Performance analysis: http://neil.fraser.name/news/2010/11/04/2815 best = 0;2816 length = 1;2817 while (true) {2818 pattern = text1.substring(textLength - length);2819 found = text2.indexOf(pattern);2820 if (found === -1) {2821 return best;2822 }2823 length += found;2824 if (found === 0 || text1.substring(textLength - length) ===2825 text2.substring(0, length)) {2826 best = length;2827 length++;2828 }2829 }2830 };2831 2832 /**2833 * Split two texts into an array of strings. Reduce the texts to a string of2834 * hashes where each Unicode character represents one line.2835 * @param {string} text1 First string.2836 * @param {string} text2 Second string.2837 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}2838 * An object containing the encoded text1, the encoded text2 and2839 * the array of unique strings.2840 * The zeroth element of the array of unique strings is intentionally blank.2841 * @private2842 */2843 DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {2844 var lineArray, lineHash, chars1, chars2;2845 lineArray = []; // e.g. lineArray[4] === 'Hello\n'2846 lineHash = {}; // e.g. lineHash['Hello\n'] === 42847 2848 // '\x00' is a valid character, but various debuggers don't like it.2849 // So we'll insert a junk entry to avoid generating a null character.2850 lineArray[0] = "";2851 2852 /**2853 * Split a text into an array of strings. Reduce the texts to a string of2854 * hashes where each Unicode character represents one line.2855 * Modifies linearray and linehash through being a closure.2856 * @param {string} text String to encode.2857 * @return {string} Encoded string.2858 * @private2859 */2860 function diffLinesToCharsMunge(text) {2861 var chars, lineStart, lineEnd, lineArrayLength, line;2862 chars = "";2863 // Walk the text, pulling out a substring for each line.2864 // text.split('\n') would would temporarily double our memory footprint.2865 // Modifying text would create many large strings to garbage collect.2866 lineStart = 0;2867 lineEnd = -1;2868 // Keeping our own length variable is faster than looking it up.2869 lineArrayLength = lineArray.length;2870 while (lineEnd < text.length - 1) {2871 lineEnd = text.indexOf("\n", lineStart);2872 if (lineEnd === -1) {2873 lineEnd = text.length - 1;2874 }2875 line = text.substring(lineStart, lineEnd + 1);2876 lineStart = lineEnd + 1;2877 2878 if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :2879 (lineHash[line] !== undefined)) {2880 chars += String.fromCharCode( lineHash[ line ] );2881 } else {2882 chars += String.fromCharCode(lineArrayLength);2883 lineHash[line] = lineArrayLength;2884 lineArray[lineArrayLength++] = line;2885 }2886 }2887 return chars;2888 }2889 2890 chars1 = diffLinesToCharsMunge(text1);2891 chars2 = diffLinesToCharsMunge(text2);2892 return {2893 chars1: chars1,2894 chars2: chars2,2895 lineArray: lineArray2896 };2897 };2898 2899 /**2900 * Rehydrate the text in a diff from a string of line hashes to real lines of2901 * text.2902 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.2903 * @param {!Array.<string>} lineArray Array of unique strings.2904 * @private2905 */2906 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {2907 var x, chars, text, y;2908 for ( x = 0; x < diffs.length; x++ ) {2909 chars = diffs[x][1];2910 text = [];2911 for ( y = 0; y < chars.length; y++ ) {2912 text[y] = lineArray[chars.charCodeAt(y)];2913 }2914 diffs[x][1] = text.join("");2915 }2916 };2917 2918 /**2919 * Reorder and merge like edit sections. Merge equalities.2920 * Any edit section can move as long as it doesn't cross an equality.2921 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.2922 */2923 DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {2924 var pointer, countDelete, countInsert, textInsert, textDelete,2925 commonlength, changes;2926 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.2927 pointer = 0;2928 countDelete = 0;2929 countInsert = 0;2930 textDelete = "";2931 textInsert = "";2932 commonlength;2933 while (pointer < diffs.length) {2934 switch ( diffs[ pointer ][ 0 ] ) {2935 case DIFF_INSERT:2936 countInsert++;2937 textInsert += diffs[pointer][1];2938 pointer++;2939 break;2940 case DIFF_DELETE:2941 countDelete++;2942 textDelete += diffs[pointer][1];2943 pointer++;2944 break;2945 case DIFF_EQUAL:2946 // Upon reaching an equality, check for prior redundancies.2947 if (countDelete + countInsert > 1) {2948 if (countDelete !== 0 && countInsert !== 0) {2949 // Factor out any common prefixies.2950 commonlength = this.diffCommonPrefix(textInsert, textDelete);2951 if (commonlength !== 0) {2952 if ((pointer - countDelete - countInsert) > 0 &&2953 diffs[pointer - countDelete - countInsert - 1][0] ===2954 DIFF_EQUAL) {2955 diffs[pointer - countDelete - countInsert - 1][1] +=2956 textInsert.substring(0, commonlength);2957 } else {2958 diffs.splice( 0, 0, [ DIFF_EQUAL,2959 textInsert.substring( 0, commonlength )2960 ] );2961 pointer++;2962 }2963 textInsert = textInsert.substring(commonlength);2964 textDelete = textDelete.substring(commonlength);2965 }2966 // Factor out any common suffixies.2967 commonlength = this.diffCommonSuffix(textInsert, textDelete);2968 if (commonlength !== 0) {2969 diffs[pointer][1] = textInsert.substring(textInsert.length -2970 commonlength) + diffs[pointer][1];2971 textInsert = textInsert.substring(0, textInsert.length -2972 commonlength);2973 textDelete = textDelete.substring(0, textDelete.length -2974 commonlength);2975 }2976 }2977 // Delete the offending records and add the merged ones.2978 if (countDelete === 0) {2979 diffs.splice( pointer - countInsert,2980 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );2981 } else if (countInsert === 0) {2982 diffs.splice( pointer - countDelete,2983 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );2984 } else {2985 diffs.splice( pointer - countDelete - countInsert,2986 countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );2987 }2988 pointer = pointer - countDelete - countInsert +2989 (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;2990 } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {2991 // Merge this equality with the previous one.2992 diffs[pointer - 1][1] += diffs[pointer][1];2993 diffs.splice(pointer, 1);2994 } else {2995 pointer++;2996 }2997 countInsert = 0;2998 countDelete = 0;2999 textDelete = "";3000 textInsert = "";3001 break;3002 }3003 }3004 if (diffs[diffs.length - 1][1] === "") {3005 diffs.pop(); // Remove the dummy entry at the end.3006 }3007 3008 // Second pass: look for single edits surrounded on both sides by equalities3009 // which can be shifted sideways to eliminate an equality.3010 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC3011 changes = false;3012 pointer = 1;3013 // Intentionally ignore the first and last element (don't need checking).3014 while (pointer < diffs.length - 1) {3015 if (diffs[pointer - 1][0] === DIFF_EQUAL &&3016 diffs[pointer + 1][0] === DIFF_EQUAL) {3017 // This is a single edit surrounded by equalities.3018 if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -3019 diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {3020 // Shift the edit over the previous equality.3021 diffs[pointer][1] = diffs[pointer - 1][1] +3022 diffs[pointer][1].substring(0, diffs[pointer][1].length -3023 diffs[pointer - 1][1].length);3024 diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];3025 diffs.splice(pointer - 1, 1);3026 changes = true;3027 } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===3028 diffs[ pointer + 1 ][ 1 ] ) {3029 // Shift the edit over the next equality.3030 diffs[pointer - 1][1] += diffs[pointer + 1][1];3031 diffs[pointer][1] =3032 diffs[pointer][1].substring(diffs[pointer + 1][1].length) +3033 diffs[pointer + 1][1];3034 diffs.splice(pointer + 1, 1);3035 changes = true;3036 }3037 }3038 pointer++;3039 }3040 // If shifts were made, the diff needs reordering and another shift sweep.3041 if (changes) {3042 this.diffCleanupMerge(diffs);3043 }3044 };3045 3046 return function(o, n) {3047 var diff, output, text;3048 diff = new DiffMatchPatch();3049 output = diff.DiffMain(o, n);3050 //console.log(output);3051 diff.diffCleanupEfficiency(output);3052 text = diff.diffPrettyHtml(output);3053 3054 return text;3055 };3056 }());3057 // jscs:enable3058 3059 (function() {3060 3061 // Deprecated QUnit.init - Ref #5303062 // Re-initialize the configuration options3063 QUnit.init = function() {3064 var tests, banner, result, qunit,3065 config = QUnit.config;3066 3067 config.stats = { all: 0, bad: 0 };3068 config.moduleStats = { all: 0, bad: 0 };3069 config.started = 0;3070 config.updateRate = 1000;3071 config.blocking = false;3072 config.autostart = true;3073 config.autorun = false;3074 config.filter = "";3075 config.queue = [];3076 3077 // Return on non-browser environments3078 // This is necessary to not break on node tests3079 if ( typeof window === "undefined" ) {3080 return;3081 }3082 3083 qunit = id( "qunit" );3084 if ( qunit ) {3085 qunit.innerHTML =3086 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +3087 "<h2 id='qunit-banner'></h2>" +3088 "<div id='qunit-testrunner-toolbar'></div>" +3089 "<h2 id='qunit-userAgent'></h2>" +3090 "<ol id='qunit-tests'></ol>";3091 }3092 3093 tests = id( "qunit-tests" );3094 banner = id( "qunit-banner" );3095 result = id( "qunit-testresult" );3096 3097 if ( tests ) {3098 tests.innerHTML = "";3099 }3100 3101 if ( banner ) {3102 banner.className = "";3103 }3104 3105 if ( result ) {3106 result.parentNode.removeChild( result );3107 }3108 3109 if ( tests ) {3110 result = document.createElement( "p" );3111 result.id = "qunit-testresult";3112 result.className = "result";3113 tests.parentNode.insertBefore( result, tests );3114 result.innerHTML = "Running...<br /> ";3115 }3116 };3117 3118 // Don't load the HTML Reporter on non-Browser environments3119 if ( typeof window === "undefined" ) {3120 return;3121 }3122 3123 var config = QUnit.config,3124 hasOwn = Object.prototype.hasOwnProperty,3125 defined = {3126 document: window.document !== undefined,3127 sessionStorage: (function() {3128 var x = "qunit-test-string";3129 try {3130 sessionStorage.setItem( x, x );3131 sessionStorage.removeItem( x );3132 return true;3133 } catch ( e ) {3134 return false;3135 }3136 }())3137 },3138 modulesList = [];3139 3140 /**3141 * Escape text for attribute or text content.3142 */3143 function escapeText( s ) {3144 if ( !s ) {3145 return "";3146 }3147 s = s + "";3148 3149 // Both single quotes and double quotes (for attributes)3150 return s.replace( /['"<>&]/g, function( s ) {3151 switch ( s ) {3152 case "'":3153 return "'";3154 case "\"":3155 return """;3156 case "<":3157 return "<";3158 case ">":3159 return ">";3160 case "&":3161 return "&";3162 }3163 });3164 }3165 3166 /**3167 * @param {HTMLElement} elem3168 * @param {string} type3169 * @param {Function} fn3170 */3171 function addEvent( elem, type, fn ) {3172 if ( elem.addEventListener ) {3173 3174 // Standards-based browsers3175 elem.addEventListener( type, fn, false );3176 } else if ( elem.attachEvent ) {3177 3178 // support: IE <93179 elem.attachEvent( "on" + type, function() {3180 var event = window.event;3181 if ( !event.target ) {3182 event.target = event.srcElement || document;3183 }3184 3185 fn.call( elem, event );3186 });3187 }3188 }3189 3190 /**3191 * @param {Array|NodeList} elems3192 * @param {string} type3193 * @param {Function} fn3194 */3195 function addEvents( elems, type, fn ) {3196 var i = elems.length;3197 while ( i-- ) {3198 addEvent( elems[ i ], type, fn );3199 }3200 }3201 3202 function hasClass( elem, name ) {3203 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;3204 }3205 3206 function addClass( elem, name ) {3207 if ( !hasClass( elem, name ) ) {3208 elem.className += ( elem.className ? " " : "" ) + name;3209 }3210 }3211 3212 function toggleClass( elem, name ) {3213 if ( hasClass( elem, name ) ) {3214 removeClass( elem, name );3215 } else {3216 addClass( elem, name );3217 }3218 }3219 3220 function removeClass( elem, name ) {3221 var set = " " + elem.className + " ";3222 3223 // Class name may appear multiple times3224 while ( set.indexOf( " " + name + " " ) >= 0 ) {3225 set = set.replace( " " + name + " ", " " );3226 }3227 3228 // trim for prettiness3229 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );3230 }3231 3232 function id( name ) {3233 return defined.document && document.getElementById && document.getElementById( name );3234 }3235 3236 function getUrlConfigHtml() {3237 var i, j, val,3238 escaped, escapedTooltip,3239 selection = false,3240 len = config.urlConfig.length,3241 urlConfigHtml = "";3242 3243 for ( i = 0; i < len; i++ ) {3244 val = config.urlConfig[ i ];3245 if ( typeof val === "string" ) {3246 val = {3247 id: val,3248 label: val3249 };3250 }3251 3252 escaped = escapeText( val.id );3253 escapedTooltip = escapeText( val.tooltip );3254 3255 if ( config[ val.id ] === undefined ) {3256 config[ val.id ] = QUnit.urlParams[ val.id ];3257 }3258 3259 if ( !val.value || typeof val.value === "string" ) {3260 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +3261 "' name='" + escaped + "' type='checkbox'" +3262 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +3263 ( config[ val.id ] ? " checked='checked'" : "" ) +3264 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +3265 "' title='" + escapedTooltip + "'>" + val.label + "</label>";3266 } else {3267 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +3268 "' title='" + escapedTooltip + "'>" + val.label +3269 ": </label><select id='qunit-urlconfig-" + escaped +3270 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";3271 3272 if ( QUnit.is( "array", val.value ) ) {3273 for ( j = 0; j < val.value.length; j++ ) {3274 escaped = escapeText( val.value[ j ] );3275 urlConfigHtml += "<option value='" + escaped + "'" +3276 ( config[ val.id ] === val.value[ j ] ?3277 ( selection = true ) && " selected='selected'" : "" ) +3278 ">" + escaped + "</option>";3279 }3280 } else {3281 for ( j in val.value ) {3282 if ( hasOwn.call( val.value, j ) ) {3283 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +3284 ( config[ val.id ] === j ?3285 ( selection = true ) && " selected='selected'" : "" ) +3286 ">" + escapeText( val.value[ j ] ) + "</option>";3287 }3288 }3289 }3290 if ( config[ val.id ] && !selection ) {3291 escaped = escapeText( config[ val.id ] );3292 urlConfigHtml += "<option value='" + escaped +3293 "' selected='selected' disabled='disabled'>" + escaped + "</option>";3294 }3295 urlConfigHtml += "</select>";3296 }3297 }3298 3299 return urlConfigHtml;3300 }3301 3302 // Handle "click" events on toolbar checkboxes and "change" for select menus.3303 // Updates the URL with the new state of `config.urlConfig` values.3304 function toolbarChanged() {3305 var updatedUrl, value,3306 field = this,3307 params = {};3308 3309 // Detect if field is a select menu or a checkbox3310 if ( "selectedIndex" in field ) {3311 value = field.options[ field.selectedIndex ].value || undefined;3312 } else {3313 value = field.checked ? ( field.defaultValue || true ) : undefined;3314 }3315 3316 params[ field.name ] = value;3317 updatedUrl = setUrl( params );3318 3319 if ( "hidepassed" === field.name && "replaceState" in window.history ) {3320 config[ field.name ] = value || false;3321 if ( value ) {3322 addClass( id( "qunit-tests" ), "hidepass" );3323 } else {3324 removeClass( id( "qunit-tests" ), "hidepass" );3325 }3326 3327 // It is not necessary to refresh the whole page3328 window.history.replaceState( null, "", updatedUrl );3329 } else {3330 window.location = updatedUrl;3331 }3332 }3333 3334 function setUrl( params ) {3335 var key,3336 querystring = "?";3337 3338 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );3339 3340 for ( key in params ) {3341 if ( hasOwn.call( params, key ) ) {3342 if ( params[ key ] === undefined ) {3343 continue;3344 }3345 querystring += encodeURIComponent( key );3346 if ( params[ key ] !== true ) {3347 querystring += "=" + encodeURIComponent( params[ key ] );3348 }3349 querystring += "&";3350 }3351 }3352 return location.protocol + "//" + location.host +3353 location.pathname + querystring.slice( 0, -1 );3354 }3355 3356 function applyUrlParams() {3357 var selectedModule,3358 modulesList = id( "qunit-modulefilter" ),3359 filter = id( "qunit-filter-input" ).value;3360 3361 selectedModule = modulesList ?3362 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :3363 undefined;3364 3365 window.location = setUrl({3366 module: ( selectedModule === "" ) ? undefined : selectedModule,3367 filter: ( filter === "" ) ? undefined : filter,3368 3369 // Remove testId filter3370 testId: undefined3371 });3372 }3373 3374 function toolbarUrlConfigContainer() {3375 var urlConfigContainer = document.createElement( "span" );3376 3377 urlConfigContainer.innerHTML = getUrlConfigHtml();3378 addClass( urlConfigContainer, "qunit-url-config" );3379 3380 // For oldIE support:3381 // * Add handlers to the individual elements instead of the container3382 // * Use "click" instead of "change" for checkboxes3383 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );3384 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );3385 3386 return urlConfigContainer;3387 }3388 3389 function toolbarLooseFilter() {3390 var filter = document.createElement( "form" ),3391 label = document.createElement( "label" ),3392 input = document.createElement( "input" ),3393 button = document.createElement( "button" );3394 3395 addClass( filter, "qunit-filter" );3396 3397 label.innerHTML = "Filter: ";3398 3399 input.type = "text";3400 input.value = config.filter || "";3401 input.name = "filter";3402 input.id = "qunit-filter-input";3403 3404 button.innerHTML = "Go";3405 3406 label.appendChild( input );3407 3408 filter.appendChild( label );3409 filter.appendChild( button );3410 addEvent( filter, "submit", function( ev ) {3411 applyUrlParams();3412 3413 if ( ev && ev.preventDefault ) {3414 ev.preventDefault();3415 }3416 3417 return false;3418 });3419 3420 return filter;3421 }3422 3423 function toolbarModuleFilterHtml() {3424 var i,3425 moduleFilterHtml = "";3426 3427 if ( !modulesList.length ) {3428 return false;3429 }3430 3431 modulesList.sort(function( a, b ) {3432 return a.localeCompare( b );3433 });3434 3435 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +3436 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +3437 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +3438 ">< All Modules ></option>";3439 3440 for ( i = 0; i < modulesList.length; i++ ) {3441 moduleFilterHtml += "<option value='" +3442 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +3443 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +3444 ">" + escapeText( modulesList[ i ] ) + "</option>";3445 }3446 moduleFilterHtml += "</select>";3447 3448 return moduleFilterHtml;3449 }3450 3451 function toolbarModuleFilter() {3452 var toolbar = id( "qunit-testrunner-toolbar" ),3453 moduleFilter = document.createElement( "span" ),3454 moduleFilterHtml = toolbarModuleFilterHtml();3455 3456 if ( !toolbar || !moduleFilterHtml ) {3457 return false;3458 }3459 3460 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );3461 moduleFilter.innerHTML = moduleFilterHtml;3462 3463 addEvent( moduleFilter.lastChild, "change", applyUrlParams );3464 3465 toolbar.appendChild( moduleFilter );3466 }3467 3468 function appendToolbar() {3469 var toolbar = id( "qunit-testrunner-toolbar" );3470 3471 if ( toolbar ) {3472 toolbar.appendChild( toolbarUrlConfigContainer() );3473 toolbar.appendChild( toolbarLooseFilter() );3474 }3475 }3476 3477 function appendHeader() {3478 var header = id( "qunit-header" );3479 3480 if ( header ) {3481 header.innerHTML = "<a href='" +3482 setUrl({ filter: undefined, module: undefined, testId: undefined }) +3483 "'>" + header.innerHTML + "</a> ";3484 }3485 }3486 3487 function appendBanner() {3488 var banner = id( "qunit-banner" );3489 3490 if ( banner ) {3491 banner.className = "";3492 }3493 }3494 3495 function appendTestResults() {3496 var tests = id( "qunit-tests" ),3497 result = id( "qunit-testresult" );3498 3499 if ( result ) {3500 result.parentNode.removeChild( result );3501 }3502 3503 if ( tests ) {3504 tests.innerHTML = "";3505 result = document.createElement( "p" );3506 result.id = "qunit-testresult";3507 result.className = "result";3508 tests.parentNode.insertBefore( result, tests );3509 result.innerHTML = "Running...<br /> ";3510 }3511 }3512 3513 function storeFixture() {3514 var fixture = id( "qunit-fixture" );3515 if ( fixture ) {3516 config.fixture = fixture.innerHTML;3517 }3518 }3519 3520 function appendUserAgent() {3521 var userAgent = id( "qunit-userAgent" );3522 3523 if ( userAgent ) {3524 userAgent.innerHTML = "";3525 userAgent.appendChild(3526 document.createTextNode(3527 "QUnit " + QUnit.version + "; " + navigator.userAgent3528 )3529 );3530 }3531 }3532 3533 function appendTestsList( modules ) {3534 var i, l, x, z, test, moduleObj;3535 3536 for ( i = 0, l = modules.length; i < l; i++ ) {3537 moduleObj = modules[ i ];3538 3539 if ( moduleObj.name ) {3540 modulesList.push( moduleObj.name );3541 }3542 3543 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {3544 test = moduleObj.tests[ x ];3545 3546 appendTest( test.name, test.testId, moduleObj.name );3547 }3548 }3549 }3550 3551 function appendTest( name, testId, moduleName ) {3552 var title, rerunTrigger, testBlock, assertList,3553 tests = id( "qunit-tests" );3554 3555 if ( !tests ) {3556 return;3557 }3558 3559 title = document.createElement( "strong" );3560 title.innerHTML = getNameHtml( name, moduleName );3561 3562 rerunTrigger = document.createElement( "a" );3563 rerunTrigger.innerHTML = "Rerun";3564 rerunTrigger.href = setUrl({ testId: testId });3565 3566 testBlock = document.createElement( "li" );3567 testBlock.appendChild( title );3568 testBlock.appendChild( rerunTrigger );3569 testBlock.id = "qunit-test-output-" + testId;3570 3571 assertList = document.createElement( "ol" );3572 assertList.className = "qunit-assert-list";3573 3574 testBlock.appendChild( assertList );3575 3576 tests.appendChild( testBlock );3577 }3578 3579 // HTML Reporter initialization and load3580 QUnit.begin(function( details ) {3581 var qunit = id( "qunit" );3582 3583 // Fixture is the only one necessary to run without the #qunit element3584 storeFixture();3585 3586 if ( qunit ) {3587 qunit.innerHTML =3588 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +3589 "<h2 id='qunit-banner'></h2>" +3590 "<div id='qunit-testrunner-toolbar'></div>" +3591 "<h2 id='qunit-userAgent'></h2>" +3592 "<ol id='qunit-tests'></ol>";3593 }3594 3595 appendHeader();3596 appendBanner();3597 appendTestResults();3598 appendUserAgent();3599 appendToolbar();3600 appendTestsList( details.modules );3601 toolbarModuleFilter();3602 3603 if ( qunit && config.hidepassed ) {3604 addClass( qunit.lastChild, "hidepass" );3605 }3606 });3607 3608 QUnit.done(function( details ) {3609 var i, key,3610 banner = id( "qunit-banner" ),3611 tests = id( "qunit-tests" ),3612 html = [3613 "Tests completed in ",3614 details.runtime,3615 " milliseconds.<br />",3616 "<span class='passed'>",3617 details.passed,3618 "</span> assertions of <span class='total'>",3619 details.total,3620 "</span> passed, <span class='failed'>",3621 details.failed,3622 "</span> failed."3623 ].join( "" );3624 3625 if ( banner ) {3626 banner.className = details.failed ? "qunit-fail" : "qunit-pass";3627 }3628 3629 if ( tests ) {3630 id( "qunit-testresult" ).innerHTML = html;3631 }3632 3633 if ( config.altertitle && defined.document && document.title ) {3634 3635 // show ✖ for good, ✔ for bad suite result in title3636 // use escape sequences in case file gets loaded with non-utf-8-charset3637 document.title = [3638 ( details.failed ? "\u2716" : "\u2714" ),3639 document.title.replace( /^[\u2714\u2716] /i, "" )3640 ].join( " " );3641 }3642 3643 // clear own sessionStorage items if all tests passed3644 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {3645 for ( i = 0; i < sessionStorage.length; i++ ) {3646 key = sessionStorage.key( i++ );3647 if ( key.indexOf( "qunit-test-" ) === 0 ) {3648 sessionStorage.removeItem( key );3649 }3650 }3651 }3652 3653 // scroll back to top to show results3654 if ( config.scrolltop && window.scrollTo ) {3655 window.scrollTo( 0, 0 );3656 }3657 });3658 3659 function getNameHtml( name, module ) {3660 var nameHtml = "";3661 3662 if ( module ) {3663 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";3664 }3665 3666 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";3667 3668 return nameHtml;3669 }3670 3671 QUnit.testStart(function( details ) {3672 var running, testBlock, bad;3673 3674 testBlock = id( "qunit-test-output-" + details.testId );3675 if ( testBlock ) {3676 testBlock.className = "running";3677 } else {3678 3679 // Report later registered tests3680 appendTest( details.name, details.testId, details.module );3681 }3682 3683 running = id( "qunit-testresult" );3684 if ( running ) {3685 bad = QUnit.config.reorder && defined.sessionStorage &&3686 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );3687 3688 running.innerHTML = ( bad ?3689 "Rerunning previously failed test: <br />" :3690 "Running: <br />" ) +3691 getNameHtml( details.name, details.module );3692 }3693 3694 });3695 3696 QUnit.log(function( details ) {3697 var assertList, assertLi,3698 message, expected, actual,3699 testItem = id( "qunit-test-output-" + details.testId );3700 3701 if ( !testItem ) {3702 return;3703 }3704 3705 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );3706 message = "<span class='test-message'>" + message + "</span>";3707 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";3708 3709 // pushFailure doesn't provide details.expected3710 // when it calls, it's implicit to also not show expected and diff stuff3711 // Also, we need to check details.expected existence, as it can exist and be undefined3712 if ( !details.result && hasOwn.call( details, "expected" ) ) {3713 expected = escapeText( QUnit.dump.parse( details.expected ) );3714 actual = escapeText( QUnit.dump.parse( details.actual ) );3715 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +3716 expected +3717 "</pre></td></tr>";3718 3719 if ( actual !== expected ) {3720 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +3721 actual + "</pre></td></tr>" +3722 "<tr class='test-diff'><th>Diff: </th><td><pre>" +3723 QUnit.diff( expected, actual ) + "</pre></td></tr>";3724 } else {3725 if ( expected.indexOf( "[object Array]" ) !== -1 ||3726 expected.indexOf( "[object Object]" ) !== -1 ) {3727 message += "<tr class='test-message'><th>Message: </th><td>" +3728 "Diff suppressed as the depth of object is more than current max depth (" +3729 QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +3730 " run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +3731 "Rerun</a> without max depth.</p></td></tr>";3732 }3733 }3734 3735 if ( details.source ) {3736 message += "<tr class='test-source'><th>Source: </th><td><pre>" +3737 escapeText( details.source ) + "</pre></td></tr>";3738 }3739 3740 message += "</table>";3741 3742 // this occours when pushFailure is set and we have an extracted stack trace3743 } else if ( !details.result && details.source ) {3744 message += "<table>" +3745 "<tr class='test-source'><th>Source: </th><td><pre>" +3746 escapeText( details.source ) + "</pre></td></tr>" +3747 "</table>";3748 }3749 3750 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];3751 3752 assertLi = document.createElement( "li" );3753 assertLi.className = details.result ? "pass" : "fail";3754 assertLi.innerHTML = message;3755 assertList.appendChild( assertLi );3756 });3757 3758 QUnit.testDone(function( details ) {3759 var testTitle, time, testItem, assertList,3760 good, bad, testCounts, skipped,3761 tests = id( "qunit-tests" );3762 3763 if ( !tests ) {3764 return;3765 }3766 3767 testItem = id( "qunit-test-output-" + details.testId );3768 3769 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];3770 3771 good = details.passed;3772 bad = details.failed;3773 3774 // store result when possible3775 if ( config.reorder && defined.sessionStorage ) {3776 if ( bad ) {3777 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );3778 } else {3779 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );3780 }3781 }3782 3783 if ( bad === 0 ) {3784 addClass( assertList, "qunit-collapsed" );3785 }3786 3787 // testItem.firstChild is the test name3788 testTitle = testItem.firstChild;3789 3790 testCounts = bad ?3791 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :3792 "";3793 3794 testTitle.innerHTML += " <b class='counts'>(" + testCounts +3795 details.assertions.length + ")</b>";3796 3797 if ( details.skipped ) {3798 testItem.className = "skipped";3799 skipped = document.createElement( "em" );3800 skipped.className = "qunit-skipped-label";3801 skipped.innerHTML = "skipped";3802 testItem.insertBefore( skipped, testTitle );3803 } else {3804 addEvent( testTitle, "click", function() {3805 toggleClass( assertList, "qunit-collapsed" );3806 });3807 3808 testItem.className = bad ? "fail" : "pass";3809 3810 time = document.createElement( "span" );3811 time.className = "runtime";3812 time.innerHTML = details.runtime + " ms";3813 testItem.insertBefore( time, assertList );3814 }3815 });3816 3817 if ( defined.document ) {3818 if ( document.readyState === "complete" ) {3819 QUnit.load();3820 } else {3821 addEvent( window, "load", QUnit.load );3822 }3823 } else {3824 config.pageLoaded = true;3825 config.autorun = true;3826 }3827 3828 })(); -
tests/qunit/vendor/sinon-qunit.js
Property changes on: tests/qunit/vendor/qunit.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 /**2 * sinon-qunit 1.0.0, 2010/12/093 *4 * @author Christian Johansen (christian@cjohansen.no)5 *6 * (The BSD License)7 *8 * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no9 * All rights reserved.10 *11 * Redistribution and use in source and binary forms, with or without modification,12 * are permitted provided that the following conditions are met:13 *14 * * Redistributions of source code must retain the above copyright notice,15 * this list of conditions and the following disclaimer.16 * * Redistributions in binary form must reproduce the above copyright notice,17 * this list of conditions and the following disclaimer in the documentation18 * and/or other materials provided with the distribution.19 * * Neither the name of Christian Johansen nor the names of his contributors20 * may be used to endorse or promote products derived from this software21 * without specific prior written permission.22 *23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE26 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.33 */34 /*global sinon, QUnit, test*/35 sinon.assert.fail = function (msg) {36 QUnit.ok(false, msg);37 };38 39 sinon.assert.pass = function (assertion) {40 QUnit.ok(true, assertion);41 };42 43 sinon.config = {44 injectIntoThis: true,45 injectInto: null,46 properties: ["spy", "stub", "mock", "clock", "sandbox"],47 useFakeTimers: true,48 useFakeServer: false49 };50 51 (function (global) {52 var qTest = QUnit.test;53 54 QUnit.test = global.test = function (testName, expected, callback, async) {55 if (arguments.length === 2) {56 callback = expected;57 expected = null;58 }59 60 61 return qTest(testName, expected, sinon.test(callback), async);62 };63 }(this)); -
tests/qunit/vendor/sinon.js
Property changes on: tests/qunit/vendor/sinon-qunit.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 /**2 * Sinon.JS 1.8.2, 2014/02/113 *4 * @author Christian Johansen (christian@cjohansen.no)5 * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS6 *7 * (The BSD License)8 *9 * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no10 * All rights reserved.11 *12 * Redistribution and use in source and binary forms, with or without modification,13 * are permitted provided that the following conditions are met:14 *15 * * Redistributions of source code must retain the above copyright notice,16 * this list of conditions and the following disclaimer.17 * * Redistributions in binary form must reproduce the above copyright notice,18 * this list of conditions and the following disclaimer in the documentation19 * and/or other materials provided with the distribution.20 * * Neither the name of Christian Johansen nor the names of his contributors21 * may be used to endorse or promote products derived from this software22 * without specific prior written permission.23 *24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED26 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE27 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER31 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,32 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.34 */35 36 this.sinon = (function () {37 var samsam, formatio;38 function define(mod, deps, fn) { if (mod == "samsam") { samsam = deps(); } else { formatio = fn(samsam); } }39 define.amd = true;40 ((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) ||41 (typeof module === "object" &&42 function (m) { module.exports = m(); }) || // Node43 function (m) { this.samsam = m(); } // Browser globals44 )(function () {45 var o = Object.prototype;46 var div = typeof document !== "undefined" && document.createElement("div");47 48 function isNaN(value) {49 // Unlike global isNaN, this avoids type coercion50 // typeof check avoids IE host object issues, hat tip to51 // lodash52 var val = value; // JsLint thinks value !== value is "weird"53 return typeof value === "number" && value !== val;54 }55 56 function getClass(value) {57 // Returns the internal [[Class]] by calling Object.prototype.toString58 // with the provided value as this. Return value is a string, naming the59 // internal class, e.g. "Array"60 return o.toString.call(value).split(/[ \]]/)[1];61 }62 63 /**64 * @name samsam.isArguments65 * @param Object object66 *67 * Returns ``true`` if ``object`` is an ``arguments`` object,68 * ``false`` otherwise.69 */70 function isArguments(object) {71 if (typeof object !== "object" || typeof object.length !== "number" ||72 getClass(object) === "Array") {73 return false;74 }75 if (typeof object.callee == "function") { return true; }76 try {77 object[object.length] = 6;78 delete object[object.length];79 } catch (e) {80 return true;81 }82 return false;83 }84 85 /**86 * @name samsam.isElement87 * @param Object object88 *89 * Returns ``true`` if ``object`` is a DOM element node. Unlike90 * Underscore.js/lodash, this function will return ``false`` if ``object``91 * is an *element-like* object, i.e. a regular object with a ``nodeType``92 * property that holds the value ``1``.93 */94 function isElement(object) {95 if (!object || object.nodeType !== 1 || !div) { return false; }96 try {97 object.appendChild(div);98 object.removeChild(div);99 } catch (e) {100 return false;101 }102 return true;103 }104 105 /**106 * @name samsam.keys107 * @param Object object108 *109 * Return an array of own property names.110 */111 function keys(object) {112 var ks = [], prop;113 for (prop in object) {114 if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }115 }116 return ks;117 }118 119 /**120 * @name samsam.isDate121 * @param Object value122 *123 * Returns true if the object is a ``Date``, or *date-like*. Duck typing124 * of date objects work by checking that the object has a ``getTime``125 * function whose return value equals the return value from the object's126 * ``valueOf``.127 */128 function isDate(value) {129 return typeof value.getTime == "function" &&130 value.getTime() == value.valueOf();131 }132 133 /**134 * @name samsam.isNegZero135 * @param Object value136 *137 * Returns ``true`` if ``value`` is ``-0``.138 */139 function isNegZero(value) {140 return value === 0 && 1 / value === -Infinity;141 }142 143 /**144 * @name samsam.equal145 * @param Object obj1146 * @param Object obj2147 *148 * Returns ``true`` if two objects are strictly equal. Compared to149 * ``===`` there are two exceptions:150 *151 * - NaN is considered equal to NaN152 * - -0 and +0 are not considered equal153 */154 function identical(obj1, obj2) {155 if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {156 return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);157 }158 }159 160 161 /**162 * @name samsam.deepEqual163 * @param Object obj1164 * @param Object obj2165 *166 * Deep equal comparison. Two values are "deep equal" if:167 *168 * - They are equal, according to samsam.identical169 * - They are both date objects representing the same time170 * - They are both arrays containing elements that are all deepEqual171 * - They are objects with the same set of properties, and each property172 * in ``obj1`` is deepEqual to the corresponding property in ``obj2``173 *174 * Supports cyclic objects.175 */176 function deepEqualCyclic(obj1, obj2) {177 178 // used for cyclic comparison179 // contain already visited objects180 var objects1 = [],181 objects2 = [],182 // contain pathes (position in the object structure)183 // of the already visited objects184 // indexes same as in objects arrays185 paths1 = [],186 paths2 = [],187 // contains combinations of already compared objects188 // in the manner: { "$1['ref']$2['ref']": true }189 compared = {};190 191 /**192 * used to check, if the value of a property is an object193 * (cyclic logic is only needed for objects)194 * only needed for cyclic logic195 */196 function isObject(value) {197 198 if (typeof value === 'object' && value !== null &&199 !(value instanceof Boolean) &&200 !(value instanceof Date) &&201 !(value instanceof Number) &&202 !(value instanceof RegExp) &&203 !(value instanceof String)) {204 205 return true;206 }207 208 return false;209 }210 211 /**212 * returns the index of the given object in the213 * given objects array, -1 if not contained214 * only needed for cyclic logic215 */216 function getIndex(objects, obj) {217 218 var i;219 for (i = 0; i < objects.length; i++) {220 if (objects[i] === obj) {221 return i;222 }223 }224 225 return -1;226 }227 228 // does the recursion for the deep equal check229 return (function deepEqual(obj1, obj2, path1, path2) {230 var type1 = typeof obj1;231 var type2 = typeof obj2;232 233 // == null also matches undefined234 if (obj1 === obj2 ||235 isNaN(obj1) || isNaN(obj2) ||236 obj1 == null || obj2 == null ||237 type1 !== "object" || type2 !== "object") {238 239 return identical(obj1, obj2);240 }241 242 // Elements are only equal if identical(expected, actual)243 if (isElement(obj1) || isElement(obj2)) { return false; }244 245 var isDate1 = isDate(obj1), isDate2 = isDate(obj2);246 if (isDate1 || isDate2) {247 if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {248 return false;249 }250 }251 252 if (obj1 instanceof RegExp && obj2 instanceof RegExp) {253 if (obj1.toString() !== obj2.toString()) { return false; }254 }255 256 var class1 = getClass(obj1);257 var class2 = getClass(obj2);258 var keys1 = keys(obj1);259 var keys2 = keys(obj2);260 261 if (isArguments(obj1) || isArguments(obj2)) {262 if (obj1.length !== obj2.length) { return false; }263 } else {264 if (type1 !== type2 || class1 !== class2 ||265 keys1.length !== keys2.length) {266 return false;267 }268 }269 270 var key, i, l,271 // following vars are used for the cyclic logic272 value1, value2,273 isObject1, isObject2,274 index1, index2,275 newPath1, newPath2;276 277 for (i = 0, l = keys1.length; i < l; i++) {278 key = keys1[i];279 if (!o.hasOwnProperty.call(obj2, key)) {280 return false;281 }282 283 // Start of the cyclic logic284 285 value1 = obj1[key];286 value2 = obj2[key];287 288 isObject1 = isObject(value1);289 isObject2 = isObject(value2);290 291 // determine, if the objects were already visited292 // (it's faster to check for isObject first, than to293 // get -1 from getIndex for non objects)294 index1 = isObject1 ? getIndex(objects1, value1) : -1;295 index2 = isObject2 ? getIndex(objects2, value2) : -1;296 297 // determine the new pathes of the objects298 // - for non cyclic objects the current path will be extended299 // by current property name300 // - for cyclic objects the stored path is taken301 newPath1 = index1 !== -1302 ? paths1[index1]303 : path1 + '[' + JSON.stringify(key) + ']';304 newPath2 = index2 !== -1305 ? paths2[index2]306 : path2 + '[' + JSON.stringify(key) + ']';307 308 // stop recursion if current objects are already compared309 if (compared[newPath1 + newPath2]) {310 return true;311 }312 313 // remember the current objects and their pathes314 if (index1 === -1 && isObject1) {315 objects1.push(value1);316 paths1.push(newPath1);317 }318 if (index2 === -1 && isObject2) {319 objects2.push(value2);320 paths2.push(newPath2);321 }322 323 // remember that the current objects are already compared324 if (isObject1 && isObject2) {325 compared[newPath1 + newPath2] = true;326 }327 328 // End of cyclic logic329 330 // neither value1 nor value2 is a cycle331 // continue with next level332 if (!deepEqual(value1, value2, newPath1, newPath2)) {333 return false;334 }335 }336 337 return true;338 339 }(obj1, obj2, '$1', '$2'));340 }341 342 var match;343 344 function arrayContains(array, subset) {345 if (subset.length === 0) { return true; }346 var i, l, j, k;347 for (i = 0, l = array.length; i < l; ++i) {348 if (match(array[i], subset[0])) {349 for (j = 0, k = subset.length; j < k; ++j) {350 if (!match(array[i + j], subset[j])) { return false; }351 }352 return true;353 }354 }355 return false;356 }357 358 /**359 * @name samsam.match360 * @param Object object361 * @param Object matcher362 *363 * Compare arbitrary value ``object`` with matcher.364 */365 match = function match(object, matcher) {366 if (matcher && typeof matcher.test === "function") {367 return matcher.test(object);368 }369 370 if (typeof matcher === "function") {371 return matcher(object) === true;372 }373 374 if (typeof matcher === "string") {375 matcher = matcher.toLowerCase();376 var notNull = typeof object === "string" || !!object;377 return notNull &&378 (String(object)).toLowerCase().indexOf(matcher) >= 0;379 }380 381 if (typeof matcher === "number") {382 return matcher === object;383 }384 385 if (typeof matcher === "boolean") {386 return matcher === object;387 }388 389 if (getClass(object) === "Array" && getClass(matcher) === "Array") {390 return arrayContains(object, matcher);391 }392 393 if (matcher && typeof matcher === "object") {394 var prop;395 for (prop in matcher) {396 if (!match(object[prop], matcher[prop])) {397 return false;398 }399 }400 return true;401 }402 403 throw new Error("Matcher was not a string, a number, a " +404 "function, a boolean or an object");405 };406 407 return {408 isArguments: isArguments,409 isElement: isElement,410 isDate: isDate,411 isNegZero: isNegZero,412 identical: identical,413 deepEqual: deepEqualCyclic,414 match: match,415 keys: keys416 };417 });418 ((typeof define === "function" && define.amd && function (m) {419 define("formatio", ["samsam"], m);420 }) || (typeof module === "object" && function (m) {421 module.exports = m(require("samsam"));422 }) || function (m) { this.formatio = m(this.samsam); }423 )(function (samsam) {424 425 var formatio = {426 excludeConstructors: ["Object", /^.$/],427 quoteStrings: true428 };429 430 var hasOwn = Object.prototype.hasOwnProperty;431 432 var specialObjects = [];433 if (typeof global !== "undefined") {434 specialObjects.push({ object: global, value: "[object global]" });435 }436 if (typeof document !== "undefined") {437 specialObjects.push({438 object: document,439 value: "[object HTMLDocument]"440 });441 }442 if (typeof window !== "undefined") {443 specialObjects.push({ object: window, value: "[object Window]" });444 }445 446 function functionName(func) {447 if (!func) { return ""; }448 if (func.displayName) { return func.displayName; }449 if (func.name) { return func.name; }450 var matches = func.toString().match(/function\s+([^\(]+)/m);451 return (matches && matches[1]) || "";452 }453 454 function constructorName(f, object) {455 var name = functionName(object && object.constructor);456 var excludes = f.excludeConstructors ||457 formatio.excludeConstructors || [];458 459 var i, l;460 for (i = 0, l = excludes.length; i < l; ++i) {461 if (typeof excludes[i] === "string" && excludes[i] === name) {462 return "";463 } else if (excludes[i].test && excludes[i].test(name)) {464 return "";465 }466 }467 468 return name;469 }470 471 function isCircular(object, objects) {472 if (typeof object !== "object") { return false; }473 var i, l;474 for (i = 0, l = objects.length; i < l; ++i) {475 if (objects[i] === object) { return true; }476 }477 return false;478 }479 480 function ascii(f, object, processed, indent) {481 if (typeof object === "string") {482 var qs = f.quoteStrings;483 var quote = typeof qs !== "boolean" || qs;484 return processed || quote ? '"' + object + '"' : object;485 }486 487 if (typeof object === "function" && !(object instanceof RegExp)) {488 return ascii.func(object);489 }490 491 processed = processed || [];492 493 if (isCircular(object, processed)) { return "[Circular]"; }494 495 if (Object.prototype.toString.call(object) === "[object Array]") {496 return ascii.array.call(f, object, processed);497 }498 499 if (!object) { return String((1/object) === -Infinity ? "-0" : object); }500 if (samsam.isElement(object)) { return ascii.element(object); }501 502 if (typeof object.toString === "function" &&503 object.toString !== Object.prototype.toString) {504 return object.toString();505 }506 507 var i, l;508 for (i = 0, l = specialObjects.length; i < l; i++) {509 if (object === specialObjects[i].object) {510 return specialObjects[i].value;511 }512 }513 514 return ascii.object.call(f, object, processed, indent);515 }516 517 ascii.func = function (func) {518 return "function " + functionName(func) + "() {}";519 };520 521 ascii.array = function (array, processed) {522 processed = processed || [];523 processed.push(array);524 var i, l, pieces = [];525 for (i = 0, l = array.length; i < l; ++i) {526 pieces.push(ascii(this, array[i], processed));527 }528 return "[" + pieces.join(", ") + "]";529 };530 531 ascii.object = function (object, processed, indent) {532 processed = processed || [];533 processed.push(object);534 indent = indent || 0;535 var pieces = [], properties = samsam.keys(object).sort();536 var length = 3;537 var prop, str, obj, i, l;538 539 for (i = 0, l = properties.length; i < l; ++i) {540 prop = properties[i];541 obj = object[prop];542 543 if (isCircular(obj, processed)) {544 str = "[Circular]";545 } else {546 str = ascii(this, obj, processed, indent + 2);547 }548 549 str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;550 length += str.length;551 pieces.push(str);552 }553 554 var cons = constructorName(this, object);555 var prefix = cons ? "[" + cons + "] " : "";556 var is = "";557 for (i = 0, l = indent; i < l; ++i) { is += " "; }558 559 if (length + indent > 80) {560 return prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" +561 is + "}";562 }563 return prefix + "{ " + pieces.join(", ") + " }";564 };565 566 ascii.element = function (element) {567 var tagName = element.tagName.toLowerCase();568 var attrs = element.attributes, attr, pairs = [], attrName, i, l, val;569 570 for (i = 0, l = attrs.length; i < l; ++i) {571 attr = attrs.item(i);572 attrName = attr.nodeName.toLowerCase().replace("html:", "");573 val = attr.nodeValue;574 if (attrName !== "contenteditable" || val !== "inherit") {575 if (!!val) { pairs.push(attrName + "=\"" + val + "\""); }576 }577 }578 579 var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");580 var content = element.innerHTML;581 582 if (content.length > 20) {583 content = content.substr(0, 20) + "[...]";584 }585 586 var res = formatted + pairs.join(" ") + ">" + content +587 "</" + tagName + ">";588 589 return res.replace(/ contentEditable="inherit"/, "");590 };591 592 function Formatio(options) {593 for (var opt in options) {594 this[opt] = options[opt];595 }596 }597 598 Formatio.prototype = {599 functionName: functionName,600 601 configure: function (options) {602 return new Formatio(options);603 },604 605 constructorName: function (object) {606 return constructorName(this, object);607 },608 609 ascii: function (object, processed, indent) {610 return ascii(this, object, processed, indent);611 }612 };613 614 return Formatio.prototype;615 });616 /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/617 /*global module, require, __dirname, document*/618 /**619 * Sinon core utilities. For internal use only.620 *621 * @author Christian Johansen (christian@cjohansen.no)622 * @license BSD623 *624 * Copyright (c) 2010-2013 Christian Johansen625 */626 627 var sinon = (function (formatio) {628 var div = typeof document != "undefined" && document.createElement("div");629 var hasOwn = Object.prototype.hasOwnProperty;630 631 function isDOMNode(obj) {632 var success = false;633 634 try {635 obj.appendChild(div);636 success = div.parentNode == obj;637 } catch (e) {638 return false;639 } finally {640 try {641 obj.removeChild(div);642 } catch (e) {643 // Remove failed, not much we can do about that644 }645 }646 647 return success;648 }649 650 function isElement(obj) {651 return div && obj && obj.nodeType === 1 && isDOMNode(obj);652 }653 654 function isFunction(obj) {655 return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply);656 }657 658 function mirrorProperties(target, source) {659 for (var prop in source) {660 if (!hasOwn.call(target, prop)) {661 target[prop] = source[prop];662 }663 }664 }665 666 function isRestorable (obj) {667 return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon;668 }669 670 var sinon = {671 wrapMethod: function wrapMethod(object, property, method) {672 if (!object) {673 throw new TypeError("Should wrap property of object");674 }675 676 if (typeof method != "function") {677 throw new TypeError("Method wrapper should be function");678 }679 680 var wrappedMethod = object[property],681 error;682 683 if (!isFunction(wrappedMethod)) {684 error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +685 property + " as function");686 }687 688 if (wrappedMethod.restore && wrappedMethod.restore.sinon) {689 error = new TypeError("Attempted to wrap " + property + " which is already wrapped");690 }691 692 if (wrappedMethod.calledBefore) {693 var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";694 error = new TypeError("Attempted to wrap " + property + " which is already " + verb);695 }696 697 if (error) {698 if (wrappedMethod._stack) {699 error.stack += '\n--------------\n' + wrappedMethod._stack;700 }701 throw error;702 }703 704 // IE 8 does not support hasOwnProperty on the window object and Firefox has a problem705 // when using hasOwn.call on objects from other frames.706 var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property);707 object[property] = method;708 method.displayName = property;709 // Set up a stack trace which can be used later to find what line of710 // code the original method was created on.711 method._stack = (new Error('Stack Trace for original')).stack;712 713 method.restore = function () {714 // For prototype properties try to reset by delete first.715 // If this fails (ex: localStorage on mobile safari) then force a reset716 // via direct assignment.717 if (!owned) {718 delete object[property];719 }720 if (object[property] === method) {721 object[property] = wrappedMethod;722 }723 };724 725 method.restore.sinon = true;726 mirrorProperties(method, wrappedMethod);727 728 return method;729 },730 731 extend: function extend(target) {732 for (var i = 1, l = arguments.length; i < l; i += 1) {733 for (var prop in arguments[i]) {734 if (arguments[i].hasOwnProperty(prop)) {735 target[prop] = arguments[i][prop];736 }737 738 // DONT ENUM bug, only care about toString739 if (arguments[i].hasOwnProperty("toString") &&740 arguments[i].toString != target.toString) {741 target.toString = arguments[i].toString;742 }743 }744 }745 746 return target;747 },748 749 create: function create(proto) {750 var F = function () {};751 F.prototype = proto;752 return new F();753 },754 755 deepEqual: function deepEqual(a, b) {756 if (sinon.match && sinon.match.isMatcher(a)) {757 return a.test(b);758 }759 if (typeof a != "object" || typeof b != "object") {760 return a === b;761 }762 763 if (isElement(a) || isElement(b)) {764 return a === b;765 }766 767 if (a === b) {768 return true;769 }770 771 if ((a === null && b !== null) || (a !== null && b === null)) {772 return false;773 }774 775 var aString = Object.prototype.toString.call(a);776 if (aString != Object.prototype.toString.call(b)) {777 return false;778 }779 780 if (aString == "[object Date]") {781 return a.valueOf() === b.valueOf();782 }783 784 var prop, aLength = 0, bLength = 0;785 786 if (aString == "[object Array]" && a.length !== b.length) {787 return false;788 }789 790 for (prop in a) {791 aLength += 1;792 793 if (!deepEqual(a[prop], b[prop])) {794 return false;795 }796 }797 798 for (prop in b) {799 bLength += 1;800 }801 802 return aLength == bLength;803 },804 805 functionName: function functionName(func) {806 var name = func.displayName || func.name;807 808 // Use function decomposition as a last resort to get function809 // name. Does not rely on function decomposition to work - if it810 // doesn't debugging will be slightly less informative811 // (i.e. toString will say 'spy' rather than 'myFunc').812 if (!name) {813 var matches = func.toString().match(/function ([^\s\(]+)/);814 name = matches && matches[1];815 }816 817 return name;818 },819 820 functionToString: function toString() {821 if (this.getCall && this.callCount) {822 var thisValue, prop, i = this.callCount;823 824 while (i--) {825 thisValue = this.getCall(i).thisValue;826 827 for (prop in thisValue) {828 if (thisValue[prop] === this) {829 return prop;830 }831 }832 }833 }834 835 return this.displayName || "sinon fake";836 },837 838 getConfig: function (custom) {839 var config = {};840 custom = custom || {};841 var defaults = sinon.defaultConfig;842 843 for (var prop in defaults) {844 if (defaults.hasOwnProperty(prop)) {845 config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];846 }847 }848 849 return config;850 },851 852 format: function (val) {853 return "" + val;854 },855 856 defaultConfig: {857 injectIntoThis: true,858 injectInto: null,859 properties: ["spy", "stub", "mock", "clock", "server", "requests"],860 useFakeTimers: true,861 useFakeServer: true862 },863 864 timesInWords: function timesInWords(count) {865 return count == 1 && "once" ||866 count == 2 && "twice" ||867 count == 3 && "thrice" ||868 (count || 0) + " times";869 },870 871 calledInOrder: function (spies) {872 for (var i = 1, l = spies.length; i < l; i++) {873 if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) {874 return false;875 }876 }877 878 return true;879 },880 881 orderByFirstCall: function (spies) {882 return spies.sort(function (a, b) {883 // uuid, won't ever be equal884 var aCall = a.getCall(0);885 var bCall = b.getCall(0);886 var aId = aCall && aCall.callId || -1;887 var bId = bCall && bCall.callId || -1;888 889 return aId < bId ? -1 : 1;890 });891 },892 893 log: function () {},894 895 logError: function (label, err) {896 var msg = label + " threw exception: ";897 sinon.log(msg + "[" + err.name + "] " + err.message);898 if (err.stack) { sinon.log(err.stack); }899 900 setTimeout(function () {901 err.message = msg + err.message;902 throw err;903 }, 0);904 },905 906 typeOf: function (value) {907 if (value === null) {908 return "null";909 }910 else if (value === undefined) {911 return "undefined";912 }913 var string = Object.prototype.toString.call(value);914 return string.substring(8, string.length - 1).toLowerCase();915 },916 917 createStubInstance: function (constructor) {918 if (typeof constructor !== "function") {919 throw new TypeError("The constructor should be a function.");920 }921 return sinon.stub(sinon.create(constructor.prototype));922 },923 924 restore: function (object) {925 if (object !== null && typeof object === "object") {926 for (var prop in object) {927 if (isRestorable(object[prop])) {928 object[prop].restore();929 }930 }931 }932 else if (isRestorable(object)) {933 object.restore();934 }935 }936 };937 938 var isNode = typeof module !== "undefined" && module.exports;939 var isAMD = typeof define === 'function' && typeof define.amd === 'object' && define.amd;940 941 if (isAMD) {942 define(function(){943 return sinon;944 });945 } else if (isNode) {946 try {947 formatio = require("formatio");948 } catch (e) {}949 module.exports = sinon;950 module.exports.spy = require("./sinon/spy");951 module.exports.spyCall = require("./sinon/call");952 module.exports.behavior = require("./sinon/behavior");953 module.exports.stub = require("./sinon/stub");954 module.exports.mock = require("./sinon/mock");955 module.exports.collection = require("./sinon/collection");956 module.exports.assert = require("./sinon/assert");957 module.exports.sandbox = require("./sinon/sandbox");958 module.exports.test = require("./sinon/test");959 module.exports.testCase = require("./sinon/test_case");960 module.exports.assert = require("./sinon/assert");961 module.exports.match = require("./sinon/match");962 }963 964 if (formatio) {965 var formatter = formatio.configure({ quoteStrings: false });966 sinon.format = function () {967 return formatter.ascii.apply(formatter, arguments);968 };969 } else if (isNode) {970 try {971 var util = require("util");972 sinon.format = function (value) {973 return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value;974 };975 } catch (e) {976 /* Node, but no util module - would be very old, but better safe than977 sorry */978 }979 }980 981 return sinon;982 }(typeof formatio == "object" && formatio));983 984 /* @depend ../sinon.js */985 /*jslint eqeqeq: false, onevar: false, plusplus: false*/986 /*global module, require, sinon*/987 /**988 * Match functions989 *990 * @author Maximilian Antoni (mail@maxantoni.de)991 * @license BSD992 *993 * Copyright (c) 2012 Maximilian Antoni994 */995 996 (function (sinon) {997 var commonJSModule = typeof module !== 'undefined' && module.exports;998 999 if (!sinon && commonJSModule) {1000 sinon = require("../sinon");1001 }1002 1003 if (!sinon) {1004 return;1005 }1006 1007 function assertType(value, type, name) {1008 var actual = sinon.typeOf(value);1009 if (actual !== type) {1010 throw new TypeError("Expected type of " + name + " to be " +1011 type + ", but was " + actual);1012 }1013 }1014 1015 var matcher = {1016 toString: function () {1017 return this.message;1018 }1019 };1020 1021 function isMatcher(object) {1022 return matcher.isPrototypeOf(object);1023 }1024 1025 function matchObject(expectation, actual) {1026 if (actual === null || actual === undefined) {1027 return false;1028 }1029 for (var key in expectation) {1030 if (expectation.hasOwnProperty(key)) {1031 var exp = expectation[key];1032 var act = actual[key];1033 if (match.isMatcher(exp)) {1034 if (!exp.test(act)) {1035 return false;1036 }1037 } else if (sinon.typeOf(exp) === "object") {1038 if (!matchObject(exp, act)) {1039 return false;1040 }1041 } else if (!sinon.deepEqual(exp, act)) {1042 return false;1043 }1044 }1045 }1046 return true;1047 }1048 1049 matcher.or = function (m2) {1050 if (!isMatcher(m2)) {1051 throw new TypeError("Matcher expected");1052 }1053 var m1 = this;1054 var or = sinon.create(matcher);1055 or.test = function (actual) {1056 return m1.test(actual) || m2.test(actual);1057 };1058 or.message = m1.message + ".or(" + m2.message + ")";1059 return or;1060 };1061 1062 matcher.and = function (m2) {1063 if (!isMatcher(m2)) {1064 throw new TypeError("Matcher expected");1065 }1066 var m1 = this;1067 var and = sinon.create(matcher);1068 and.test = function (actual) {1069 return m1.test(actual) && m2.test(actual);1070 };1071 and.message = m1.message + ".and(" + m2.message + ")";1072 return and;1073 };1074 1075 var match = function (expectation, message) {1076 var m = sinon.create(matcher);1077 var type = sinon.typeOf(expectation);1078 switch (type) {1079 case "object":1080 if (typeof expectation.test === "function") {1081 m.test = function (actual) {1082 return expectation.test(actual) === true;1083 };1084 m.message = "match(" + sinon.functionName(expectation.test) + ")";1085 return m;1086 }1087 var str = [];1088 for (var key in expectation) {1089 if (expectation.hasOwnProperty(key)) {1090 str.push(key + ": " + expectation[key]);1091 }1092 }1093 m.test = function (actual) {1094 return matchObject(expectation, actual);1095 };1096 m.message = "match(" + str.join(", ") + ")";1097 break;1098 case "number":1099 m.test = function (actual) {1100 return expectation == actual;1101 };1102 break;1103 case "string":1104 m.test = function (actual) {1105 if (typeof actual !== "string") {1106 return false;1107 }1108 return actual.indexOf(expectation) !== -1;1109 };1110 m.message = "match(\"" + expectation + "\")";1111 break;1112 case "regexp":1113 m.test = function (actual) {1114 if (typeof actual !== "string") {1115 return false;1116 }1117 return expectation.test(actual);1118 };1119 break;1120 case "function":1121 m.test = expectation;1122 if (message) {1123 m.message = message;1124 } else {1125 m.message = "match(" + sinon.functionName(expectation) + ")";1126 }1127 break;1128 default:1129 m.test = function (actual) {1130 return sinon.deepEqual(expectation, actual);1131 };1132 }1133 if (!m.message) {1134 m.message = "match(" + expectation + ")";1135 }1136 return m;1137 };1138 1139 match.isMatcher = isMatcher;1140 1141 match.any = match(function () {1142 return true;1143 }, "any");1144 1145 match.defined = match(function (actual) {1146 return actual !== null && actual !== undefined;1147 }, "defined");1148 1149 match.truthy = match(function (actual) {1150 return !!actual;1151 }, "truthy");1152 1153 match.falsy = match(function (actual) {1154 return !actual;1155 }, "falsy");1156 1157 match.same = function (expectation) {1158 return match(function (actual) {1159 return expectation === actual;1160 }, "same(" + expectation + ")");1161 };1162 1163 match.typeOf = function (type) {1164 assertType(type, "string", "type");1165 return match(function (actual) {1166 return sinon.typeOf(actual) === type;1167 }, "typeOf(\"" + type + "\")");1168 };1169 1170 match.instanceOf = function (type) {1171 assertType(type, "function", "type");1172 return match(function (actual) {1173 return actual instanceof type;1174 }, "instanceOf(" + sinon.functionName(type) + ")");1175 };1176 1177 function createPropertyMatcher(propertyTest, messagePrefix) {1178 return function (property, value) {1179 assertType(property, "string", "property");1180 var onlyProperty = arguments.length === 1;1181 var message = messagePrefix + "(\"" + property + "\"";1182 if (!onlyProperty) {1183 message += ", " + value;1184 }1185 message += ")";1186 return match(function (actual) {1187 if (actual === undefined || actual === null ||1188 !propertyTest(actual, property)) {1189 return false;1190 }1191 return onlyProperty || sinon.deepEqual(value, actual[property]);1192 }, message);1193 };1194 }1195 1196 match.has = createPropertyMatcher(function (actual, property) {1197 if (typeof actual === "object") {1198 return property in actual;1199 }1200 return actual[property] !== undefined;1201 }, "has");1202 1203 match.hasOwn = createPropertyMatcher(function (actual, property) {1204 return actual.hasOwnProperty(property);1205 }, "hasOwn");1206 1207 match.bool = match.typeOf("boolean");1208 match.number = match.typeOf("number");1209 match.string = match.typeOf("string");1210 match.object = match.typeOf("object");1211 match.func = match.typeOf("function");1212 match.array = match.typeOf("array");1213 match.regexp = match.typeOf("regexp");1214 match.date = match.typeOf("date");1215 1216 if (commonJSModule) {1217 module.exports = match;1218 } else {1219 sinon.match = match;1220 }1221 }(typeof sinon == "object" && sinon || null));1222 1223 /**1224 * @depend ../sinon.js1225 * @depend match.js1226 */1227 /*jslint eqeqeq: false, onevar: false, plusplus: false*/1228 /*global module, require, sinon*/1229 /**1230 * Spy calls1231 *1232 * @author Christian Johansen (christian@cjohansen.no)1233 * @author Maximilian Antoni (mail@maxantoni.de)1234 * @license BSD1235 *1236 * Copyright (c) 2010-2013 Christian Johansen1237 * Copyright (c) 2013 Maximilian Antoni1238 */1239 1240 (function (sinon) {1241 var commonJSModule = typeof module !== 'undefined' && module.exports;1242 if (!sinon && commonJSModule) {1243 sinon = require("../sinon");1244 }1245 1246 if (!sinon) {1247 return;1248 }1249 1250 function throwYieldError(proxy, text, args) {1251 var msg = sinon.functionName(proxy) + text;1252 if (args.length) {1253 msg += " Received [" + slice.call(args).join(", ") + "]";1254 }1255 throw new Error(msg);1256 }1257 1258 var slice = Array.prototype.slice;1259 1260 var callProto = {1261 calledOn: function calledOn(thisValue) {1262 if (sinon.match && sinon.match.isMatcher(thisValue)) {1263 return thisValue.test(this.thisValue);1264 }1265 return this.thisValue === thisValue;1266 },1267 1268 calledWith: function calledWith() {1269 for (var i = 0, l = arguments.length; i < l; i += 1) {1270 if (!sinon.deepEqual(arguments[i], this.args[i])) {1271 return false;1272 }1273 }1274 1275 return true;1276 },1277 1278 calledWithMatch: function calledWithMatch() {1279 for (var i = 0, l = arguments.length; i < l; i += 1) {1280 var actual = this.args[i];1281 var expectation = arguments[i];1282 if (!sinon.match || !sinon.match(expectation).test(actual)) {1283 return false;1284 }1285 }1286 return true;1287 },1288 1289 calledWithExactly: function calledWithExactly() {1290 return arguments.length == this.args.length &&1291 this.calledWith.apply(this, arguments);1292 },1293 1294 notCalledWith: function notCalledWith() {1295 return !this.calledWith.apply(this, arguments);1296 },1297 1298 notCalledWithMatch: function notCalledWithMatch() {1299 return !this.calledWithMatch.apply(this, arguments);1300 },1301 1302 returned: function returned(value) {1303 return sinon.deepEqual(value, this.returnValue);1304 },1305 1306 threw: function threw(error) {1307 if (typeof error === "undefined" || !this.exception) {1308 return !!this.exception;1309 }1310 1311 return this.exception === error || this.exception.name === error;1312 },1313 1314 calledWithNew: function calledWithNew() {1315 return this.proxy.prototype && this.thisValue instanceof this.proxy;1316 },1317 1318 calledBefore: function (other) {1319 return this.callId < other.callId;1320 },1321 1322 calledAfter: function (other) {1323 return this.callId > other.callId;1324 },1325 1326 callArg: function (pos) {1327 this.args[pos]();1328 },1329 1330 callArgOn: function (pos, thisValue) {1331 this.args[pos].apply(thisValue);1332 },1333 1334 callArgWith: function (pos) {1335 this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1)));1336 },1337 1338 callArgOnWith: function (pos, thisValue) {1339 var args = slice.call(arguments, 2);1340 this.args[pos].apply(thisValue, args);1341 },1342 1343 "yield": function () {1344 this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0)));1345 },1346 1347 yieldOn: function (thisValue) {1348 var args = this.args;1349 for (var i = 0, l = args.length; i < l; ++i) {1350 if (typeof args[i] === "function") {1351 args[i].apply(thisValue, slice.call(arguments, 1));1352 return;1353 }1354 }1355 throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);1356 },1357 1358 yieldTo: function (prop) {1359 this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1)));1360 },1361 1362 yieldToOn: function (prop, thisValue) {1363 var args = this.args;1364 for (var i = 0, l = args.length; i < l; ++i) {1365 if (args[i] && typeof args[i][prop] === "function") {1366 args[i][prop].apply(thisValue, slice.call(arguments, 2));1367 return;1368 }1369 }1370 throwYieldError(this.proxy, " cannot yield to '" + prop +1371 "' since no callback was passed.", args);1372 },1373 1374 toString: function () {1375 var callStr = this.proxy.toString() + "(";1376 var args = [];1377 1378 for (var i = 0, l = this.args.length; i < l; ++i) {1379 args.push(sinon.format(this.args[i]));1380 }1381 1382 callStr = callStr + args.join(", ") + ")";1383 1384 if (typeof this.returnValue != "undefined") {1385 callStr += " => " + sinon.format(this.returnValue);1386 }1387 1388 if (this.exception) {1389 callStr += " !" + this.exception.name;1390 1391 if (this.exception.message) {1392 callStr += "(" + this.exception.message + ")";1393 }1394 }1395 1396 return callStr;1397 }1398 };1399 1400 callProto.invokeCallback = callProto.yield;1401 1402 function createSpyCall(spy, thisValue, args, returnValue, exception, id) {1403 if (typeof id !== "number") {1404 throw new TypeError("Call id is not a number");1405 }1406 var proxyCall = sinon.create(callProto);1407 proxyCall.proxy = spy;1408 proxyCall.thisValue = thisValue;1409 proxyCall.args = args;1410 proxyCall.returnValue = returnValue;1411 proxyCall.exception = exception;1412 proxyCall.callId = id;1413 1414 return proxyCall;1415 }1416 createSpyCall.toString = callProto.toString; // used by mocks1417 1418 if (commonJSModule) {1419 module.exports = createSpyCall;1420 } else {1421 sinon.spyCall = createSpyCall;1422 }1423 }(typeof sinon == "object" && sinon || null));1424 1425 1426 /**1427 * @depend ../sinon.js1428 * @depend call.js1429 */1430 /*jslint eqeqeq: false, onevar: false, plusplus: false*/1431 /*global module, require, sinon*/1432 /**1433 * Spy functions1434 *1435 * @author Christian Johansen (christian@cjohansen.no)1436 * @license BSD1437 *1438 * Copyright (c) 2010-2013 Christian Johansen1439 */1440 1441 (function (sinon) {1442 var commonJSModule = typeof module !== 'undefined' && module.exports;1443 var push = Array.prototype.push;1444 var slice = Array.prototype.slice;1445 var callId = 0;1446 1447 if (!sinon && commonJSModule) {1448 sinon = require("../sinon");1449 }1450 1451 if (!sinon) {1452 return;1453 }1454 1455 function spy(object, property) {1456 if (!property && typeof object == "function") {1457 return spy.create(object);1458 }1459 1460 if (!object && !property) {1461 return spy.create(function () { });1462 }1463 1464 var method = object[property];1465 return sinon.wrapMethod(object, property, spy.create(method));1466 }1467 1468 function matchingFake(fakes, args, strict) {1469 if (!fakes) {1470 return;1471 }1472 1473 for (var i = 0, l = fakes.length; i < l; i++) {1474 if (fakes[i].matches(args, strict)) {1475 return fakes[i];1476 }1477 }1478 }1479 1480 function incrementCallCount() {1481 this.called = true;1482 this.callCount += 1;1483 this.notCalled = false;1484 this.calledOnce = this.callCount == 1;1485 this.calledTwice = this.callCount == 2;1486 this.calledThrice = this.callCount == 3;1487 }1488 1489 function createCallProperties() {1490 this.firstCall = this.getCall(0);1491 this.secondCall = this.getCall(1);1492 this.thirdCall = this.getCall(2);1493 this.lastCall = this.getCall(this.callCount - 1);1494 }1495 1496 var vars = "a,b,c,d,e,f,g,h,i,j,k,l";1497 function createProxy(func) {1498 // Retain the function length:1499 var p;1500 if (func.length) {1501 eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) +1502 ") { return p.invoke(func, this, slice.call(arguments)); });");1503 }1504 else {1505 p = function proxy() {1506 return p.invoke(func, this, slice.call(arguments));1507 };1508 }1509 return p;1510 }1511 1512 var uuid = 0;1513 1514 // Public API1515 var spyApi = {1516 reset: function () {1517 this.called = false;1518 this.notCalled = true;1519 this.calledOnce = false;1520 this.calledTwice = false;1521 this.calledThrice = false;1522 this.callCount = 0;1523 this.firstCall = null;1524 this.secondCall = null;1525 this.thirdCall = null;1526 this.lastCall = null;1527 this.args = [];1528 this.returnValues = [];1529 this.thisValues = [];1530 this.exceptions = [];1531 this.callIds = [];1532 if (this.fakes) {1533 for (var i = 0; i < this.fakes.length; i++) {1534 this.fakes[i].reset();1535 }1536 }1537 },1538 1539 create: function create(func) {1540 var name;1541 1542 if (typeof func != "function") {1543 func = function () { };1544 } else {1545 name = sinon.functionName(func);1546 }1547 1548 var proxy = createProxy(func);1549 1550 sinon.extend(proxy, spy);1551 delete proxy.create;1552 sinon.extend(proxy, func);1553 1554 proxy.reset();1555 proxy.prototype = func.prototype;1556 proxy.displayName = name || "spy";1557 proxy.toString = sinon.functionToString;1558 proxy._create = sinon.spy.create;1559 proxy.id = "spy#" + uuid++;1560 1561 return proxy;1562 },1563 1564 invoke: function invoke(func, thisValue, args) {1565 var matching = matchingFake(this.fakes, args);1566 var exception, returnValue;1567 1568 incrementCallCount.call(this);1569 push.call(this.thisValues, thisValue);1570 push.call(this.args, args);1571 push.call(this.callIds, callId++);1572 1573 try {1574 if (matching) {1575 returnValue = matching.invoke(func, thisValue, args);1576 } else {1577 returnValue = (this.func || func).apply(thisValue, args);1578 }1579 1580 var thisCall = this.getCall(this.callCount - 1);1581 if (thisCall.calledWithNew() && typeof returnValue !== 'object') {1582 returnValue = thisValue;1583 }1584 } catch (e) {1585 exception = e;1586 }1587 1588 push.call(this.exceptions, exception);1589 push.call(this.returnValues, returnValue);1590 1591 createCallProperties.call(this);1592 1593 if (exception !== undefined) {1594 throw exception;1595 }1596 1597 return returnValue;1598 },1599 1600 getCall: function getCall(i) {1601 if (i < 0 || i >= this.callCount) {1602 return null;1603 }1604 1605 return sinon.spyCall(this, this.thisValues[i], this.args[i],1606 this.returnValues[i], this.exceptions[i],1607 this.callIds[i]);1608 },1609 1610 getCalls: function () {1611 var calls = [];1612 var i;1613 1614 for (i = 0; i < this.callCount; i++) {1615 calls.push(this.getCall(i));1616 }1617 1618 return calls;1619 },1620 1621 calledBefore: function calledBefore(spyFn) {1622 if (!this.called) {1623 return false;1624 }1625 1626 if (!spyFn.called) {1627 return true;1628 }1629 1630 return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1];1631 },1632 1633 calledAfter: function calledAfter(spyFn) {1634 if (!this.called || !spyFn.called) {1635 return false;1636 }1637 1638 return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];1639 },1640 1641 withArgs: function () {1642 var args = slice.call(arguments);1643 1644 if (this.fakes) {1645 var match = matchingFake(this.fakes, args, true);1646 1647 if (match) {1648 return match;1649 }1650 } else {1651 this.fakes = [];1652 }1653 1654 var original = this;1655 var fake = this._create();1656 fake.matchingAguments = args;1657 fake.parent = this;1658 push.call(this.fakes, fake);1659 1660 fake.withArgs = function () {1661 return original.withArgs.apply(original, arguments);1662 };1663 1664 for (var i = 0; i < this.args.length; i++) {1665 if (fake.matches(this.args[i])) {1666 incrementCallCount.call(fake);1667 push.call(fake.thisValues, this.thisValues[i]);1668 push.call(fake.args, this.args[i]);1669 push.call(fake.returnValues, this.returnValues[i]);1670 push.call(fake.exceptions, this.exceptions[i]);1671 push.call(fake.callIds, this.callIds[i]);1672 }1673 }1674 createCallProperties.call(fake);1675 1676 return fake;1677 },1678 1679 matches: function (args, strict) {1680 var margs = this.matchingAguments;1681 1682 if (margs.length <= args.length &&1683 sinon.deepEqual(margs, args.slice(0, margs.length))) {1684 return !strict || margs.length == args.length;1685 }1686 },1687 1688 printf: function (format) {1689 var spy = this;1690 var args = slice.call(arguments, 1);1691 var formatter;1692 1693 return (format || "").replace(/%(.)/g, function (match, specifyer) {1694 formatter = spyApi.formatters[specifyer];1695 1696 if (typeof formatter == "function") {1697 return formatter.call(null, spy, args);1698 } else if (!isNaN(parseInt(specifyer, 10))) {1699 return sinon.format(args[specifyer - 1]);1700 }1701 1702 return "%" + specifyer;1703 });1704 }1705 };1706 1707 function delegateToCalls(method, matchAny, actual, notCalled) {1708 spyApi[method] = function () {1709 if (!this.called) {1710 if (notCalled) {1711 return notCalled.apply(this, arguments);1712 }1713 return false;1714 }1715 1716 var currentCall;1717 var matches = 0;1718 1719 for (var i = 0, l = this.callCount; i < l; i += 1) {1720 currentCall = this.getCall(i);1721 1722 if (currentCall[actual || method].apply(currentCall, arguments)) {1723 matches += 1;1724 1725 if (matchAny) {1726 return true;1727 }1728 }1729 }1730 1731 return matches === this.callCount;1732 };1733 }1734 1735 delegateToCalls("calledOn", true);1736 delegateToCalls("alwaysCalledOn", false, "calledOn");1737 delegateToCalls("calledWith", true);1738 delegateToCalls("calledWithMatch", true);1739 delegateToCalls("alwaysCalledWith", false, "calledWith");1740 delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch");1741 delegateToCalls("calledWithExactly", true);1742 delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly");1743 delegateToCalls("neverCalledWith", false, "notCalledWith",1744 function () { return true; });1745 delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch",1746 function () { return true; });1747 delegateToCalls("threw", true);1748 delegateToCalls("alwaysThrew", false, "threw");1749 delegateToCalls("returned", true);1750 delegateToCalls("alwaysReturned", false, "returned");1751 delegateToCalls("calledWithNew", true);1752 delegateToCalls("alwaysCalledWithNew", false, "calledWithNew");1753 delegateToCalls("callArg", false, "callArgWith", function () {1754 throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");1755 });1756 spyApi.callArgWith = spyApi.callArg;1757 delegateToCalls("callArgOn", false, "callArgOnWith", function () {1758 throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");1759 });1760 spyApi.callArgOnWith = spyApi.callArgOn;1761 delegateToCalls("yield", false, "yield", function () {1762 throw new Error(this.toString() + " cannot yield since it was not yet invoked.");1763 });1764 // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.1765 spyApi.invokeCallback = spyApi.yield;1766 delegateToCalls("yieldOn", false, "yieldOn", function () {1767 throw new Error(this.toString() + " cannot yield since it was not yet invoked.");1768 });1769 delegateToCalls("yieldTo", false, "yieldTo", function (property) {1770 throw new Error(this.toString() + " cannot yield to '" + property +1771 "' since it was not yet invoked.");1772 });1773 delegateToCalls("yieldToOn", false, "yieldToOn", function (property) {1774 throw new Error(this.toString() + " cannot yield to '" + property +1775 "' since it was not yet invoked.");1776 });1777 1778 spyApi.formatters = {1779 "c": function (spy) {1780 return sinon.timesInWords(spy.callCount);1781 },1782 1783 "n": function (spy) {1784 return spy.toString();1785 },1786 1787 "C": function (spy) {1788 var calls = [];1789 1790 for (var i = 0, l = spy.callCount; i < l; ++i) {1791 var stringifiedCall = " " + spy.getCall(i).toString();1792 if (/\n/.test(calls[i - 1])) {1793 stringifiedCall = "\n" + stringifiedCall;1794 }1795 push.call(calls, stringifiedCall);1796 }1797 1798 return calls.length > 0 ? "\n" + calls.join("\n") : "";1799 },1800 1801 "t": function (spy) {1802 var objects = [];1803 1804 for (var i = 0, l = spy.callCount; i < l; ++i) {1805 push.call(objects, sinon.format(spy.thisValues[i]));1806 }1807 1808 return objects.join(", ");1809 },1810 1811 "*": function (spy, args) {1812 var formatted = [];1813 1814 for (var i = 0, l = args.length; i < l; ++i) {1815 push.call(formatted, sinon.format(args[i]));1816 }1817 1818 return formatted.join(", ");1819 }1820 };1821 1822 sinon.extend(spy, spyApi);1823 1824 spy.spyCall = sinon.spyCall;1825 1826 if (commonJSModule) {1827 module.exports = spy;1828 } else {1829 sinon.spy = spy;1830 }1831 }(typeof sinon == "object" && sinon || null));1832 1833 /**1834 * @depend ../sinon.js1835 */1836 /*jslint eqeqeq: false, onevar: false*/1837 /*global module, require, sinon, process, setImmediate, setTimeout*/1838 /**1839 * Stub behavior1840 *1841 * @author Christian Johansen (christian@cjohansen.no)1842 * @author Tim Fischbach (mail@timfischbach.de)1843 * @license BSD1844 *1845 * Copyright (c) 2010-2013 Christian Johansen1846 */1847 1848 (function (sinon) {1849 var commonJSModule = typeof module !== 'undefined' && module.exports;1850 1851 if (!sinon && commonJSModule) {1852 sinon = require("../sinon");1853 }1854 1855 if (!sinon) {1856 return;1857 }1858 1859 var slice = Array.prototype.slice;1860 var join = Array.prototype.join;1861 var proto;1862 1863 var nextTick = (function () {1864 if (typeof process === "object" && typeof process.nextTick === "function") {1865 return process.nextTick;1866 } else if (typeof setImmediate === "function") {1867 return setImmediate;1868 } else {1869 return function (callback) {1870 setTimeout(callback, 0);1871 };1872 }1873 })();1874 1875 function throwsException(error, message) {1876 if (typeof error == "string") {1877 this.exception = new Error(message || "");1878 this.exception.name = error;1879 } else if (!error) {1880 this.exception = new Error("Error");1881 } else {1882 this.exception = error;1883 }1884 1885 return this;1886 }1887 1888 function getCallback(behavior, args) {1889 var callArgAt = behavior.callArgAt;1890 1891 if (callArgAt < 0) {1892 var callArgProp = behavior.callArgProp;1893 1894 for (var i = 0, l = args.length; i < l; ++i) {1895 if (!callArgProp && typeof args[i] == "function") {1896 return args[i];1897 }1898 1899 if (callArgProp && args[i] &&1900 typeof args[i][callArgProp] == "function") {1901 return args[i][callArgProp];1902 }1903 }1904 1905 return null;1906 }1907 1908 return args[callArgAt];1909 }1910 1911 function getCallbackError(behavior, func, args) {1912 if (behavior.callArgAt < 0) {1913 var msg;1914 1915 if (behavior.callArgProp) {1916 msg = sinon.functionName(behavior.stub) +1917 " expected to yield to '" + behavior.callArgProp +1918 "', but no object with such a property was passed.";1919 } else {1920 msg = sinon.functionName(behavior.stub) +1921 " expected to yield, but no callback was passed.";1922 }1923 1924 if (args.length > 0) {1925 msg += " Received [" + join.call(args, ", ") + "]";1926 }1927 1928 return msg;1929 }1930 1931 return "argument at index " + behavior.callArgAt + " is not a function: " + func;1932 }1933 1934 function callCallback(behavior, args) {1935 if (typeof behavior.callArgAt == "number") {1936 var func = getCallback(behavior, args);1937 1938 if (typeof func != "function") {1939 throw new TypeError(getCallbackError(behavior, func, args));1940 }1941 1942 if (behavior.callbackAsync) {1943 nextTick(function() {1944 func.apply(behavior.callbackContext, behavior.callbackArguments);1945 });1946 } else {1947 func.apply(behavior.callbackContext, behavior.callbackArguments);1948 }1949 }1950 }1951 1952 proto = {1953 create: function(stub) {1954 var behavior = sinon.extend({}, sinon.behavior);1955 delete behavior.create;1956 behavior.stub = stub;1957 1958 return behavior;1959 },1960 1961 isPresent: function() {1962 return (typeof this.callArgAt == 'number' ||1963 this.exception ||1964 typeof this.returnArgAt == 'number' ||1965 this.returnThis ||1966 this.returnValueDefined);1967 },1968 1969 invoke: function(context, args) {1970 callCallback(this, args);1971 1972 if (this.exception) {1973 throw this.exception;1974 } else if (typeof this.returnArgAt == 'number') {1975 return args[this.returnArgAt];1976 } else if (this.returnThis) {1977 return context;1978 }1979 1980 return this.returnValue;1981 },1982 1983 onCall: function(index) {1984 return this.stub.onCall(index);1985 },1986 1987 onFirstCall: function() {1988 return this.stub.onFirstCall();1989 },1990 1991 onSecondCall: function() {1992 return this.stub.onSecondCall();1993 },1994 1995 onThirdCall: function() {1996 return this.stub.onThirdCall();1997 },1998 1999 withArgs: function(/* arguments */) {2000 throw new Error('Defining a stub by invoking "stub.onCall(...).withArgs(...)" is not supported. ' +2001 'Use "stub.withArgs(...).onCall(...)" to define sequential behavior for calls with certain arguments.');2002 },2003 2004 callsArg: function callsArg(pos) {2005 if (typeof pos != "number") {2006 throw new TypeError("argument index is not number");2007 }2008 2009 this.callArgAt = pos;2010 this.callbackArguments = [];2011 this.callbackContext = undefined;2012 this.callArgProp = undefined;2013 this.callbackAsync = false;2014 2015 return this;2016 },2017 2018 callsArgOn: function callsArgOn(pos, context) {2019 if (typeof pos != "number") {2020 throw new TypeError("argument index is not number");2021 }2022 if (typeof context != "object") {2023 throw new TypeError("argument context is not an object");2024 }2025 2026 this.callArgAt = pos;2027 this.callbackArguments = [];2028 this.callbackContext = context;2029 this.callArgProp = undefined;2030 this.callbackAsync = false;2031 2032 return this;2033 },2034 2035 callsArgWith: function callsArgWith(pos) {2036 if (typeof pos != "number") {2037 throw new TypeError("argument index is not number");2038 }2039 2040 this.callArgAt = pos;2041 this.callbackArguments = slice.call(arguments, 1);2042 this.callbackContext = undefined;2043 this.callArgProp = undefined;2044 this.callbackAsync = false;2045 2046 return this;2047 },2048 2049 callsArgOnWith: function callsArgWith(pos, context) {2050 if (typeof pos != "number") {2051 throw new TypeError("argument index is not number");2052 }2053 if (typeof context != "object") {2054 throw new TypeError("argument context is not an object");2055 }2056 2057 this.callArgAt = pos;2058 this.callbackArguments = slice.call(arguments, 2);2059 this.callbackContext = context;2060 this.callArgProp = undefined;2061 this.callbackAsync = false;2062 2063 return this;2064 },2065 2066 yields: function () {2067 this.callArgAt = -1;2068 this.callbackArguments = slice.call(arguments, 0);2069 this.callbackContext = undefined;2070 this.callArgProp = undefined;2071 this.callbackAsync = false;2072 2073 return this;2074 },2075 2076 yieldsOn: function (context) {2077 if (typeof context != "object") {2078 throw new TypeError("argument context is not an object");2079 }2080 2081 this.callArgAt = -1;2082 this.callbackArguments = slice.call(arguments, 1);2083 this.callbackContext = context;2084 this.callArgProp = undefined;2085 this.callbackAsync = false;2086 2087 return this;2088 },2089 2090 yieldsTo: function (prop) {2091 this.callArgAt = -1;2092 this.callbackArguments = slice.call(arguments, 1);2093 this.callbackContext = undefined;2094 this.callArgProp = prop;2095 this.callbackAsync = false;2096 2097 return this;2098 },2099 2100 yieldsToOn: function (prop, context) {2101 if (typeof context != "object") {2102 throw new TypeError("argument context is not an object");2103 }2104 2105 this.callArgAt = -1;2106 this.callbackArguments = slice.call(arguments, 2);2107 this.callbackContext = context;2108 this.callArgProp = prop;2109 this.callbackAsync = false;2110 2111 return this;2112 },2113 2114 2115 "throws": throwsException,2116 throwsException: throwsException,2117 2118 returns: function returns(value) {2119 this.returnValue = value;2120 this.returnValueDefined = true;2121 2122 return this;2123 },2124 2125 returnsArg: function returnsArg(pos) {2126 if (typeof pos != "number") {2127 throw new TypeError("argument index is not number");2128 }2129 2130 this.returnArgAt = pos;2131 2132 return this;2133 },2134 2135 returnsThis: function returnsThis() {2136 this.returnThis = true;2137 2138 return this;2139 }2140 };2141 2142 // create asynchronous versions of callsArg* and yields* methods2143 for (var method in proto) {2144 // need to avoid creating anotherasync versions of the newly added async methods2145 if (proto.hasOwnProperty(method) &&2146 method.match(/^(callsArg|yields)/) &&2147 !method.match(/Async/)) {2148 proto[method + 'Async'] = (function (syncFnName) {2149 return function () {2150 var result = this[syncFnName].apply(this, arguments);2151 this.callbackAsync = true;2152 return result;2153 };2154 })(method);2155 }2156 }2157 2158 if (commonJSModule) {2159 module.exports = proto;2160 } else {2161 sinon.behavior = proto;2162 }2163 }(typeof sinon == "object" && sinon || null));2164 /**2165 * @depend ../sinon.js2166 * @depend spy.js2167 * @depend behavior.js2168 */2169 /*jslint eqeqeq: false, onevar: false*/2170 /*global module, require, sinon*/2171 /**2172 * Stub functions2173 *2174 * @author Christian Johansen (christian@cjohansen.no)2175 * @license BSD2176 *2177 * Copyright (c) 2010-2013 Christian Johansen2178 */2179 2180 (function (sinon) {2181 var commonJSModule = typeof module !== 'undefined' && module.exports;2182 2183 if (!sinon && commonJSModule) {2184 sinon = require("../sinon");2185 }2186 2187 if (!sinon) {2188 return;2189 }2190 2191 function stub(object, property, func) {2192 if (!!func && typeof func != "function") {2193 throw new TypeError("Custom stub should be function");2194 }2195 2196 var wrapper;2197 2198 if (func) {2199 wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;2200 } else {2201 wrapper = stub.create();2202 }2203 2204 if (!object && typeof property === "undefined") {2205 return sinon.stub.create();2206 }2207 2208 if (typeof property === "undefined" && typeof object == "object") {2209 for (var prop in object) {2210 if (typeof object[prop] === "function") {2211 stub(object, prop);2212 }2213 }2214 2215 return object;2216 }2217 2218 return sinon.wrapMethod(object, property, wrapper);2219 }2220 2221 function getDefaultBehavior(stub) {2222 return stub.defaultBehavior || getParentBehaviour(stub) || sinon.behavior.create(stub);2223 }2224 2225 function getParentBehaviour(stub) {2226 return (stub.parent && getCurrentBehavior(stub.parent));2227 }2228 2229 function getCurrentBehavior(stub) {2230 var behavior = stub.behaviors[stub.callCount - 1];2231 return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stub);2232 }2233 2234 var uuid = 0;2235 2236 sinon.extend(stub, (function () {2237 var proto = {2238 create: function create() {2239 var functionStub = function () {2240 return getCurrentBehavior(functionStub).invoke(this, arguments);2241 };2242 2243 functionStub.id = "stub#" + uuid++;2244 var orig = functionStub;2245 functionStub = sinon.spy.create(functionStub);2246 functionStub.func = orig;2247 2248 sinon.extend(functionStub, stub);2249 functionStub._create = sinon.stub.create;2250 functionStub.displayName = "stub";2251 functionStub.toString = sinon.functionToString;2252 2253 functionStub.defaultBehavior = null;2254 functionStub.behaviors = [];2255 2256 return functionStub;2257 },2258 2259 resetBehavior: function () {2260 var i;2261 2262 this.defaultBehavior = null;2263 this.behaviors = [];2264 2265 delete this.returnValue;2266 delete this.returnArgAt;2267 this.returnThis = false;2268 2269 if (this.fakes) {2270 for (i = 0; i < this.fakes.length; i++) {2271 this.fakes[i].resetBehavior();2272 }2273 }2274 },2275 2276 onCall: function(index) {2277 if (!this.behaviors[index]) {2278 this.behaviors[index] = sinon.behavior.create(this);2279 }2280 2281 return this.behaviors[index];2282 },2283 2284 onFirstCall: function() {2285 return this.onCall(0);2286 },2287 2288 onSecondCall: function() {2289 return this.onCall(1);2290 },2291 2292 onThirdCall: function() {2293 return this.onCall(2);2294 }2295 };2296 2297 for (var method in sinon.behavior) {2298 if (sinon.behavior.hasOwnProperty(method) &&2299 !proto.hasOwnProperty(method) &&2300 method != 'create' &&2301 method != 'withArgs' &&2302 method != 'invoke') {2303 proto[method] = (function(behaviorMethod) {2304 return function() {2305 this.defaultBehavior = this.defaultBehavior || sinon.behavior.create(this);2306 this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments);2307 return this;2308 };2309 }(method));2310 }2311 }2312 2313 return proto;2314 }()));2315 2316 if (commonJSModule) {2317 module.exports = stub;2318 } else {2319 sinon.stub = stub;2320 }2321 }(typeof sinon == "object" && sinon || null));2322 2323 /**2324 * @depend ../sinon.js2325 * @depend stub.js2326 */2327 /*jslint eqeqeq: false, onevar: false, nomen: false*/2328 /*global module, require, sinon*/2329 /**2330 * Mock functions.2331 *2332 * @author Christian Johansen (christian@cjohansen.no)2333 * @license BSD2334 *2335 * Copyright (c) 2010-2013 Christian Johansen2336 */2337 2338 (function (sinon) {2339 var commonJSModule = typeof module !== 'undefined' && module.exports;2340 var push = [].push;2341 var match;2342 2343 if (!sinon && commonJSModule) {2344 sinon = require("../sinon");2345 }2346 2347 if (!sinon) {2348 return;2349 }2350 2351 match = sinon.match;2352 2353 if (!match && commonJSModule) {2354 match = require("./match");2355 }2356 2357 function mock(object) {2358 if (!object) {2359 return sinon.expectation.create("Anonymous mock");2360 }2361 2362 return mock.create(object);2363 }2364 2365 sinon.mock = mock;2366 2367 sinon.extend(mock, (function () {2368 function each(collection, callback) {2369 if (!collection) {2370 return;2371 }2372 2373 for (var i = 0, l = collection.length; i < l; i += 1) {2374 callback(collection[i]);2375 }2376 }2377 2378 return {2379 create: function create(object) {2380 if (!object) {2381 throw new TypeError("object is null");2382 }2383 2384 var mockObject = sinon.extend({}, mock);2385 mockObject.object = object;2386 delete mockObject.create;2387 2388 return mockObject;2389 },2390 2391 expects: function expects(method) {2392 if (!method) {2393 throw new TypeError("method is falsy");2394 }2395 2396 if (!this.expectations) {2397 this.expectations = {};2398 this.proxies = [];2399 }2400 2401 if (!this.expectations[method]) {2402 this.expectations[method] = [];2403 var mockObject = this;2404 2405 sinon.wrapMethod(this.object, method, function () {2406 return mockObject.invokeMethod(method, this, arguments);2407 });2408 2409 push.call(this.proxies, method);2410 }2411 2412 var expectation = sinon.expectation.create(method);2413 push.call(this.expectations[method], expectation);2414 2415 return expectation;2416 },2417 2418 restore: function restore() {2419 var object = this.object;2420 2421 each(this.proxies, function (proxy) {2422 if (typeof object[proxy].restore == "function") {2423 object[proxy].restore();2424 }2425 });2426 },2427 2428 verify: function verify() {2429 var expectations = this.expectations || {};2430 var messages = [], met = [];2431 2432 each(this.proxies, function (proxy) {2433 each(expectations[proxy], function (expectation) {2434 if (!expectation.met()) {2435 push.call(messages, expectation.toString());2436 } else {2437 push.call(met, expectation.toString());2438 }2439 });2440 });2441 2442 this.restore();2443 2444 if (messages.length > 0) {2445 sinon.expectation.fail(messages.concat(met).join("\n"));2446 } else {2447 sinon.expectation.pass(messages.concat(met).join("\n"));2448 }2449 2450 return true;2451 },2452 2453 invokeMethod: function invokeMethod(method, thisValue, args) {2454 var expectations = this.expectations && this.expectations[method];2455 var length = expectations && expectations.length || 0, i;2456 2457 for (i = 0; i < length; i += 1) {2458 if (!expectations[i].met() &&2459 expectations[i].allowsCall(thisValue, args)) {2460 return expectations[i].apply(thisValue, args);2461 }2462 }2463 2464 var messages = [], available, exhausted = 0;2465 2466 for (i = 0; i < length; i += 1) {2467 if (expectations[i].allowsCall(thisValue, args)) {2468 available = available || expectations[i];2469 } else {2470 exhausted += 1;2471 }2472 push.call(messages, " " + expectations[i].toString());2473 }2474 2475 if (exhausted === 0) {2476 return available.apply(thisValue, args);2477 }2478 2479 messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({2480 proxy: method,2481 args: args2482 }));2483 2484 sinon.expectation.fail(messages.join("\n"));2485 }2486 };2487 }()));2488 2489 var times = sinon.timesInWords;2490 2491 sinon.expectation = (function () {2492 var slice = Array.prototype.slice;2493 var _invoke = sinon.spy.invoke;2494 2495 function callCountInWords(callCount) {2496 if (callCount == 0) {2497 return "never called";2498 } else {2499 return "called " + times(callCount);2500 }2501 }2502 2503 function expectedCallCountInWords(expectation) {2504 var min = expectation.minCalls;2505 var max = expectation.maxCalls;2506 2507 if (typeof min == "number" && typeof max == "number") {2508 var str = times(min);2509 2510 if (min != max) {2511 str = "at least " + str + " and at most " + times(max);2512 }2513 2514 return str;2515 }2516 2517 if (typeof min == "number") {2518 return "at least " + times(min);2519 }2520 2521 return "at most " + times(max);2522 }2523 2524 function receivedMinCalls(expectation) {2525 var hasMinLimit = typeof expectation.minCalls == "number";2526 return !hasMinLimit || expectation.callCount >= expectation.minCalls;2527 }2528 2529 function receivedMaxCalls(expectation) {2530 if (typeof expectation.maxCalls != "number") {2531 return false;2532 }2533 2534 return expectation.callCount == expectation.maxCalls;2535 }2536 2537 function verifyMatcher(possibleMatcher, arg){2538 if (match && match.isMatcher(possibleMatcher)) {2539 return possibleMatcher.test(arg);2540 } else {2541 return true;2542 }2543 }2544 2545 return {2546 minCalls: 1,2547 maxCalls: 1,2548 2549 create: function create(methodName) {2550 var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);2551 delete expectation.create;2552 expectation.method = methodName;2553 2554 return expectation;2555 },2556 2557 invoke: function invoke(func, thisValue, args) {2558 this.verifyCallAllowed(thisValue, args);2559 2560 return _invoke.apply(this, arguments);2561 },2562 2563 atLeast: function atLeast(num) {2564 if (typeof num != "number") {2565 throw new TypeError("'" + num + "' is not number");2566 }2567 2568 if (!this.limitsSet) {2569 this.maxCalls = null;2570 this.limitsSet = true;2571 }2572 2573 this.minCalls = num;2574 2575 return this;2576 },2577 2578 atMost: function atMost(num) {2579 if (typeof num != "number") {2580 throw new TypeError("'" + num + "' is not number");2581 }2582 2583 if (!this.limitsSet) {2584 this.minCalls = null;2585 this.limitsSet = true;2586 }2587 2588 this.maxCalls = num;2589 2590 return this;2591 },2592 2593 never: function never() {2594 return this.exactly(0);2595 },2596 2597 once: function once() {2598 return this.exactly(1);2599 },2600 2601 twice: function twice() {2602 return this.exactly(2);2603 },2604 2605 thrice: function thrice() {2606 return this.exactly(3);2607 },2608 2609 exactly: function exactly(num) {2610 if (typeof num != "number") {2611 throw new TypeError("'" + num + "' is not a number");2612 }2613 2614 this.atLeast(num);2615 return this.atMost(num);2616 },2617 2618 met: function met() {2619 return !this.failed && receivedMinCalls(this);2620 },2621 2622 verifyCallAllowed: function verifyCallAllowed(thisValue, args) {2623 if (receivedMaxCalls(this)) {2624 this.failed = true;2625 sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));2626 }2627 2628 if ("expectedThis" in this && this.expectedThis !== thisValue) {2629 sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +2630 this.expectedThis);2631 }2632 2633 if (!("expectedArguments" in this)) {2634 return;2635 }2636 2637 if (!args) {2638 sinon.expectation.fail(this.method + " received no arguments, expected " +2639 sinon.format(this.expectedArguments));2640 }2641 2642 if (args.length < this.expectedArguments.length) {2643 sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) +2644 "), expected " + sinon.format(this.expectedArguments));2645 }2646 2647 if (this.expectsExactArgCount &&2648 args.length != this.expectedArguments.length) {2649 sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) +2650 "), expected " + sinon.format(this.expectedArguments));2651 }2652 2653 for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {2654 2655 if (!verifyMatcher(this.expectedArguments[i],args[i])) {2656 sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +2657 ", didn't match " + this.expectedArguments.toString());2658 }2659 2660 if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {2661 sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +2662 ", expected " + sinon.format(this.expectedArguments));2663 }2664 }2665 },2666 2667 allowsCall: function allowsCall(thisValue, args) {2668 if (this.met() && receivedMaxCalls(this)) {2669 return false;2670 }2671 2672 if ("expectedThis" in this && this.expectedThis !== thisValue) {2673 return false;2674 }2675 2676 if (!("expectedArguments" in this)) {2677 return true;2678 }2679 2680 args = args || [];2681 2682 if (args.length < this.expectedArguments.length) {2683 return false;2684 }2685 2686 if (this.expectsExactArgCount &&2687 args.length != this.expectedArguments.length) {2688 return false;2689 }2690 2691 for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {2692 if (!verifyMatcher(this.expectedArguments[i],args[i])) {2693 return false;2694 }2695 2696 if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {2697 return false;2698 }2699 }2700 2701 return true;2702 },2703 2704 withArgs: function withArgs() {2705 this.expectedArguments = slice.call(arguments);2706 return this;2707 },2708 2709 withExactArgs: function withExactArgs() {2710 this.withArgs.apply(this, arguments);2711 this.expectsExactArgCount = true;2712 return this;2713 },2714 2715 on: function on(thisValue) {2716 this.expectedThis = thisValue;2717 return this;2718 },2719 2720 toString: function () {2721 var args = (this.expectedArguments || []).slice();2722 2723 if (!this.expectsExactArgCount) {2724 push.call(args, "[...]");2725 }2726 2727 var callStr = sinon.spyCall.toString.call({2728 proxy: this.method || "anonymous mock expectation",2729 args: args2730 });2731 2732 var message = callStr.replace(", [...", "[, ...") + " " +2733 expectedCallCountInWords(this);2734 2735 if (this.met()) {2736 return "Expectation met: " + message;2737 }2738 2739 return "Expected " + message + " (" +2740 callCountInWords(this.callCount) + ")";2741 },2742 2743 verify: function verify() {2744 if (!this.met()) {2745 sinon.expectation.fail(this.toString());2746 } else {2747 sinon.expectation.pass(this.toString());2748 }2749 2750 return true;2751 },2752 2753 pass: function(message) {2754 sinon.assert.pass(message);2755 },2756 fail: function (message) {2757 var exception = new Error(message);2758 exception.name = "ExpectationError";2759 2760 throw exception;2761 }2762 };2763 }());2764 2765 if (commonJSModule) {2766 module.exports = mock;2767 } else {2768 sinon.mock = mock;2769 }2770 }(typeof sinon == "object" && sinon || null));2771 2772 /**2773 * @depend ../sinon.js2774 * @depend stub.js2775 * @depend mock.js2776 */2777 /*jslint eqeqeq: false, onevar: false, forin: true*/2778 /*global module, require, sinon*/2779 /**2780 * Collections of stubs, spies and mocks.2781 *2782 * @author Christian Johansen (christian@cjohansen.no)2783 * @license BSD2784 *2785 * Copyright (c) 2010-2013 Christian Johansen2786 */2787 2788 (function (sinon) {2789 var commonJSModule = typeof module !== 'undefined' && module.exports;2790 var push = [].push;2791 var hasOwnProperty = Object.prototype.hasOwnProperty;2792 2793 if (!sinon && commonJSModule) {2794 sinon = require("../sinon");2795 }2796 2797 if (!sinon) {2798 return;2799 }2800 2801 function getFakes(fakeCollection) {2802 if (!fakeCollection.fakes) {2803 fakeCollection.fakes = [];2804 }2805 2806 return fakeCollection.fakes;2807 }2808 2809 function each(fakeCollection, method) {2810 var fakes = getFakes(fakeCollection);2811 2812 for (var i = 0, l = fakes.length; i < l; i += 1) {2813 if (typeof fakes[i][method] == "function") {2814 fakes[i][method]();2815 }2816 }2817 }2818 2819 function compact(fakeCollection) {2820 var fakes = getFakes(fakeCollection);2821 var i = 0;2822 while (i < fakes.length) {2823 fakes.splice(i, 1);2824 }2825 }2826 2827 var collection = {2828 verify: function resolve() {2829 each(this, "verify");2830 },2831 2832 restore: function restore() {2833 each(this, "restore");2834 compact(this);2835 },2836 2837 verifyAndRestore: function verifyAndRestore() {2838 var exception;2839 2840 try {2841 this.verify();2842 } catch (e) {2843 exception = e;2844 }2845 2846 this.restore();2847 2848 if (exception) {2849 throw exception;2850 }2851 },2852 2853 add: function add(fake) {2854 push.call(getFakes(this), fake);2855 return fake;2856 },2857 2858 spy: function spy() {2859 return this.add(sinon.spy.apply(sinon, arguments));2860 },2861 2862 stub: function stub(object, property, value) {2863 if (property) {2864 var original = object[property];2865 2866 if (typeof original != "function") {2867 if (!hasOwnProperty.call(object, property)) {2868 throw new TypeError("Cannot stub non-existent own property " + property);2869 }2870 2871 object[property] = value;2872 2873 return this.add({2874 restore: function () {2875 object[property] = original;2876 }2877 });2878 }2879 }2880 if (!property && !!object && typeof object == "object") {2881 var stubbedObj = sinon.stub.apply(sinon, arguments);2882 2883 for (var prop in stubbedObj) {2884 if (typeof stubbedObj[prop] === "function") {2885 this.add(stubbedObj[prop]);2886 }2887 }2888 2889 return stubbedObj;2890 }2891 2892 return this.add(sinon.stub.apply(sinon, arguments));2893 },2894 2895 mock: function mock() {2896 return this.add(sinon.mock.apply(sinon, arguments));2897 },2898 2899 inject: function inject(obj) {2900 var col = this;2901 2902 obj.spy = function () {2903 return col.spy.apply(col, arguments);2904 };2905 2906 obj.stub = function () {2907 return col.stub.apply(col, arguments);2908 };2909 2910 obj.mock = function () {2911 return col.mock.apply(col, arguments);2912 };2913 2914 return obj;2915 }2916 };2917 2918 if (commonJSModule) {2919 module.exports = collection;2920 } else {2921 sinon.collection = collection;2922 }2923 }(typeof sinon == "object" && sinon || null));2924 2925 /*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/2926 /*global module, require, window*/2927 /**2928 * Fake timer API2929 * setTimeout2930 * setInterval2931 * clearTimeout2932 * clearInterval2933 * tick2934 * reset2935 * Date2936 *2937 * Inspired by jsUnitMockTimeOut from JsUnit2938 *2939 * @author Christian Johansen (christian@cjohansen.no)2940 * @license BSD2941 *2942 * Copyright (c) 2010-2013 Christian Johansen2943 */2944 2945 if (typeof sinon == "undefined") {2946 var sinon = {};2947 }2948 2949 (function (global) {2950 var id = 1;2951 2952 function addTimer(args, recurring) {2953 if (args.length === 0) {2954 throw new Error("Function requires at least 1 parameter");2955 }2956 2957 if (typeof args[0] === "undefined") {2958 throw new Error("Callback must be provided to timer calls");2959 }2960 2961 var toId = id++;2962 var delay = args[1] || 0;2963 2964 if (!this.timeouts) {2965 this.timeouts = {};2966 }2967 2968 this.timeouts[toId] = {2969 id: toId,2970 func: args[0],2971 callAt: this.now + delay,2972 invokeArgs: Array.prototype.slice.call(args, 2)2973 };2974 2975 if (recurring === true) {2976 this.timeouts[toId].interval = delay;2977 }2978 2979 return toId;2980 }2981 2982 function parseTime(str) {2983 if (!str) {2984 return 0;2985 }2986 2987 var strings = str.split(":");2988 var l = strings.length, i = l;2989 var ms = 0, parsed;2990 2991 if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {2992 throw new Error("tick only understands numbers and 'h:m:s'");2993 }2994 2995 while (i--) {2996 parsed = parseInt(strings[i], 10);2997 2998 if (parsed >= 60) {2999 throw new Error("Invalid time " + str);3000 }3001 3002 ms += parsed * Math.pow(60, (l - i - 1));3003 }3004 3005 return ms * 1000;3006 }3007 3008 function createObject(object) {3009 var newObject;3010 3011 if (Object.create) {3012 newObject = Object.create(object);3013 } else {3014 var F = function () {};3015 F.prototype = object;3016 newObject = new F();3017 }3018 3019 newObject.Date.clock = newObject;3020 return newObject;3021 }3022 3023 sinon.clock = {3024 now: 0,3025 3026 create: function create(now) {3027 var clock = createObject(this);3028 3029 if (typeof now == "number") {3030 clock.now = now;3031 }3032 3033 if (!!now && typeof now == "object") {3034 throw new TypeError("now should be milliseconds since UNIX epoch");3035 }3036 3037 return clock;3038 },3039 3040 setTimeout: function setTimeout(callback, timeout) {3041 return addTimer.call(this, arguments, false);3042 },3043 3044 clearTimeout: function clearTimeout(timerId) {3045 if (!this.timeouts) {3046 this.timeouts = [];3047 }3048 3049 if (timerId in this.timeouts) {3050 delete this.timeouts[timerId];3051 }3052 },3053 3054 setInterval: function setInterval(callback, timeout) {3055 return addTimer.call(this, arguments, true);3056 },3057 3058 clearInterval: function clearInterval(timerId) {3059 this.clearTimeout(timerId);3060 },3061 3062 setImmediate: function setImmediate(callback) {3063 var passThruArgs = Array.prototype.slice.call(arguments, 1);3064 3065 return addTimer.call(this, [callback, 0].concat(passThruArgs), false);3066 },3067 3068 clearImmediate: function clearImmediate(timerId) {3069 this.clearTimeout(timerId);3070 },3071 3072 tick: function tick(ms) {3073 ms = typeof ms == "number" ? ms : parseTime(ms);3074 var tickFrom = this.now, tickTo = this.now + ms, previous = this.now;3075 var timer = this.firstTimerInRange(tickFrom, tickTo);3076 3077 var firstException;3078 while (timer && tickFrom <= tickTo) {3079 if (this.timeouts[timer.id]) {3080 tickFrom = this.now = timer.callAt;3081 try {3082 this.callTimer(timer);3083 } catch (e) {3084 firstException = firstException || e;3085 }3086 }3087 3088 timer = this.firstTimerInRange(previous, tickTo);3089 previous = tickFrom;3090 }3091 3092 this.now = tickTo;3093 3094 if (firstException) {3095 throw firstException;3096 }3097 3098 return this.now;3099 },3100 3101 firstTimerInRange: function (from, to) {3102 var timer, smallest = null, originalTimer;3103 3104 for (var id in this.timeouts) {3105 if (this.timeouts.hasOwnProperty(id)) {3106 if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) {3107 continue;3108 }3109 3110 if (smallest === null || this.timeouts[id].callAt < smallest) {3111 originalTimer = this.timeouts[id];3112 smallest = this.timeouts[id].callAt;3113 3114 timer = {3115 func: this.timeouts[id].func,3116 callAt: this.timeouts[id].callAt,3117 interval: this.timeouts[id].interval,3118 id: this.timeouts[id].id,3119 invokeArgs: this.timeouts[id].invokeArgs3120 };3121 }3122 }3123 }3124 3125 return timer || null;3126 },3127 3128 callTimer: function (timer) {3129 if (typeof timer.interval == "number") {3130 this.timeouts[timer.id].callAt += timer.interval;3131 } else {3132 delete this.timeouts[timer.id];3133 }3134 3135 try {3136 if (typeof timer.func == "function") {3137 timer.func.apply(null, timer.invokeArgs);3138 } else {3139 eval(timer.func);3140 }3141 } catch (e) {3142 var exception = e;3143 }3144 3145 if (!this.timeouts[timer.id]) {3146 if (exception) {3147 throw exception;3148 }3149 return;3150 }3151 3152 if (exception) {3153 throw exception;3154 }3155 },3156 3157 reset: function reset() {3158 this.timeouts = {};3159 },3160 3161 Date: (function () {3162 var NativeDate = Date;3163 3164 function ClockDate(year, month, date, hour, minute, second, ms) {3165 // Defensive and verbose to avoid potential harm in passing3166 // explicit undefined when user does not pass argument3167 switch (arguments.length) {3168 case 0:3169 return new NativeDate(ClockDate.clock.now);3170 case 1:3171 return new NativeDate(year);3172 case 2:3173 return new NativeDate(year, month);3174 case 3:3175 return new NativeDate(year, month, date);3176 case 4:3177 return new NativeDate(year, month, date, hour);3178 case 5:3179 return new NativeDate(year, month, date, hour, minute);3180 case 6:3181 return new NativeDate(year, month, date, hour, minute, second);3182 default:3183 return new NativeDate(year, month, date, hour, minute, second, ms);3184 }3185 }3186 3187 return mirrorDateProperties(ClockDate, NativeDate);3188 }())3189 };3190 3191 function mirrorDateProperties(target, source) {3192 if (source.now) {3193 target.now = function now() {3194 return target.clock.now;3195 };3196 } else {3197 delete target.now;3198 }3199 3200 if (source.toSource) {3201 target.toSource = function toSource() {3202 return source.toSource();3203 };3204 } else {3205 delete target.toSource;3206 }3207 3208 target.toString = function toString() {3209 return source.toString();3210 };3211 3212 target.prototype = source.prototype;3213 target.parse = source.parse;3214 target.UTC = source.UTC;3215 target.prototype.toUTCString = source.prototype.toUTCString;3216 3217 for (var prop in source) {3218 if (source.hasOwnProperty(prop)) {3219 target[prop] = source[prop];3220 }3221 }3222 3223 return target;3224 }3225 3226 var methods = ["Date", "setTimeout", "setInterval",3227 "clearTimeout", "clearInterval"];3228 3229 if (typeof global.setImmediate !== "undefined") {3230 methods.push("setImmediate");3231 }3232 3233 if (typeof global.clearImmediate !== "undefined") {3234 methods.push("clearImmediate");3235 }3236 3237 function restore() {3238 var method;3239 3240 for (var i = 0, l = this.methods.length; i < l; i++) {3241 method = this.methods[i];3242 3243 if (global[method].hadOwnProperty) {3244 global[method] = this["_" + method];3245 } else {3246 try {3247 delete global[method];3248 } catch (e) {}3249 }3250 }3251 3252 // Prevent multiple executions which will completely remove these props3253 this.methods = [];3254 }3255 3256 function stubGlobal(method, clock) {3257 clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method);3258 clock["_" + method] = global[method];3259 3260 if (method == "Date") {3261 var date = mirrorDateProperties(clock[method], global[method]);3262 global[method] = date;3263 } else {3264 global[method] = function () {3265 return clock[method].apply(clock, arguments);3266 };3267 3268 for (var prop in clock[method]) {3269 if (clock[method].hasOwnProperty(prop)) {3270 global[method][prop] = clock[method][prop];3271 }3272 }3273 }3274 3275 global[method].clock = clock;3276 }3277 3278 sinon.useFakeTimers = function useFakeTimers(now) {3279 var clock = sinon.clock.create(now);3280 clock.restore = restore;3281 clock.methods = Array.prototype.slice.call(arguments,3282 typeof now == "number" ? 1 : 0);3283 3284 if (clock.methods.length === 0) {3285 clock.methods = methods;3286 }3287 3288 for (var i = 0, l = clock.methods.length; i < l; i++) {3289 stubGlobal(clock.methods[i], clock);3290 }3291 3292 return clock;3293 };3294 }(typeof global != "undefined" && typeof global !== "function" ? global : this));3295 3296 sinon.timers = {3297 setTimeout: setTimeout,3298 clearTimeout: clearTimeout,3299 setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined),3300 clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate: undefined),3301 setInterval: setInterval,3302 clearInterval: clearInterval,3303 Date: Date3304 };3305 3306 if (typeof module !== 'undefined' && module.exports) {3307 module.exports = sinon;3308 }3309 3310 /*jslint eqeqeq: false, onevar: false*/3311 /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/3312 /**3313 * Minimal Event interface implementation3314 *3315 * Original implementation by Sven Fuchs: https://gist.github.com/9950283316 * Modifications and tests by Christian Johansen.3317 *3318 * @author Sven Fuchs (svenfuchs@artweb-design.de)3319 * @author Christian Johansen (christian@cjohansen.no)3320 * @license BSD3321 *3322 * Copyright (c) 2011 Sven Fuchs, Christian Johansen3323 */3324 3325 if (typeof sinon == "undefined") {3326 this.sinon = {};3327 }3328 3329 (function () {3330 var push = [].push;3331 3332 sinon.Event = function Event(type, bubbles, cancelable, target) {3333 this.initEvent(type, bubbles, cancelable, target);3334 };3335 3336 sinon.Event.prototype = {3337 initEvent: function(type, bubbles, cancelable, target) {3338 this.type = type;3339 this.bubbles = bubbles;3340 this.cancelable = cancelable;3341 this.target = target;3342 },3343 3344 stopPropagation: function () {},3345 3346 preventDefault: function () {3347 this.defaultPrevented = true;3348 }3349 };3350 3351 sinon.EventTarget = {3352 addEventListener: function addEventListener(event, listener) {3353 this.eventListeners = this.eventListeners || {};3354 this.eventListeners[event] = this.eventListeners[event] || [];3355 push.call(this.eventListeners[event], listener);3356 },3357 3358 removeEventListener: function removeEventListener(event, listener) {3359 var listeners = this.eventListeners && this.eventListeners[event] || [];3360 3361 for (var i = 0, l = listeners.length; i < l; ++i) {3362 if (listeners[i] == listener) {3363 return listeners.splice(i, 1);3364 }3365 }3366 },3367 3368 dispatchEvent: function dispatchEvent(event) {3369 var type = event.type;3370 var listeners = this.eventListeners && this.eventListeners[type] || [];3371 3372 for (var i = 0; i < listeners.length; i++) {3373 if (typeof listeners[i] == "function") {3374 listeners[i].call(this, event);3375 } else {3376 listeners[i].handleEvent(event);3377 }3378 }3379 3380 return !!event.defaultPrevented;3381 }3382 };3383 }());3384 3385 /**3386 * @depend ../../sinon.js3387 * @depend event.js3388 */3389 /*jslint eqeqeq: false, onevar: false*/3390 /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/3391 /**3392 * Fake XMLHttpRequest object3393 *3394 * @author Christian Johansen (christian@cjohansen.no)3395 * @license BSD3396 *3397 * Copyright (c) 2010-2013 Christian Johansen3398 */3399 3400 // wrapper for global3401 (function(global) {3402 if (typeof sinon === "undefined") {3403 global.sinon = {};3404 }3405 3406 var supportsProgress = typeof ProgressEvent !== "undefined";3407 var supportsCustomEvent = typeof CustomEvent !== "undefined";3408 sinon.xhr = { XMLHttpRequest: global.XMLHttpRequest };3409 var xhr = sinon.xhr;3410 xhr.GlobalXMLHttpRequest = global.XMLHttpRequest;3411 xhr.GlobalActiveXObject = global.ActiveXObject;3412 xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined";3413 xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined";3414 xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX3415 ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false;3416 xhr.supportsCORS = 'withCredentials' in (new sinon.xhr.GlobalXMLHttpRequest());3417 3418 /*jsl:ignore*/3419 var unsafeHeaders = {3420 "Accept-Charset": true,3421 "Accept-Encoding": true,3422 "Connection": true,3423 "Content-Length": true,3424 "Cookie": true,3425 "Cookie2": true,3426 "Content-Transfer-Encoding": true,3427 "Date": true,3428 "Expect": true,3429 "Host": true,3430 "Keep-Alive": true,3431 "Referer": true,3432 "TE": true,3433 "Trailer": true,3434 "Transfer-Encoding": true,3435 "Upgrade": true,3436 "User-Agent": true,3437 "Via": true3438 };3439 /*jsl:end*/3440 3441 function FakeXMLHttpRequest() {3442 this.readyState = FakeXMLHttpRequest.UNSENT;3443 this.requestHeaders = {};3444 this.requestBody = null;3445 this.status = 0;3446 this.statusText = "";3447 this.upload = new UploadProgress();3448 if (sinon.xhr.supportsCORS) {3449 this.withCredentials = false;3450 }3451 3452 3453 var xhr = this;3454 var events = ["loadstart", "load", "abort", "loadend"];3455 3456 function addEventListener(eventName) {3457 xhr.addEventListener(eventName, function (event) {3458 var listener = xhr["on" + eventName];3459 3460 if (listener && typeof listener == "function") {3461 listener(event);3462 }3463 });3464 }3465 3466 for (var i = events.length - 1; i >= 0; i--) {3467 addEventListener(events[i]);3468 }3469 3470 if (typeof FakeXMLHttpRequest.onCreate == "function") {3471 FakeXMLHttpRequest.onCreate(this);3472 }3473 }3474 3475 // An upload object is created for each3476 // FakeXMLHttpRequest and allows upload3477 // events to be simulated using uploadProgress3478 // and uploadError.3479 function UploadProgress() {3480 this.eventListeners = {3481 "progress": [],3482 "load": [],3483 "abort": [],3484 "error": []3485 }3486 }3487 3488 UploadProgress.prototype.addEventListener = function(event, listener) {3489 this.eventListeners[event].push(listener);3490 };3491 3492 UploadProgress.prototype.removeEventListener = function(event, listener) {3493 var listeners = this.eventListeners[event] || [];3494 3495 for (var i = 0, l = listeners.length; i < l; ++i) {3496 if (listeners[i] == listener) {3497 return listeners.splice(i, 1);3498 }3499 }3500 };3501 3502 UploadProgress.prototype.dispatchEvent = function(event) {3503 var listeners = this.eventListeners[event.type] || [];3504 3505 for (var i = 0, listener; (listener = listeners[i]) != null; i++) {3506 listener(event);3507 }3508 };3509 3510 function verifyState(xhr) {3511 if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {3512 throw new Error("INVALID_STATE_ERR");3513 }3514 3515 if (xhr.sendFlag) {3516 throw new Error("INVALID_STATE_ERR");3517 }3518 }3519 3520 // filtering to enable a white-list version of Sinon FakeXhr,3521 // where whitelisted requests are passed through to real XHR3522 function each(collection, callback) {3523 if (!collection) return;3524 for (var i = 0, l = collection.length; i < l; i += 1) {3525 callback(collection[i]);3526 }3527 }3528 function some(collection, callback) {3529 for (var index = 0; index < collection.length; index++) {3530 if(callback(collection[index]) === true) return true;3531 }3532 return false;3533 }3534 // largest arity in XHR is 5 - XHR#open3535 var apply = function(obj,method,args) {3536 switch(args.length) {3537 case 0: return obj[method]();3538 case 1: return obj[method](args[0]);3539 case 2: return obj[method](args[0],args[1]);3540 case 3: return obj[method](args[0],args[1],args[2]);3541 case 4: return obj[method](args[0],args[1],args[2],args[3]);3542 case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]);3543 }3544 };3545 3546 FakeXMLHttpRequest.filters = [];3547 FakeXMLHttpRequest.addFilter = function(fn) {3548 this.filters.push(fn)3549 };3550 var IE6Re = /MSIE 6/;3551 FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) {3552 var xhr = new sinon.xhr.workingXHR();3553 each(["open","setRequestHeader","send","abort","getResponseHeader",3554 "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"],3555 function(method) {3556 fakeXhr[method] = function() {3557 return apply(xhr,method,arguments);3558 };3559 });3560 3561 var copyAttrs = function(args) {3562 each(args, function(attr) {3563 try {3564 fakeXhr[attr] = xhr[attr]3565 } catch(e) {3566 if(!IE6Re.test(navigator.userAgent)) throw e;3567 }3568 });3569 };3570 3571 var stateChange = function() {3572 fakeXhr.readyState = xhr.readyState;3573 if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {3574 copyAttrs(["status","statusText"]);3575 }3576 if(xhr.readyState >= FakeXMLHttpRequest.LOADING) {3577 copyAttrs(["responseText"]);3578 }3579 if(xhr.readyState === FakeXMLHttpRequest.DONE) {3580 copyAttrs(["responseXML"]);3581 }3582 if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr });3583 };3584 if(xhr.addEventListener) {3585 for(var event in fakeXhr.eventListeners) {3586 if(fakeXhr.eventListeners.hasOwnProperty(event)) {3587 each(fakeXhr.eventListeners[event],function(handler) {3588 xhr.addEventListener(event, handler);3589 });3590 }3591 }3592 xhr.addEventListener("readystatechange",stateChange);3593 } else {3594 xhr.onreadystatechange = stateChange;3595 }3596 apply(xhr,"open",xhrArgs);3597 };3598 FakeXMLHttpRequest.useFilters = false;3599 3600 function verifyRequestSent(xhr) {3601 if (xhr.readyState == FakeXMLHttpRequest.DONE) {3602 throw new Error("Request done");3603 }3604 }3605 3606 function verifyHeadersReceived(xhr) {3607 if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {3608 throw new Error("No headers received");3609 }3610 }3611 3612 function verifyResponseBodyType(body) {3613 if (typeof body != "string") {3614 var error = new Error("Attempted to respond to fake XMLHttpRequest with " +3615 body + ", which is not a string.");3616 error.name = "InvalidBodyException";3617 throw error;3618 }3619 }3620 3621 sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {3622 async: true,3623 3624 open: function open(method, url, async, username, password) {3625 this.method = method;3626 this.url = url;3627 this.async = typeof async == "boolean" ? async : true;3628 this.username = username;3629 this.password = password;3630 this.responseText = null;3631 this.responseXML = null;3632 this.requestHeaders = {};3633 this.sendFlag = false;3634 if(sinon.FakeXMLHttpRequest.useFilters === true) {3635 var xhrArgs = arguments;3636 var defake = some(FakeXMLHttpRequest.filters,function(filter) {3637 return filter.apply(this,xhrArgs)3638 });3639 if (defake) {3640 return sinon.FakeXMLHttpRequest.defake(this,arguments);3641 }3642 }3643 this.readyStateChange(FakeXMLHttpRequest.OPENED);3644 },3645 3646 readyStateChange: function readyStateChange(state) {3647 this.readyState = state;3648 3649 if (typeof this.onreadystatechange == "function") {3650 try {3651 this.onreadystatechange();3652 } catch (e) {3653 sinon.logError("Fake XHR onreadystatechange handler", e);3654 }3655 }3656 3657 this.dispatchEvent(new sinon.Event("readystatechange"));3658 3659 switch (this.readyState) {3660 case FakeXMLHttpRequest.DONE:3661 this.dispatchEvent(new sinon.Event("load", false, false, this));3662 this.dispatchEvent(new sinon.Event("loadend", false, false, this));3663 this.upload.dispatchEvent(new sinon.Event("load", false, false, this));3664 if (supportsProgress) {3665 this.upload.dispatchEvent(new ProgressEvent("progress", {loaded: 100, total: 100}));3666 }3667 break;3668 }3669 },3670 3671 setRequestHeader: function setRequestHeader(header, value) {3672 verifyState(this);3673 3674 if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {3675 throw new Error("Refused to set unsafe header \"" + header + "\"");3676 }3677 3678 if (this.requestHeaders[header]) {3679 this.requestHeaders[header] += "," + value;3680 } else {3681 this.requestHeaders[header] = value;3682 }3683 },3684 3685 // Helps testing3686 setResponseHeaders: function setResponseHeaders(headers) {3687 this.responseHeaders = {};3688 3689 for (var header in headers) {3690 if (headers.hasOwnProperty(header)) {3691 this.responseHeaders[header] = headers[header];3692 }3693 }3694 3695 if (this.async) {3696 this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);3697 } else {3698 this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;3699 }3700 },3701 3702 // Currently treats ALL data as a DOMString (i.e. no Document)3703 send: function send(data) {3704 verifyState(this);3705 3706 if (!/^(get|head)$/i.test(this.method)) {3707 if (this.requestHeaders["Content-Type"]) {3708 var value = this.requestHeaders["Content-Type"].split(";");3709 this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";3710 } else {3711 this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";3712 }3713 3714 this.requestBody = data;3715 }3716 3717 this.errorFlag = false;3718 this.sendFlag = this.async;3719 this.readyStateChange(FakeXMLHttpRequest.OPENED);3720 3721 if (typeof this.onSend == "function") {3722 this.onSend(this);3723 }3724 3725 this.dispatchEvent(new sinon.Event("loadstart", false, false, this));3726 },3727 3728 abort: function abort() {3729 this.aborted = true;3730 this.responseText = null;3731 this.errorFlag = true;3732 this.requestHeaders = {};3733 3734 if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) {3735 this.readyStateChange(sinon.FakeXMLHttpRequest.DONE);3736 this.sendFlag = false;3737 }3738 3739 this.readyState = sinon.FakeXMLHttpRequest.UNSENT;3740 3741 this.dispatchEvent(new sinon.Event("abort", false, false, this));3742 3743 this.upload.dispatchEvent(new sinon.Event("abort", false, false, this));3744 3745 if (typeof this.onerror === "function") {3746 this.onerror();3747 }3748 },3749 3750 getResponseHeader: function getResponseHeader(header) {3751 if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {3752 return null;3753 }3754 3755 if (/^Set-Cookie2?$/i.test(header)) {3756 return null;3757 }3758 3759 header = header.toLowerCase();3760 3761 for (var h in this.responseHeaders) {3762 if (h.toLowerCase() == header) {3763 return this.responseHeaders[h];3764 }3765 }3766 3767 return null;3768 },3769 3770 getAllResponseHeaders: function getAllResponseHeaders() {3771 if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {3772 return "";3773 }3774 3775 var headers = "";3776 3777 for (var header in this.responseHeaders) {3778 if (this.responseHeaders.hasOwnProperty(header) &&3779 !/^Set-Cookie2?$/i.test(header)) {3780 headers += header + ": " + this.responseHeaders[header] + "\r\n";3781 }3782 }3783 3784 return headers;3785 },3786 3787 setResponseBody: function setResponseBody(body) {3788 verifyRequestSent(this);3789 verifyHeadersReceived(this);3790 verifyResponseBodyType(body);3791 3792 var chunkSize = this.chunkSize || 10;3793 var index = 0;3794 this.responseText = "";3795 3796 do {3797 if (this.async) {3798 this.readyStateChange(FakeXMLHttpRequest.LOADING);3799 }3800 3801 this.responseText += body.substring(index, index + chunkSize);3802 index += chunkSize;3803 } while (index < body.length);3804 3805 var type = this.getResponseHeader("Content-Type");3806 3807 if (this.responseText &&3808 (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {3809 try {3810 this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);3811 } catch (e) {3812 // Unable to parse XML - no biggie3813 }3814 }3815 3816 if (this.async) {3817 this.readyStateChange(FakeXMLHttpRequest.DONE);3818 } else {3819 this.readyState = FakeXMLHttpRequest.DONE;3820 }3821 },3822 3823 respond: function respond(status, headers, body) {3824 this.status = typeof status == "number" ? status : 200;3825 this.statusText = FakeXMLHttpRequest.statusCodes[this.status];3826 this.setResponseHeaders(headers || {});3827 this.setResponseBody(body || "");3828 },3829 3830 uploadProgress: function uploadProgress(progressEventRaw) {3831 if (supportsProgress) {3832 this.upload.dispatchEvent(new ProgressEvent("progress", progressEventRaw));3833 }3834 },3835 3836 uploadError: function uploadError(error) {3837 if (supportsCustomEvent) {3838 this.upload.dispatchEvent(new CustomEvent("error", {"detail": error}));3839 }3840 }3841 });3842 3843 sinon.extend(FakeXMLHttpRequest, {3844 UNSENT: 0,3845 OPENED: 1,3846 HEADERS_RECEIVED: 2,3847 LOADING: 3,3848 DONE: 43849 });3850 3851 // Borrowed from JSpec3852 FakeXMLHttpRequest.parseXML = function parseXML(text) {3853 var xmlDoc;3854 3855 if (typeof DOMParser != "undefined") {3856 var parser = new DOMParser();3857 xmlDoc = parser.parseFromString(text, "text/xml");3858 } else {3859 xmlDoc = new ActiveXObject("Microsoft.XMLDOM");3860 xmlDoc.async = "false";3861 xmlDoc.loadXML(text);3862 }3863 3864 return xmlDoc;3865 };3866 3867 FakeXMLHttpRequest.statusCodes = {3868 100: "Continue",3869 101: "Switching Protocols",3870 200: "OK",3871 201: "Created",3872 202: "Accepted",3873 203: "Non-Authoritative Information",3874 204: "No Content",3875 205: "Reset Content",3876 206: "Partial Content",3877 300: "Multiple Choice",3878 301: "Moved Permanently",3879 302: "Found",3880 303: "See Other",3881 304: "Not Modified",3882 305: "Use Proxy",3883 307: "Temporary Redirect",3884 400: "Bad Request",3885 401: "Unauthorized",3886 402: "Payment Required",3887 403: "Forbidden",3888 404: "Not Found",3889 405: "Method Not Allowed",3890 406: "Not Acceptable",3891 407: "Proxy Authentication Required",3892 408: "Request Timeout",3893 409: "Conflict",3894 410: "Gone",3895 411: "Length Required",3896 412: "Precondition Failed",3897 413: "Request Entity Too Large",3898 414: "Request-URI Too Long",3899 415: "Unsupported Media Type",3900 416: "Requested Range Not Satisfiable",3901 417: "Expectation Failed",3902 422: "Unprocessable Entity",3903 500: "Internal Server Error",3904 501: "Not Implemented",3905 502: "Bad Gateway",3906 503: "Service Unavailable",3907 504: "Gateway Timeout",3908 505: "HTTP Version Not Supported"3909 };3910 3911 sinon.useFakeXMLHttpRequest = function () {3912 sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) {3913 if (xhr.supportsXHR) {3914 global.XMLHttpRequest = xhr.GlobalXMLHttpRequest;3915 }3916 3917 if (xhr.supportsActiveX) {3918 global.ActiveXObject = xhr.GlobalActiveXObject;3919 }3920 3921 delete sinon.FakeXMLHttpRequest.restore;3922 3923 if (keepOnCreate !== true) {3924 delete sinon.FakeXMLHttpRequest.onCreate;3925 }3926 };3927 if (xhr.supportsXHR) {3928 global.XMLHttpRequest = sinon.FakeXMLHttpRequest;3929 }3930 3931 if (xhr.supportsActiveX) {3932 global.ActiveXObject = function ActiveXObject(objId) {3933 if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {3934 3935 return new sinon.FakeXMLHttpRequest();3936 }3937 3938 return new xhr.GlobalActiveXObject(objId);3939 };3940 }3941 3942 return sinon.FakeXMLHttpRequest;3943 };3944 3945 sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;3946 3947 })(typeof global === "object" ? global : this);3948 3949 if (typeof module !== 'undefined' && module.exports) {3950 module.exports = sinon;3951 }3952 3953 /**3954 * @depend fake_xml_http_request.js3955 */3956 /*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/3957 /*global module, require, window*/3958 /**3959 * The Sinon "server" mimics a web server that receives requests from3960 * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,3961 * both synchronously and asynchronously. To respond synchronuously, canned3962 * answers have to be provided upfront.3963 *3964 * @author Christian Johansen (christian@cjohansen.no)3965 * @license BSD3966 *3967 * Copyright (c) 2010-2013 Christian Johansen3968 */3969 3970 if (typeof sinon == "undefined") {3971 var sinon = {};3972 }3973 3974 sinon.fakeServer = (function () {3975 var push = [].push;3976 function F() {}3977 3978 function create(proto) {3979 F.prototype = proto;3980 return new F();3981 }3982 3983 function responseArray(handler) {3984 var response = handler;3985 3986 if (Object.prototype.toString.call(handler) != "[object Array]") {3987 response = [200, {}, handler];3988 }3989 3990 if (typeof response[2] != "string") {3991 throw new TypeError("Fake server response body should be string, but was " +3992 typeof response[2]);3993 }3994 3995 return response;3996 }3997 3998 var wloc = typeof window !== "undefined" ? window.location : {};3999 var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);4000 4001 function matchOne(response, reqMethod, reqUrl) {4002 var rmeth = response.method;4003 var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase();4004 var url = response.url;4005 var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl));4006 4007 return matchMethod && matchUrl;4008 }4009 4010 function match(response, request) {4011 var requestUrl = request.url;4012 4013 if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {4014 requestUrl = requestUrl.replace(rCurrLoc, "");4015 }4016 4017 if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {4018 if (typeof response.response == "function") {4019 var ru = response.url;4020 var args = [request].concat(ru && typeof ru.exec == "function" ? ru.exec(requestUrl).slice(1) : []);4021 return response.response.apply(response, args);4022 }4023 4024 return true;4025 }4026 4027 return false;4028 }4029 4030 function log(response, request) {4031 var str;4032 4033 str = "Request:\n" + sinon.format(request) + "\n\n";4034 str += "Response:\n" + sinon.format(response) + "\n\n";4035 4036 sinon.log(str);4037 }4038 4039 return {4040 create: function () {4041 var server = create(this);4042 this.xhr = sinon.useFakeXMLHttpRequest();4043 server.requests = [];4044 4045 this.xhr.onCreate = function (xhrObj) {4046 server.addRequest(xhrObj);4047 };4048 4049 return server;4050 },4051 4052 addRequest: function addRequest(xhrObj) {4053 var server = this;4054 push.call(this.requests, xhrObj);4055 4056 xhrObj.onSend = function () {4057 server.handleRequest(this);4058 4059 if (server.autoRespond && !server.responding) {4060 setTimeout(function () {4061 server.responding = false;4062 server.respond();4063 }, server.autoRespondAfter || 10);4064 4065 server.responding = true;4066 }4067 };4068 },4069 4070 getHTTPMethod: function getHTTPMethod(request) {4071 if (this.fakeHTTPMethods && /post/i.test(request.method)) {4072 var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);4073 return !!matches ? matches[1] : request.method;4074 }4075 4076 return request.method;4077 },4078 4079 handleRequest: function handleRequest(xhr) {4080 if (xhr.async) {4081 if (!this.queue) {4082 this.queue = [];4083 }4084 4085 push.call(this.queue, xhr);4086 } else {4087 this.processRequest(xhr);4088 }4089 },4090 4091 respondWith: function respondWith(method, url, body) {4092 if (arguments.length == 1 && typeof method != "function") {4093 this.response = responseArray(method);4094 return;4095 }4096 4097 if (!this.responses) { this.responses = []; }4098 4099 if (arguments.length == 1) {4100 body = method;4101 url = method = null;4102 }4103 4104 if (arguments.length == 2) {4105 body = url;4106 url = method;4107 method = null;4108 }4109 4110 push.call(this.responses, {4111 method: method,4112 url: url,4113 response: typeof body == "function" ? body : responseArray(body)4114 });4115 },4116 4117 respond: function respond() {4118 if (arguments.length > 0) this.respondWith.apply(this, arguments);4119 var queue = this.queue || [];4120 var requests = queue.splice(0);4121 var request;4122 4123 while(request = requests.shift()) {4124 this.processRequest(request);4125 }4126 },4127 4128 processRequest: function processRequest(request) {4129 try {4130 if (request.aborted) {4131 return;4132 }4133 4134 var response = this.response || [404, {}, ""];4135 4136 if (this.responses) {4137 for (var l = this.responses.length, i = l - 1; i >= 0; i--) {4138 if (match.call(this, this.responses[i], request)) {4139 response = this.responses[i].response;4140 break;4141 }4142 }4143 }4144 4145 if (request.readyState != 4) {4146 log(response, request);4147 4148 request.respond(response[0], response[1], response[2]);4149 }4150 } catch (e) {4151 sinon.logError("Fake server request processing", e);4152 }4153 },4154 4155 restore: function restore() {4156 return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);4157 }4158 };4159 }());4160 4161 if (typeof module !== 'undefined' && module.exports) {4162 module.exports = sinon;4163 }4164 4165 /**4166 * @depend fake_server.js4167 * @depend fake_timers.js4168 */4169 /*jslint browser: true, eqeqeq: false, onevar: false*/4170 /*global sinon*/4171 /**4172 * Add-on for sinon.fakeServer that automatically handles a fake timer along with4173 * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery4174 * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead,4175 * it polls the object for completion with setInterval. Dispite the direct4176 * motivation, there is nothing jQuery-specific in this file, so it can be used4177 * in any environment where the ajax implementation depends on setInterval or4178 * setTimeout.4179 *4180 * @author Christian Johansen (christian@cjohansen.no)4181 * @license BSD4182 *4183 * Copyright (c) 2010-2013 Christian Johansen4184 */4185 4186 (function () {4187 function Server() {}4188 Server.prototype = sinon.fakeServer;4189 4190 sinon.fakeServerWithClock = new Server();4191 4192 sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {4193 if (xhr.async) {4194 if (typeof setTimeout.clock == "object") {4195 this.clock = setTimeout.clock;4196 } else {4197 this.clock = sinon.useFakeTimers();4198 this.resetClock = true;4199 }4200 4201 if (!this.longestTimeout) {4202 var clockSetTimeout = this.clock.setTimeout;4203 var clockSetInterval = this.clock.setInterval;4204 var server = this;4205 4206 this.clock.setTimeout = function (fn, timeout) {4207 server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);4208 4209 return clockSetTimeout.apply(this, arguments);4210 };4211 4212 this.clock.setInterval = function (fn, timeout) {4213 server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);4214 4215 return clockSetInterval.apply(this, arguments);4216 };4217 }4218 }4219 4220 return sinon.fakeServer.addRequest.call(this, xhr);4221 };4222 4223 sinon.fakeServerWithClock.respond = function respond() {4224 var returnVal = sinon.fakeServer.respond.apply(this, arguments);4225 4226 if (this.clock) {4227 this.clock.tick(this.longestTimeout || 0);4228 this.longestTimeout = 0;4229 4230 if (this.resetClock) {4231 this.clock.restore();4232 this.resetClock = false;4233 }4234 }4235 4236 return returnVal;4237 };4238 4239 sinon.fakeServerWithClock.restore = function restore() {4240 if (this.clock) {4241 this.clock.restore();4242 }4243 4244 return sinon.fakeServer.restore.apply(this, arguments);4245 };4246 }());4247 4248 /**4249 * @depend ../sinon.js4250 * @depend collection.js4251 * @depend util/fake_timers.js4252 * @depend util/fake_server_with_clock.js4253 */4254 /*jslint eqeqeq: false, onevar: false, plusplus: false*/4255 /*global require, module*/4256 /**4257 * Manages fake collections as well as fake utilities such as Sinon's4258 * timers and fake XHR implementation in one convenient object.4259 *4260 * @author Christian Johansen (christian@cjohansen.no)4261 * @license BSD4262 *4263 * Copyright (c) 2010-2013 Christian Johansen4264 */4265 4266 if (typeof module !== 'undefined' && module.exports) {4267 var sinon = require("../sinon");4268 sinon.extend(sinon, require("./util/fake_timers"));4269 }4270 4271 (function () {4272 var push = [].push;4273 4274 function exposeValue(sandbox, config, key, value) {4275 if (!value) {4276 return;4277 }4278 4279 if (config.injectInto && !(key in config.injectInto) ) {4280 config.injectInto[key] = value;4281 } else {4282 push.call(sandbox.args, value);4283 }4284 }4285 4286 function prepareSandboxFromConfig(config) {4287 var sandbox = sinon.create(sinon.sandbox);4288 4289 if (config.useFakeServer) {4290 if (typeof config.useFakeServer == "object") {4291 sandbox.serverPrototype = config.useFakeServer;4292 }4293 4294 sandbox.useFakeServer();4295 }4296 4297 if (config.useFakeTimers) {4298 if (typeof config.useFakeTimers == "object") {4299 sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);4300 } else {4301 sandbox.useFakeTimers();4302 }4303 }4304 4305 return sandbox;4306 }4307 4308 sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {4309 useFakeTimers: function useFakeTimers() {4310 this.clock = sinon.useFakeTimers.apply(sinon, arguments);4311 4312 return this.add(this.clock);4313 },4314 4315 serverPrototype: sinon.fakeServer,4316 4317 useFakeServer: function useFakeServer() {4318 var proto = this.serverPrototype || sinon.fakeServer;4319 4320 if (!proto || !proto.create) {4321 return null;4322 }4323 4324 this.server = proto.create();4325 return this.add(this.server);4326 },4327 4328 inject: function (obj) {4329 sinon.collection.inject.call(this, obj);4330 4331 if (this.clock) {4332 obj.clock = this.clock;4333 }4334 4335 if (this.server) {4336 obj.server = this.server;4337 obj.requests = this.server.requests;4338 }4339 4340 return obj;4341 },4342 4343 create: function (config) {4344 if (!config) {4345 return sinon.create(sinon.sandbox);4346 }4347 4348 var sandbox = prepareSandboxFromConfig(config);4349 sandbox.args = sandbox.args || [];4350 var prop, value, exposed = sandbox.inject({});4351 4352 if (config.properties) {4353 for (var i = 0, l = config.properties.length; i < l; i++) {4354 prop = config.properties[i];4355 value = exposed[prop] || prop == "sandbox" && sandbox;4356 exposeValue(sandbox, config, prop, value);4357 }4358 } else {4359 exposeValue(sandbox, config, "sandbox", value);4360 }4361 4362 return sandbox;4363 }4364 });4365 4366 sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;4367 4368 if (typeof module !== 'undefined' && module.exports) {4369 module.exports = sinon.sandbox;4370 }4371 }());4372 4373 /**4374 * @depend ../sinon.js4375 * @depend stub.js4376 * @depend mock.js4377 * @depend sandbox.js4378 */4379 /*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/4380 /*global module, require, sinon*/4381 /**4382 * Test function, sandboxes fakes4383 *4384 * @author Christian Johansen (christian@cjohansen.no)4385 * @license BSD4386 *4387 * Copyright (c) 2010-2013 Christian Johansen4388 */4389 4390 (function (sinon) {4391 var commonJSModule = typeof module !== 'undefined' && module.exports;4392 4393 if (!sinon && commonJSModule) {4394 sinon = require("../sinon");4395 }4396 4397 if (!sinon) {4398 return;4399 }4400 4401 function test(callback) {4402 var type = typeof callback;4403 4404 if (type != "function") {4405 throw new TypeError("sinon.test needs to wrap a test function, got " + type);4406 }4407 4408 return function () {4409 var config = sinon.getConfig(sinon.config);4410 config.injectInto = config.injectIntoThis && this || config.injectInto;4411 var sandbox = sinon.sandbox.create(config);4412 var exception, result;4413 var args = Array.prototype.slice.call(arguments).concat(sandbox.args);4414 4415 try {4416 result = callback.apply(this, args);4417 } catch (e) {4418 exception = e;4419 }4420 4421 if (typeof exception !== "undefined") {4422 sandbox.restore();4423 throw exception;4424 }4425 else {4426 sandbox.verifyAndRestore();4427 }4428 4429 return result;4430 };4431 }4432 4433 test.config = {4434 injectIntoThis: true,4435 injectInto: null,4436 properties: ["spy", "stub", "mock", "clock", "server", "requests"],4437 useFakeTimers: true,4438 useFakeServer: true4439 };4440 4441 if (commonJSModule) {4442 module.exports = test;4443 } else {4444 sinon.test = test;4445 }4446 }(typeof sinon == "object" && sinon || null));4447 4448 /**4449 * @depend ../sinon.js4450 * @depend test.js4451 */4452 /*jslint eqeqeq: false, onevar: false, eqeqeq: false*/4453 /*global module, require, sinon*/4454 /**4455 * Test case, sandboxes all test functions4456 *4457 * @author Christian Johansen (christian@cjohansen.no)4458 * @license BSD4459 *4460 * Copyright (c) 2010-2013 Christian Johansen4461 */4462 4463 (function (sinon) {4464 var commonJSModule = typeof module !== 'undefined' && module.exports;4465 4466 if (!sinon && commonJSModule) {4467 sinon = require("../sinon");4468 }4469 4470 if (!sinon || !Object.prototype.hasOwnProperty) {4471 return;4472 }4473 4474 function createTest(property, setUp, tearDown) {4475 return function () {4476 if (setUp) {4477 setUp.apply(this, arguments);4478 }4479 4480 var exception, result;4481 4482 try {4483 result = property.apply(this, arguments);4484 } catch (e) {4485 exception = e;4486 }4487 4488 if (tearDown) {4489 tearDown.apply(this, arguments);4490 }4491 4492 if (exception) {4493 throw exception;4494 }4495 4496 return result;4497 };4498 }4499 4500 function testCase(tests, prefix) {4501 /*jsl:ignore*/4502 if (!tests || typeof tests != "object") {4503 throw new TypeError("sinon.testCase needs an object with test functions");4504 }4505 /*jsl:end*/4506 4507 prefix = prefix || "test";4508 var rPrefix = new RegExp("^" + prefix);4509 var methods = {}, testName, property, method;4510 var setUp = tests.setUp;4511 var tearDown = tests.tearDown;4512 4513 for (testName in tests) {4514 if (tests.hasOwnProperty(testName)) {4515 property = tests[testName];4516 4517 if (/^(setUp|tearDown)$/.test(testName)) {4518 continue;4519 }4520 4521 if (typeof property == "function" && rPrefix.test(testName)) {4522 method = property;4523 4524 if (setUp || tearDown) {4525 method = createTest(property, setUp, tearDown);4526 }4527 4528 methods[testName] = sinon.test(method);4529 } else {4530 methods[testName] = tests[testName];4531 }4532 }4533 }4534 4535 return methods;4536 }4537 4538 if (commonJSModule) {4539 module.exports = testCase;4540 } else {4541 sinon.testCase = testCase;4542 }4543 }(typeof sinon == "object" && sinon || null));4544 4545 /**4546 * @depend ../sinon.js4547 * @depend stub.js4548 */4549 /*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/4550 /*global module, require, sinon*/4551 /**4552 * Assertions matching the test spy retrieval interface.4553 *4554 * @author Christian Johansen (christian@cjohansen.no)4555 * @license BSD4556 *4557 * Copyright (c) 2010-2013 Christian Johansen4558 */4559 4560 (function (sinon, global) {4561 var commonJSModule = typeof module !== "undefined" && module.exports;4562 var slice = Array.prototype.slice;4563 var assert;4564 4565 if (!sinon && commonJSModule) {4566 sinon = require("../sinon");4567 }4568 4569 if (!sinon) {4570 return;4571 }4572 4573 function verifyIsStub() {4574 var method;4575 4576 for (var i = 0, l = arguments.length; i < l; ++i) {4577 method = arguments[i];4578 4579 if (!method) {4580 assert.fail("fake is not a spy");4581 }4582 4583 if (typeof method != "function") {4584 assert.fail(method + " is not a function");4585 }4586 4587 if (typeof method.getCall != "function") {4588 assert.fail(method + " is not stubbed");4589 }4590 }4591 }4592 4593 function failAssertion(object, msg) {4594 object = object || global;4595 var failMethod = object.fail || assert.fail;4596 failMethod.call(object, msg);4597 }4598 4599 function mirrorPropAsAssertion(name, method, message) {4600 if (arguments.length == 2) {4601 message = method;4602 method = name;4603 }4604 4605 assert[name] = function (fake) {4606 verifyIsStub(fake);4607 4608 var args = slice.call(arguments, 1);4609 var failed = false;4610 4611 if (typeof method == "function") {4612 failed = !method(fake);4613 } else {4614 failed = typeof fake[method] == "function" ?4615 !fake[method].apply(fake, args) : !fake[method];4616 }4617 4618 if (failed) {4619 failAssertion(this, fake.printf.apply(fake, [message].concat(args)));4620 } else {4621 assert.pass(name);4622 }4623 };4624 }4625 4626 function exposedName(prefix, prop) {4627 return !prefix || /^fail/.test(prop) ? prop :4628 prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);4629 }4630 4631 assert = {4632 failException: "AssertError",4633 4634 fail: function fail(message) {4635 var error = new Error(message);4636 error.name = this.failException || assert.failException;4637 4638 throw error;4639 },4640 4641 pass: function pass(assertion) {},4642 4643 callOrder: function assertCallOrder() {4644 verifyIsStub.apply(null, arguments);4645 var expected = "", actual = "";4646 4647 if (!sinon.calledInOrder(arguments)) {4648 try {4649 expected = [].join.call(arguments, ", ");4650 var calls = slice.call(arguments);4651 var i = calls.length;4652 while (i) {4653 if (!calls[--i].called) {4654 calls.splice(i, 1);4655 }4656 }4657 actual = sinon.orderByFirstCall(calls).join(", ");4658 } catch (e) {4659 // If this fails, we'll just fall back to the blank string4660 }4661 4662 failAssertion(this, "expected " + expected + " to be " +4663 "called in order but were called as " + actual);4664 } else {4665 assert.pass("callOrder");4666 }4667 },4668 4669 callCount: function assertCallCount(method, count) {4670 verifyIsStub(method);4671 4672 if (method.callCount != count) {4673 var msg = "expected %n to be called " + sinon.timesInWords(count) +4674 " but was called %c%C";4675 failAssertion(this, method.printf(msg));4676 } else {4677 assert.pass("callCount");4678 }4679 },4680 4681 expose: function expose(target, options) {4682 if (!target) {4683 throw new TypeError("target is null or undefined");4684 }4685 4686 var o = options || {};4687 var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix;4688 var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail;4689 4690 for (var method in this) {4691 if (method != "export" && (includeFail || !/^(fail)/.test(method))) {4692 target[exposedName(prefix, method)] = this[method];4693 }4694 }4695 4696 return target;4697 }4698 };4699 4700 mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called");4701 mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; },4702 "expected %n to not have been called but was called %c%C");4703 mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C");4704 mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C");4705 mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C");4706 mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t");4707 mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t");4708 mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new");4709 mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new");4710 mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C");4711 mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C");4712 mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C");4713 mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C");4714 mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C");4715 mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C");4716 mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C");4717 mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C");4718 mirrorPropAsAssertion("threw", "%n did not throw exception%C");4719 mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");4720 4721 if (commonJSModule) {4722 module.exports = assert;4723 } else {4724 sinon.assert = assert;4725 }4726 }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global));4727 4728 return sinon;}.call(typeof window != 'undefined' && window || {})); -
tests/qunit/wp-admin/js/customize-base.js
Property changes on: tests/qunit/vendor/sinon.js ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property
1 /* global wp , test, ok, equal, module*/1 /* global wp */ 2 2 3 3 jQuery( function( $ ) { 4 4 var FooSuperClass, BarSubClass, foo, bar, ConstructorTestClass, newConstructor, constructorTest, $mockElement, mockString, 5 5 firstInitialValue, firstValueInstance, valuesInstance, wasCallbackFired, mockValueCallback; 6 6 7 module( 'Customize Base: Class' );7 QUnit.module( 'Customize Base: Class' ); 8 8 9 9 FooSuperClass = wp.customize.Class.extend( 10 10 { 11 initialize: function 11 initialize: function( instanceProps ) { 12 12 $.extend( this, instanceProps || {} ); 13 13 }, 14 14 protoProp: 'protoPropValue' … … 17 17 staticProp: 'staticPropValue' 18 18 } 19 19 ); 20 test( 'FooSuperClass is a function ', function () {21 equal( typeof FooSuperClass, 'function' );20 QUnit.test( 'FooSuperClass is a function', function( assert ) { 21 assert.equal( typeof FooSuperClass, 'function' ); 22 22 }); 23 test( 'FooSuperClass prototype has protoProp', function () {24 equal( FooSuperClass.prototype.protoProp, 'protoPropValue' );23 QUnit.test( 'FooSuperClass prototype has protoProp', function( assert ) { 24 assert.equal( FooSuperClass.prototype.protoProp, 'protoPropValue' ); 25 25 }); 26 test( 'FooSuperClass does not have protoProp', function () {27 equal( typeof FooSuperClass.protoProp, 'undefined' );26 QUnit.test( 'FooSuperClass does not have protoProp', function( assert ) { 27 assert.equal( typeof FooSuperClass.protoProp, 'undefined' ); 28 28 }); 29 test( 'FooSuperClass has staticProp', function () {30 equal( FooSuperClass.staticProp, 'staticPropValue' );29 QUnit.test( 'FooSuperClass has staticProp', function( assert ) { 30 assert.equal( FooSuperClass.staticProp, 'staticPropValue' ); 31 31 }); 32 test( 'FooSuperClass prototype does not have staticProp', function () {33 equal( typeof FooSuperClass.prototype.staticProp, 'undefined' );32 QUnit.test( 'FooSuperClass prototype does not have staticProp', function( assert ) { 33 assert.equal( typeof FooSuperClass.prototype.staticProp, 'undefined' ); 34 34 }); 35 35 36 36 foo = new FooSuperClass( { instanceProp: 'instancePropValue' } ); 37 test( 'FooSuperClass instance foo extended Class', function () {38 equal( foo.extended( wp.customize.Class ), true );37 QUnit.test( 'FooSuperClass instance foo extended Class', function( assert ) { 38 assert.equal( foo.extended( wp.customize.Class ), true ); 39 39 }); 40 test( 'foo instance has protoProp', function () {41 equal( foo.protoProp, 'protoPropValue' );40 QUnit.test( 'foo instance has protoProp', function( assert ) { 41 assert.equal( foo.protoProp, 'protoPropValue' ); 42 42 }); 43 test( 'foo instance does not have staticProp', function () {44 equal( typeof foo.staticProp, 'undefined' );43 QUnit.test( 'foo instance does not have staticProp', function( assert ) { 44 assert.equal( typeof foo.staticProp, 'undefined' ); 45 45 }); 46 test( 'FooSuperClass instance foo ran initialize() and has supplied instanceProp', function () {47 equal( foo.instanceProp, 'instancePropValue' );46 QUnit.test( 'FooSuperClass instance foo ran initialize() and has supplied instanceProp', function( assert ) { 47 assert.equal( foo.instanceProp, 'instancePropValue' ); 48 48 }); 49 49 50 50 // @todo Test Class.applicator? 51 51 // @todo Do we test object.instance? 52 52 53 module( 'Customize Base: Subclass' );53 QUnit.module( 'Customize Base: Subclass' ); 54 54 55 55 BarSubClass = FooSuperClass.extend( 56 56 { … … 64 64 subStaticProp: 'subStaticPropValue' 65 65 } 66 66 ); 67 test( 'BarSubClass prototype has subProtoProp', function () {68 equal( BarSubClass.prototype.subProtoProp, 'subProtoPropValue' );67 QUnit.test( 'BarSubClass prototype has subProtoProp', function( assert ) { 68 assert.equal( BarSubClass.prototype.subProtoProp, 'subProtoPropValue' ); 69 69 }); 70 test( 'BarSubClass prototype has parent FooSuperClass protoProp', function () {71 equal( BarSubClass.prototype.protoProp, 'protoPropValue' );70 QUnit.test( 'BarSubClass prototype has parent FooSuperClass protoProp', function( assert ) { 71 assert.equal( BarSubClass.prototype.protoProp, 'protoPropValue' ); 72 72 }); 73 73 74 74 bar = new BarSubClass( { instanceProp: 'instancePropValue' } ); 75 test( 'BarSubClass instance bar its initialize() and parent initialize() run', function () {76 equal( bar.instanceProp, 'instancePropValue' );77 equal( bar.subInstanceProp, 'subInstancePropValue' );75 QUnit.test( 'BarSubClass instance bar its initialize() and parent initialize() run', function( assert ) { 76 assert.equal( bar.instanceProp, 'instancePropValue' ); 77 assert.equal( bar.subInstanceProp, 'subInstancePropValue' ); 78 78 }); 79 79 80 test( 'BarSubClass instance bar extended FooSuperClass', function () {81 equal( bar.extended( FooSuperClass ), true );80 QUnit.test( 'BarSubClass instance bar extended FooSuperClass', function( assert ) { 81 assert.equal( bar.extended( FooSuperClass ), true ); 82 82 }); 83 83 84 84 85 85 // Implements todo: Test Class.constructor() manipulation. 86 module( 'Customize Base: Constructor Manipulation' );86 QUnit.module( 'Customize Base: Constructor Manipulation' ); 87 87 88 88 newConstructor = function ( instanceProps ) { 89 89 $.extend( this , instanceProps || {} ); … … 99 99 } 100 100 ); 101 101 102 test( 'New constructor added to class' , function () {103 equal( ConstructorTestClass.prototype.constructor , newConstructor );102 QUnit.test( 'New constructor added to class', function( assert ) { 103 assert.equal( ConstructorTestClass.prototype.constructor , newConstructor ); 104 104 }); 105 test( 'Class with new constructor has protoPropValue' , function () {106 equal( ConstructorTestClass.prototype.protoProp , 'protoPropValue' );105 QUnit.test( 'Class with new constructor has protoPropValue', function( assert ) { 106 assert.equal( ConstructorTestClass.prototype.protoProp , 'protoPropValue' ); 107 107 }); 108 108 109 109 constructorTest = new ConstructorTestClass( { instanceProp: 'instancePropValue' } ); 110 test( 'ConstructorTestClass instance constructorTest has the new constructor', function () {111 equal( constructorTest.constructor, newConstructor );110 QUnit.test( 'ConstructorTestClass instance constructorTest has the new constructor', function( assert ) { 111 assert.equal( constructorTest.constructor, newConstructor ); 112 112 }); 113 113 114 test( 'ConstructorTestClass instance constructorTest extended Class', function () {115 equal( constructorTest.extended( wp.customize.Class ), true );114 QUnit.test( 'ConstructorTestClass instance constructorTest extended Class', function( assert ) { 115 assert.equal( constructorTest.extended( wp.customize.Class ), true ); 116 116 }); 117 117 118 test( 'ConstructorTestClass instance constructorTest has the added instance property', function () {119 equal( constructorTest.instanceProp , 'instancePropValue' );118 QUnit.test( 'ConstructorTestClass instance constructorTest has the added instance property', function( assert ) { 119 assert.equal( constructorTest.instanceProp , 'instancePropValue' ); 120 120 }); 121 121 122 122 123 module( 'Customize Base: wp.customizer.ensure' );123 QUnit.module( 'Customize Base: wp.customizer.ensure' ); 124 124 125 125 $mockElement = $( '<div id="mockElement"></div>' ); 126 126 127 test( 'Handles jQuery argument' , function() {128 equal( wp.customize.ensure( $mockElement ) , $mockElement );127 QUnit.test( 'Handles jQuery argument', function( assert ) { 128 assert.equal( wp.customize.ensure( $mockElement ) , $mockElement ); 129 129 }); 130 130 131 131 mockString = '<div class="mockString"></div>'; 132 132 133 test( 'Handles string argument' , function() {134 ok( wp.customize.ensure( mockString ) instanceof jQuery );133 QUnit.test( 'Handles string argument', function( assert ) { 134 assert.ok( wp.customize.ensure( mockString ) instanceof jQuery ); 135 135 }); 136 136 137 137 138 module( 'Customize Base: Value Class' );138 QUnit.module( 'Customize Base: Value Class' ); 139 139 140 140 firstInitialValue = true; 141 141 firstValueInstance = new wp.customize.Value( firstInitialValue ); 142 142 143 test( 'Initialized with the right value' , function() {144 equal( firstValueInstance.get() , firstInitialValue );143 QUnit.test( 'Initialized with the right value', function( assert ) { 144 assert.equal( firstValueInstance.get() , firstInitialValue ); 145 145 }); 146 146 147 test( '.set() works' , function() {147 QUnit.test( '.set() works', function( assert ) { 148 148 firstValueInstance.set( false ); 149 equal( firstValueInstance.get() , false );149 assert.equal( firstValueInstance.get() , false ); 150 150 }); 151 151 152 test( '.bind() adds new callback that fires on set()' , function() {152 QUnit.test( '.bind() adds new callback that fires on set()', function( assert ) { 153 153 wasCallbackFired = false; 154 154 mockValueCallback = function() { 155 155 wasCallbackFired = true; … … 156 156 }; 157 157 firstValueInstance.bind( mockValueCallback ); 158 158 firstValueInstance.set( 'newValue' ); 159 ok( wasCallbackFired );159 assert.ok( wasCallbackFired ); 160 160 }); 161 161 162 module( 'Customize Base: Values Class' );162 QUnit.module( 'Customize Base: Values Class' ); 163 163 164 164 valuesInstance = new wp.customize.Values(); 165 165 166 test( 'Correct events are triggered when adding to or removing from Values collection', function() {166 QUnit.test( 'Correct events are triggered when adding to or removing from Values collection', function( assert ) { 167 167 var hasFooOnAdd = false, 168 168 hasFooOnRemove = false, 169 169 hasFooOnRemoved = true, … … 179 179 valuePassedToAdd = value; 180 180 } ); 181 181 valuesInstance.add( 'foo', fooValue ); 182 ok( hasFooOnAdd );183 equal( valuePassedToAdd.get(), fooValue.get() );182 assert.ok( hasFooOnAdd ); 183 assert.equal( valuePassedToAdd.get(), fooValue.get() ); 184 184 185 185 // Test events when removing the value. 186 186 valuesInstance.bind( 'remove', function( value ) { … … 194 194 wasEventFiredOnRemoval = true; 195 195 } ); 196 196 valuesInstance.remove( 'foo' ); 197 ok( hasFooOnRemove );198 equal( valuePassedToRemove.get(), fooValue.get() );199 ok( ! hasFooOnRemoved );200 equal( valuePassedToRemoved.get(), fooValue.get() );197 assert.ok( hasFooOnRemove ); 198 assert.equal( valuePassedToRemove.get(), fooValue.get() ); 199 assert.ok( ! hasFooOnRemoved ); 200 assert.equal( valuePassedToRemoved.get(), fooValue.get() ); 201 201 202 202 // Confirm no events are fired when nonexistent value is removed. 203 203 wasEventFiredOnRemoval = false; 204 204 valuesInstance.remove( 'bar' ); 205 ok( ! wasEventFiredOnRemoval );205 assert.ok( ! wasEventFiredOnRemoval ); 206 206 }); 207 207 208 module( 'Customize Base: Notification' );209 test( 'Notification object exists and has expected properties', function ( assert ) {208 QUnit.module( 'Customize Base: Notification' ); 209 QUnit.test( 'Notification object exists and has expected properties', function ( assert ) { 210 210 var notification = new wp.customize.Notification( 'mycode', { 211 211 'message': 'Hello World', 212 212 'type': 'update', … … 231 231 assert.equal( null, notification.data ); 232 232 } ); 233 233 234 module( 'Customize Base: utils.parseQueryString' );235 test( 'wp.customize.utils.parseQueryString works', function( assert ) {234 QUnit.module( 'Customize Base: utils.parseQueryString' ); 235 QUnit.test( 'wp.customize.utils.parseQueryString works', function( assert ) { 236 236 var queryParams; 237 237 queryParams = wp.customize.utils.parseQueryString( 'a=1&b=2' ); 238 238 assert.ok( _.isEqual( queryParams, { a: '1', b: '2' } ) ); -
tests/qunit/wp-admin/js/customize-controls-utils.js
4 4 var trueMockEvent, falseMockEvent, mockElementLists, $firstMockElement, $secondMockElement, $thirdMockElement, 5 5 BubbleTester, BubbleTesterTwoValues, bubbleTesterParent, firstBubbleTester, secondBubbleTester; 6 6 7 module( 'Customizer Model Utility functions' );7 QUnit.module( 'Customizer Model Utility functions' ); 8 8 9 9 trueMockEvent = { 10 10 type : 'keydown', … … 16 16 which : 13 17 17 }; 18 18 19 test( 'isKeydownButNotEnterEvent returns true' , function () {20 ok( wp.customize.utils.isKeydownButNotEnterEvent( trueMockEvent ) );19 QUnit.test( 'isKeydownButNotEnterEvent returns true', function( assert ) { 20 assert.ok( wp.customize.utils.isKeydownButNotEnterEvent( trueMockEvent ) ); 21 21 }); 22 22 23 test( 'isKeydownButNotEnterEvent returns false' , function () {24 equal( wp.customize.utils.isKeydownButNotEnterEvent( falseMockEvent ) , false );23 QUnit.test( 'isKeydownButNotEnterEvent returns false', function( assert ) { 24 assert.equal( wp.customize.utils.isKeydownButNotEnterEvent( falseMockEvent ) , false ); 25 25 }); 26 26 27 27 $firstMockElement = $( '<div id="foo"></div>' ); … … 36 36 thirdButLonger : [ $firstMockElement, $secondMockElement, $thirdMockElement ] 37 37 }; 38 38 39 test( 'areElementListsEqual returns true' , function () {40 ok( wp.customize.utils.areElementListsEqual( mockElementLists.first , mockElementLists.first ) );39 QUnit.test( 'areElementListsEqual returns true', function( assert ) { 40 assert.ok( wp.customize.utils.areElementListsEqual( mockElementLists.first , mockElementLists.first ) ); 41 41 }); 42 42 43 test( 'areElementListsEqual returns false' , function () {44 equal( wp.customize.utils.areElementListsEqual( mockElementLists.first , mockElementLists.second ) , false );43 QUnit.test( 'areElementListsEqual returns false', function( assert ) { 44 assert.equal( wp.customize.utils.areElementListsEqual( mockElementLists.first , mockElementLists.second ) , false ); 45 45 }); 46 46 47 test( 'areElementListsEqual: lists have same values, but in reverse order' , function () {48 equal( wp.customize.utils.areElementListsEqual( mockElementLists.first , mockElementLists.firstInReverseOrder ) , false );47 QUnit.test( 'areElementListsEqual: lists have same values, but in reverse order', function( assert ) { 48 assert.equal( wp.customize.utils.areElementListsEqual( mockElementLists.first , mockElementLists.firstInReverseOrder ) , false ); 49 49 }); 50 50 51 test( 'areElementListsEqual: lists have same values, but one is longer' , function () {52 equal( wp.customize.utils.areElementListsEqual( mockElementLists.third , mockElementLists.thirdButLonger ) , false );51 QUnit.test( 'areElementListsEqual: lists have same values, but one is longer', function( assert ) { 52 assert.equal( wp.customize.utils.areElementListsEqual( mockElementLists.third , mockElementLists.thirdButLonger ) , false ); 53 53 }); 54 54 55 55 … … 72 72 } 73 73 ); 74 74 75 test( 'bubbleChildValueChanges notifies parent of change' , function() {75 QUnit.test( 'bubbleChildValueChanges notifies parent of change', function( assert ) { 76 76 firstBubbleTester = new BubbleTester(); 77 77 wp.customize.utils.bubbleChildValueChanges( firstBubbleTester , [ 'fooValue' ] ); 78 78 firstBubbleTester.fooValue.set( 'new value' ); 79 ok( firstBubbleTester.parent.wasChangeTriggered );79 assert.ok( firstBubbleTester.parent.wasChangeTriggered ); 80 80 }); 81 81 82 test( 'bubbleChildValueChanges passes a reference to its instance' , function() {83 ok( firstBubbleTester.parent.instancePassedInTrigger instanceof BubbleTester );82 QUnit.test( 'bubbleChildValueChanges passes a reference to its instance', function( assert ) { 83 assert.ok( firstBubbleTester.parent.instancePassedInTrigger instanceof BubbleTester ); 84 84 }); 85 85 86 86 BubbleTesterTwoValues = wp.customize.Class.extend( … … 98 98 wp.customize.utils.bubbleChildValueChanges( secondBubbleTester , [ 'exampleValue' , 'barValue' ] ); 99 99 secondBubbleTester.barValue.set( 'new value' ); 100 100 101 test( 'bubbleChildValueChanges notifies parent of change when two values are bound' , function() {102 ok( secondBubbleTester.parent.wasChangeTriggered );101 QUnit.test( 'bubbleChildValueChanges notifies parent of change when two values are bound', function( assert ) { 102 assert.ok( secondBubbleTester.parent.wasChangeTriggered ); 103 103 }); 104 104 105 test( 'bubbleChildValueChanges passes a reference to its instance when two values are bound' , function() {106 ok( secondBubbleTester.parent.instancePassedInTrigger instanceof BubbleTesterTwoValues );105 QUnit.test( 'bubbleChildValueChanges passes a reference to its instance when two values are bound', function( assert ) { 106 assert.ok( secondBubbleTester.parent.instancePassedInTrigger instanceof BubbleTesterTwoValues ); 107 107 }); 108 108 }); -
tests/qunit/wp-admin/js/customize-controls.js
1 /* global JSON, wp , test, ok, equal, module*/1 /* global JSON, wp */ 2 2 3 3 wp.customize.settingConstructor.abbreviation = wp.customize.Setting.extend({ 4 4 validate: function( value ) { … … 6 6 } 7 7 }); 8 8 9 jQuery( window ).load( function (){9 jQuery( window ).load( function() { 10 10 'use strict'; 11 11 12 12 var controlId, controlLabel, controlType, controlContent, controlDescription, controlData, mockControl, … … 19 19 throw new Error( 'Must pass value type in expectedValues.' ); 20 20 } 21 21 var type = expectedValues.type; 22 test( 'Model extends proper type', function () {23 ok( model.extended( wp.customize[ type ] ) );22 QUnit.test( 'Model extends proper type', function( assert ) { 23 assert.ok( model.extended( wp.customize[ type ] ) ); 24 24 } ); 25 25 26 26 if ( expectedValues.hasOwnProperty( 'id' ) ) { 27 test( type + ' instance has the right id', function () {28 equal( model.id, expectedValues.id );27 QUnit.test( type + ' instance has the right id', function( assert ) { 28 assert.equal( model.id, expectedValues.id ); 29 29 }); 30 30 } 31 31 if ( expectedValues.hasOwnProperty( 'title') ) { 32 test( type + ' instance has the right title.', function () {33 equal( model.params.title, expectedValues.title );32 QUnit.test( type + ' instance has the right title.', function( assert ) { 33 assert.equal( model.params.title, expectedValues.title ); 34 34 }); 35 35 } 36 36 if ( expectedValues.hasOwnProperty( 'description' ) ) { 37 test( type + ' instance has the right description.', function () {38 equal( model.params.description, expectedValues.description );37 QUnit.test( type + ' instance has the right description.', function( assert ) { 38 assert.equal( model.params.description, expectedValues.description ); 39 39 }); 40 40 } 41 41 if ( expectedValues.hasOwnProperty( 'content' ) ) { 42 test( type + ' instance has the right content.', function () {43 equal( model.params.content, expectedValues.content );42 QUnit.test( type + ' instance has the right content.', function( assert ) { 43 assert.equal( model.params.content, expectedValues.content ); 44 44 }); 45 45 } 46 46 if ( expectedValues.hasOwnProperty( 'priority' ) ) { 47 test( type + ' instance has the right priority.', function () {48 equal( model.priority(), expectedValues.priority );47 QUnit.test( type + ' instance has the right priority.', function( assert ) { 48 assert.equal( model.priority(), expectedValues.priority ); 49 49 }); 50 50 } 51 51 if ( expectedValues.hasOwnProperty( 'active' ) ) { 52 test( type + ' instance has the right active state.', function () {53 equal( model.active(), expectedValues.active );52 QUnit.test( type + ' instance has the right active state.', function( assert ) { 53 assert.equal( model.active(), expectedValues.active ); 54 54 }); 55 55 } 56 test( type + ' can be deactivated', function () {56 QUnit.test( type + ' can be deactivated', function( assert ) { 57 57 model.activate(); 58 58 model.deactivate(); 59 equal( model.active(), false );59 assert.equal( model.active(), false ); 60 60 model.activate(); 61 equal( model.active(), true );62 ok(true);61 assert.equal( model.active(), true ); 62 assert.ok(true); 63 63 }); 64 64 65 65 if ( type === 'Panel' || type === 'Section' ) { 66 66 if ( expectedValues.hasOwnProperty( 'expanded' ) ) { 67 test( type + ' instance has the right expanded state.', function () {68 equal( model.expanded(), expectedValues.expanded );67 QUnit.test( type + ' instance has the right expanded state.', function( assert ) { 68 assert.equal( model.expanded(), expectedValues.expanded ); 69 69 } ); 70 70 } 71 71 72 test( type + ' instance is collapsed after calling .collapse()', function () {72 QUnit.test( type + ' instance is collapsed after calling .collapse()', function( assert ) { 73 73 model.collapse(); 74 ok( ! model.expanded() );74 assert.ok( ! model.expanded() ); 75 75 }); 76 76 77 test( type + ' instance is expanded after calling .expand()', function () {77 QUnit.test( type + ' instance is expanded after calling .expand()', function( assert ) { 78 78 model.expand(); 79 ok( model.expanded() );79 assert.ok( model.expanded() ); 80 80 }); 81 81 } 82 82 83 83 }; 84 84 85 module( 'Customizer notifications collection' );86 test( 'Notifications collection exists', function() {87 ok( wp.customize.notifications );88 equal( wp.customize.notifications.defaultConstructor, wp.customize.Notification );85 QUnit.module( 'Customizer notifications collection' ); 86 QUnit.test( 'Notifications collection exists', function( assert ) { 87 assert.ok( wp.customize.notifications ); 88 assert.equal( wp.customize.notifications.defaultConstructor, wp.customize.Notification ); 89 89 } ); 90 90 91 test( 'Notification objects are rendered as part of notifications collection', function() {91 QUnit.test( 'Notification objects are rendered as part of notifications collection', function( assert ) { 92 92 var container = jQuery( '#customize-notifications-test' ), items, collection; 93 93 94 94 collection = new wp.customize.Notifications({ … … 97 97 collection.add( 'mycode-1', new wp.customize.Notification( 'mycode-1', { message: 'My message 1' } ) ); 98 98 collection.render(); 99 99 items = collection.container.find( 'li' ); 100 equal( items.length, 1 );101 equal( items.first().data( 'code' ), 'mycode-1' );100 assert.equal( items.length, 1 ); 101 assert.equal( items.first().data( 'code' ), 'mycode-1' ); 102 102 103 103 collection.add( 'mycode-2', new wp.customize.Notification( 'mycode-2', { 104 104 message: 'My message 2', … … 106 106 } ) ); 107 107 collection.render(); 108 108 items = collection.container.find( 'li' ); 109 equal( items.length, 2 );110 equal( items.first().data( 'code' ), 'mycode-2' );111 equal( items.last().data( 'code' ), 'mycode-1' );109 assert.equal( items.length, 2 ); 110 assert.equal( items.first().data( 'code' ), 'mycode-2' ); 111 assert.equal( items.last().data( 'code' ), 'mycode-1' ); 112 112 113 equal( items.first().find( '.notice-dismiss' ).length, 1 );114 equal( items.last().find( '.notice-dismiss' ).length, 0 );113 assert.equal( items.first().find( '.notice-dismiss' ).length, 1 ); 114 assert.equal( items.last().find( '.notice-dismiss' ).length, 0 ); 115 115 116 116 collection.remove( 'mycode-2' ); 117 117 collection.render(); 118 118 items = collection.container.find( 'li' ); 119 equal( items.length, 1 );120 equal( items.first().data( 'code' ), 'mycode-1' );119 assert.equal( items.length, 1 ); 120 assert.equal( items.first().data( 'code' ), 'mycode-1' ); 121 121 122 122 collection.remove( 'mycode-1' ); 123 123 collection.render(); 124 ok( collection.container.is( ':hidden' ), 'Notifications area is hidden.' );124 assert.ok( collection.container.is( ':hidden' ), 'Notifications area is hidden.' ); 125 125 } ); 126 126 127 module( 'Customizer Previewed Device' );128 test( 'Previewed device defaults to desktop.', function () {129 equal( wp.customize.previewedDevice.get(), 'desktop' );127 QUnit.module( 'Customizer Previewed Device' ); 128 QUnit.test( 'Previewed device defaults to desktop.', function( assert ) { 129 assert.equal( wp.customize.previewedDevice.get(), 'desktop' ); 130 130 } ); 131 131 132 module( 'Customizer Setting in Fixture' );133 test( 'Setting has fixture value', function () {134 equal( wp.customize( 'fixture-setting' )(), 'Lorem Ipsum' );132 QUnit.module( 'Customizer Setting in Fixture' ); 133 QUnit.test( 'Setting has fixture value', function( assert ) { 134 assert.equal( wp.customize( 'fixture-setting' )(), 'Lorem Ipsum' ); 135 135 } ); 136 test( 'Setting has notifications', function () {136 QUnit.test( 'Setting has notifications', function( assert ) { 137 137 var setting = wp.customize( 'fixture-setting' ); 138 ok( setting.notifications.extended( wp.customize.Values ) );139 equal( wp.customize.Notification, setting.notifications.prototype.constructor.defaultConstructor );138 assert.ok( setting.notifications.extended( wp.customize.Values ) ); 139 assert.equal( wp.customize.Notification, setting.notifications.prototype.constructor.defaultConstructor ); 140 140 } ); 141 test( 'Setting has findControls method', function() {141 QUnit.test( 'Setting has findControls method', function( assert ) { 142 142 var controls, setting = wp.customize( 'fixture-setting' ); 143 equal( 'function', typeof setting.findControls );143 assert.equal( 'function', typeof setting.findControls ); 144 144 controls = setting.findControls(); 145 equal( 1, controls.length );146 equal( 'fixture-control', controls[0].id );145 assert.equal( 1, controls.length ); 146 assert.equal( 'fixture-control', controls[0].id ); 147 147 } ); 148 test( 'Setting constructor object exists', function( assert ) {148 QUnit.test( 'Setting constructor object exists', function( assert ) { 149 149 assert.ok( _.isObject( wp.customize.settingConstructor ) ); 150 150 } ); 151 test( 'Custom setting constructor is used', function( assert ) {151 QUnit.test( 'Custom setting constructor is used', function( assert ) { 152 152 var setting = wp.customize( 'fixture-setting-abbr' ); 153 153 assert.ok( setting.extended( wp.customize.settingConstructor.abbreviation ) ); 154 154 setting.set( 'usa' ); … … 155 155 assert.equal( 'USA', setting.get() ); 156 156 } ); 157 157 158 module( 'Customizer Control in Fixture' );159 test( 'Control exists', function () {160 ok( wp.customize.control.has( 'fixture-control' ) );158 QUnit.module( 'Customizer Control in Fixture' ); 159 QUnit.test( 'Control exists', function( assert ) { 160 assert.ok( wp.customize.control.has( 'fixture-control' ) ); 161 161 } ); 162 test( 'Control has the fixture setting', function () {162 QUnit.test( 'Control has the fixture setting', function( assert ) { 163 163 var control = wp.customize.control( 'fixture-control' ); 164 equal( control.setting(), 'Lorem Ipsum' );165 equal( control.setting.id, 'fixture-setting' );164 assert.equal( control.setting(), 'Lorem Ipsum' ); 165 assert.equal( control.setting.id, 'fixture-setting' ); 166 166 } ); 167 test( 'Control has the section fixture section ID', function () {167 QUnit.test( 'Control has the section fixture section ID', function( assert ) { 168 168 var control = wp.customize.control( 'fixture-control' ); 169 equal( control.section(), 'fixture-section' );169 assert.equal( control.section(), 'fixture-section' ); 170 170 } ); 171 test( 'Control has notifications', function ( assert ) {171 QUnit.test( 'Control has notifications', function ( assert ) { 172 172 var control = wp.customize.control( 'fixture-control' ), settingNotification, controlOnlyNotification, doneEmbedded; 173 173 assert.ok( control.notifications.extended( wp.customize.Values ) ); 174 174 assert.equal( wp.customize.Notification, control.notifications.prototype.constructor.defaultConstructor ); … … 215 215 } ); 216 216 } ); 217 217 218 module( 'Customizer control without associated settings' );219 test( 'Control can be created without settings', function() {218 QUnit.module( 'Customizer control without associated settings' ); 219 QUnit.test( 'Control can be created without settings', function( assert ) { 220 220 var control = new wp.customize.Control( 'settingless', { 221 221 params: { 222 222 content: jQuery( '<li class="settingless">Hello World</li>' ), … … 224 224 } 225 225 } ); 226 226 wp.customize.control.add( control.id, control ); 227 equal( control.deferred.embedded.state(), 'resolved' );228 ok( null === control.setting );229 ok( jQuery.isEmptyObject( control.settings ) );227 assert.equal( control.deferred.embedded.state(), 'resolved' ); 228 assert.ok( null === control.setting ); 229 assert.ok( jQuery.isEmptyObject( control.settings ) ); 230 230 } ); 231 231 232 232 // Begin sections. 233 module( 'Customizer Section in Fixture' );234 test( 'Fixture section exists', function () {235 ok( wp.customize.section.has( 'fixture-section' ) );233 QUnit.module( 'Customizer Section in Fixture' ); 234 QUnit.test( 'Fixture section exists', function( assert ) { 235 assert.ok( wp.customize.section.has( 'fixture-section' ) ); 236 236 } ); 237 test( 'Fixture section has control among controls()', function () {237 QUnit.test( 'Fixture section has control among controls()', function( assert ) { 238 238 var section = wp.customize.section( 'fixture-section' ); 239 ok( -1 !== _.pluck( section.controls(), 'id' ).indexOf( 'fixture-control' ) );239 assert.ok( -1 !== _.pluck( section.controls(), 'id' ).indexOf( 'fixture-control' ) ); 240 240 } ); 241 test( 'Fixture section has has expected panel', function () {241 QUnit.test( 'Fixture section has has expected panel', function( assert ) { 242 242 var section = wp.customize.section( 'fixture-section' ); 243 equal( section.panel(), 'fixture-panel' );243 assert.equal( section.panel(), 'fixture-panel' ); 244 244 } ); 245 245 246 module( 'Customizer Default Section with Template in Fixture' );247 test( 'Fixture section exists', function () {248 ok( wp.customize.section.has( 'fixture-section-default-templated' ) );246 QUnit.module( 'Customizer Default Section with Template in Fixture' ); 247 QUnit.test( 'Fixture section exists', function( assert ) { 248 assert.ok( wp.customize.section.has( 'fixture-section-default-templated' ) ); 249 249 } ); 250 test( 'Fixture section has expected content', function () {250 QUnit.test( 'Fixture section has expected content', function( assert ) { 251 251 var id = 'fixture-section-default-templated', section; 252 252 section = wp.customize.section( id ); 253 ok( ! section.params.content );254 ok( !! section.container );255 ok( !! section.headContainer );256 ok( !! section.contentContainer );257 ok( section.container.has( section.headContainer ) );258 ok( section.container.has( section.contentContainer ) );259 ok( section.headContainer.is( '.control-section.control-section-default' ) );260 ok( 1 === section.headContainer.find( '> .accordion-section-title' ).length );261 ok( section.contentContainer.is( '.accordion-section-content' ) );262 equal( section.headContainer.attr( 'aria-owns' ), section.contentContainer.attr( 'id' ) );253 assert.ok( ! section.params.content ); 254 assert.ok( !! section.container ); 255 assert.ok( !! section.headContainer ); 256 assert.ok( !! section.contentContainer ); 257 assert.ok( section.container.has( section.headContainer ) ); 258 assert.ok( section.container.has( section.contentContainer ) ); 259 assert.ok( section.headContainer.is( '.control-section.control-section-default' ) ); 260 assert.ok( 1 === section.headContainer.find( '> .accordion-section-title' ).length ); 261 assert.ok( section.contentContainer.is( '.accordion-section-content' ) ); 262 assert.equal( section.headContainer.attr( 'aria-owns' ), section.contentContainer.attr( 'id' ) ); 263 263 } ); 264 264 265 module( 'Customizer Custom Type (titleless) Section with Template in Fixture' );266 test( 'Fixture section exists', function () {267 ok( wp.customize.section.has( 'fixture-section-titleless-templated' ) );265 QUnit.module( 'Customizer Custom Type (titleless) Section with Template in Fixture' ); 266 QUnit.test( 'Fixture section exists', function( assert ) { 267 assert.ok( wp.customize.section.has( 'fixture-section-titleless-templated' ) ); 268 268 } ); 269 test( 'Fixture section has expected content', function () {269 QUnit.test( 'Fixture section has expected content', function( assert ) { 270 270 var id = 'fixture-section-titleless-templated', section; 271 271 section = wp.customize.section( id ); 272 ok( ! section.params.content );273 ok( !! section.container );274 ok( !! section.headContainer );275 ok( !! section.contentContainer );276 ok( section.container.has( section.headContainer ) );277 ok( section.container.has( section.contentContainer ) );278 ok( section.container.is( '.control-section.control-section-titleless' ) );279 ok( 0 === section.headContainer.find( '> .accordion-section-title' ).length );280 ok( section.contentContainer.is( '.accordion-section-content' ) );281 equal( section.headContainer.attr( 'aria-owns' ), section.contentContainer.attr( 'id' ) );272 assert.ok( ! section.params.content ); 273 assert.ok( !! section.container ); 274 assert.ok( !! section.headContainer ); 275 assert.ok( !! section.contentContainer ); 276 assert.ok( section.container.has( section.headContainer ) ); 277 assert.ok( section.container.has( section.contentContainer ) ); 278 assert.ok( section.container.is( '.control-section.control-section-titleless' ) ); 279 assert.ok( 0 === section.headContainer.find( '> .accordion-section-title' ).length ); 280 assert.ok( section.contentContainer.is( '.accordion-section-content' ) ); 281 assert.equal( section.headContainer.attr( 'aria-owns' ), section.contentContainer.attr( 'id' ) ); 282 282 } ); 283 module( 'Customizer Custom Type Section Lacking Specific Template' );284 test( 'Fixture section has expected content', function () {283 QUnit.module( 'Customizer Custom Type Section Lacking Specific Template' ); 284 QUnit.test( 'Fixture section has expected content', function( assert ) { 285 285 var id = 'fixture-section-reusing-default-template', section; 286 286 section = wp.customize.section( id ); 287 ok( ! section.params.content );288 ok( !! section.container );289 ok( !! section.headContainer );290 ok( !! section.contentContainer );291 ok( section.container.has( section.headContainer ) );292 ok( section.container.has( section.contentContainer ) );293 ok( section.headContainer.is( '.control-section.control-section-' + section.params.type ) );294 ok( 1 === section.headContainer.find( '> .accordion-section-title' ).length );295 ok( section.contentContainer.is( '.accordion-section-content' ) );296 equal( section.headContainer.attr( 'aria-owns' ), section.contentContainer.attr( 'id' ) );287 assert.ok( ! section.params.content ); 288 assert.ok( !! section.container ); 289 assert.ok( !! section.headContainer ); 290 assert.ok( !! section.contentContainer ); 291 assert.ok( section.container.has( section.headContainer ) ); 292 assert.ok( section.container.has( section.contentContainer ) ); 293 assert.ok( section.headContainer.is( '.control-section.control-section-' + section.params.type ) ); 294 assert.ok( 1 === section.headContainer.find( '> .accordion-section-title' ).length ); 295 assert.ok( section.contentContainer.is( '.accordion-section-content' ) ); 296 assert.equal( section.headContainer.attr( 'aria-owns' ), section.contentContainer.attr( 'id' ) ); 297 297 } ); 298 module( 'Customizer Section lacking any params' );299 test( 'Fixture section has default params supplied', function () {298 QUnit.module( 'Customizer Section lacking any params' ); 299 QUnit.test( 'Fixture section has default params supplied', function( assert ) { 300 300 var id = 'fixture-section-without-params', section, defaultParams; 301 301 section = wp.customize.section( id ); 302 302 defaultParams = { … … 310 310 customizeAction: '' 311 311 }; 312 312 jQuery.each( defaultParams, function ( key, value ) { 313 ok( 'undefined' !== typeof section.params[ key ] );314 equal( value, section.params[ key ] );313 assert.ok( 'undefined' !== typeof section.params[ key ] ); 314 assert.equal( value, section.params[ key ] ); 315 315 } ); 316 ok( _.isNumber( section.params.instanceNumber ) );316 assert.ok( _.isNumber( section.params.instanceNumber ) ); 317 317 } ); 318 318 319 319 320 320 // Begin panels. 321 module( 'Customizer Panel in Fixture' );322 test( 'Fixture panel exists', function () {323 ok( wp.customize.panel.has( 'fixture-panel' ) );321 QUnit.module( 'Customizer Panel in Fixture' ); 322 QUnit.test( 'Fixture panel exists', function( assert ) { 323 assert.ok( wp.customize.panel.has( 'fixture-panel' ) ); 324 324 } ); 325 test( 'Fixture panel has content', function () {325 QUnit.test( 'Fixture panel has content', function( assert ) { 326 326 var panel = wp.customize.panel( 'fixture-panel' ); 327 ok( !! panel.params.content );328 ok( !! panel.container );329 ok( !! panel.headContainer );330 ok( !! panel.contentContainer );331 ok( panel.container.has( panel.headContainer ) );332 ok( panel.container.has( panel.contentContainer ) );327 assert.ok( !! panel.params.content ); 328 assert.ok( !! panel.container ); 329 assert.ok( !! panel.headContainer ); 330 assert.ok( !! panel.contentContainer ); 331 assert.ok( panel.container.has( panel.headContainer ) ); 332 assert.ok( panel.container.has( panel.contentContainer ) ); 333 333 } ); 334 test( 'Fixture panel has section among its sections()', function () {334 QUnit.test( 'Fixture panel has section among its sections()', function( assert ) { 335 335 var panel = wp.customize.panel( 'fixture-panel' ); 336 ok( -1 !== _.pluck( panel.sections(), 'id' ).indexOf( 'fixture-section' ) );336 assert.ok( -1 !== _.pluck( panel.sections(), 'id' ).indexOf( 'fixture-section' ) ); 337 337 } ); 338 test( 'Panel is not expanded by default', function () {338 QUnit.test( 'Panel is not expanded by default', function( assert ) { 339 339 var panel = wp.customize.panel( 'fixture-panel' ); 340 ok( ! panel.expanded() );340 assert.ok( ! panel.expanded() ); 341 341 } ); 342 test( 'Panel is not expanded by default', function () {342 QUnit.test( 'Panel is not expanded by default', function( assert ) { 343 343 var panel = wp.customize.panel( 'fixture-panel' ); 344 ok( ! panel.expanded() );344 assert.ok( ! panel.expanded() ); 345 345 } ); 346 test( 'Focusing on a control will expand the panel and section', function () {346 QUnit.test( 'Focusing on a control will expand the panel and section', function( assert ) { 347 347 var panel, section, control; 348 348 panel = wp.customize.panel( 'fixture-panel' ); 349 349 section = wp.customize.section( 'fixture-section' ); 350 350 control = wp.customize.control( 'fixture-control' ); 351 ok( ! panel.expanded() );352 ok( ! section.expanded() );351 assert.ok( ! panel.expanded() ); 352 assert.ok( ! section.expanded() ); 353 353 control.focus(); 354 ok( section.expanded() );355 ok( panel.expanded() );354 assert.ok( section.expanded() ); 355 assert.ok( panel.expanded() ); 356 356 } ); 357 357 358 module( 'Customizer Default Panel with Template in Fixture' );359 test( 'Fixture section exists', function () {360 ok( wp.customize.panel.has( 'fixture-panel-default-templated' ) );358 QUnit.module( 'Customizer Default Panel with Template in Fixture' ); 359 QUnit.test( 'Fixture section exists', function( assert ) { 360 assert.ok( wp.customize.panel.has( 'fixture-panel-default-templated' ) ); 361 361 } ); 362 test( 'Fixture panel has expected content', function () {362 QUnit.test( 'Fixture panel has expected content', function( assert ) { 363 363 var id = 'fixture-panel-default-templated', panel; 364 364 panel = wp.customize.panel( id ); 365 ok( ! panel.params.content );366 ok( !! panel.container );367 ok( !! panel.headContainer );368 ok( !! panel.contentContainer );369 ok( panel.container.has( panel.headContainer ) );370 ok( panel.container.has( panel.contentContainer ) );371 ok( panel.headContainer.is( '.control-panel.control-panel-default' ) );372 ok( 1 === panel.headContainer.find( '> .accordion-section-title' ).length );373 ok( panel.contentContainer.is( '.control-panel-content' ) );374 equal( panel.headContainer.attr( 'aria-owns' ), panel.contentContainer.attr( 'id' ) );365 assert.ok( ! panel.params.content ); 366 assert.ok( !! panel.container ); 367 assert.ok( !! panel.headContainer ); 368 assert.ok( !! panel.contentContainer ); 369 assert.ok( panel.container.has( panel.headContainer ) ); 370 assert.ok( panel.container.has( panel.contentContainer ) ); 371 assert.ok( panel.headContainer.is( '.control-panel.control-panel-default' ) ); 372 assert.ok( 1 === panel.headContainer.find( '> .accordion-section-title' ).length ); 373 assert.ok( panel.contentContainer.is( '.control-panel-content' ) ); 374 assert.equal( panel.headContainer.attr( 'aria-owns' ), panel.contentContainer.attr( 'id' ) ); 375 375 } ); 376 376 377 module( 'Customizer Custom Type Panel (titleless) with Template in Fixture' );378 test( 'Fixture panel exists', function () {379 ok( wp.customize.panel.has( 'fixture-panel-titleless-templated' ) );377 QUnit.module( 'Customizer Custom Type Panel (titleless) with Template in Fixture' ); 378 QUnit.test( 'Fixture panel exists', function( assert ) { 379 assert.ok( wp.customize.panel.has( 'fixture-panel-titleless-templated' ) ); 380 380 } ); 381 test( 'Fixture panel has expected content', function () {381 QUnit.test( 'Fixture panel has expected content', function( assert ) { 382 382 var id = 'fixture-panel-titleless-templated', panel; 383 383 panel = wp.customize.panel( id ); 384 ok( ! panel.params.content );385 ok( !! panel.container );386 ok( !! panel.headContainer );387 ok( !! panel.contentContainer );388 ok( panel.container.has( panel.headContainer ) );389 ok( panel.container.has( panel.contentContainer ) );390 ok( panel.headContainer.is( '.control-panel.control-panel-titleless' ) );391 ok( 0 === panel.headContainer.find( '> .accordion-section-title' ).length );392 ok( panel.contentContainer.is( '.control-panel-content' ) );393 equal( panel.headContainer.attr( 'aria-owns' ), panel.contentContainer.attr( 'id' ) );384 assert.ok( ! panel.params.content ); 385 assert.ok( !! panel.container ); 386 assert.ok( !! panel.headContainer ); 387 assert.ok( !! panel.contentContainer ); 388 assert.ok( panel.container.has( panel.headContainer ) ); 389 assert.ok( panel.container.has( panel.contentContainer ) ); 390 assert.ok( panel.headContainer.is( '.control-panel.control-panel-titleless' ) ); 391 assert.ok( 0 === panel.headContainer.find( '> .accordion-section-title' ).length ); 392 assert.ok( panel.contentContainer.is( '.control-panel-content' ) ); 393 assert.equal( panel.headContainer.attr( 'aria-owns' ), panel.contentContainer.attr( 'id' ) ); 394 394 } ); 395 395 396 module( 'Customizer Custom Type Panel Lacking Specific Template' );397 test( 'Fixture panel has expected content', function () {396 QUnit.module( 'Customizer Custom Type Panel Lacking Specific Template' ); 397 QUnit.test( 'Fixture panel has expected content', function( assert ) { 398 398 var id = 'fixture-panel-reusing-default-template', panel; 399 399 panel = wp.customize.panel( id ); 400 ok( ! panel.params.content );401 ok( !! panel.container );402 ok( !! panel.headContainer );403 ok( !! panel.contentContainer );404 ok( panel.container.has( panel.headContainer ) );405 ok( panel.container.has( panel.contentContainer ) );406 ok( panel.headContainer.is( '.control-panel.control-panel-' + panel.params.type ) );407 ok( 1 === panel.headContainer.find( '> .accordion-section-title' ).length );408 ok( panel.contentContainer.is( '.control-panel-content' ) );409 equal( panel.headContainer.attr( 'aria-owns' ), panel.contentContainer.attr( 'id' ) );400 assert.ok( ! panel.params.content ); 401 assert.ok( !! panel.container ); 402 assert.ok( !! panel.headContainer ); 403 assert.ok( !! panel.contentContainer ); 404 assert.ok( panel.container.has( panel.headContainer ) ); 405 assert.ok( panel.container.has( panel.contentContainer ) ); 406 assert.ok( panel.headContainer.is( '.control-panel.control-panel-' + panel.params.type ) ); 407 assert.ok( 1 === panel.headContainer.find( '> .accordion-section-title' ).length ); 408 assert.ok( panel.contentContainer.is( '.control-panel-content' ) ); 409 assert.equal( panel.headContainer.attr( 'aria-owns' ), panel.contentContainer.attr( 'id' ) ); 410 410 } ); 411 module( 'Customizer Panel lacking any params' );412 test( 'Fixture panel has default params supplied', function () {411 QUnit.module( 'Customizer Panel lacking any params' ); 412 QUnit.test( 'Fixture panel has default params supplied', function( assert ) { 413 413 var id = 'fixture-panel-without-params', panel, defaultParams; 414 414 panel = wp.customize.panel( id ); 415 415 defaultParams = { … … 421 421 active: true 422 422 }; 423 423 jQuery.each( defaultParams, function ( key, value ) { 424 ok( 'undefined' !== typeof panel.params[ key ] );425 equal( value, panel.params[ key ] );424 assert.ok( 'undefined' !== typeof panel.params[ key ] ); 425 assert.equal( value, panel.params[ key ] ); 426 426 } ); 427 ok( _.isNumber( panel.params.instanceNumber ) );427 assert.ok( _.isNumber( panel.params.instanceNumber ) ); 428 428 } ); 429 429 430 module( 'Dynamically-created Customizer Setting Model' );430 QUnit.module( 'Dynamically-created Customizer Setting Model' ); 431 431 settingId = 'new_blogname'; 432 432 settingValue = 'Hello World'; 433 433 434 test( 'Create a new setting', function () {434 QUnit.test( 'Create a new setting', function( assert ) { 435 435 mockSetting = wp.customize.create( 436 436 settingId, 437 437 settingId, … … 441 441 previewer: wp.customize.previewer 442 442 } 443 443 ); 444 equal( mockSetting(), settingValue );445 equal( mockSetting.id, settingId );444 assert.equal( mockSetting(), settingValue ); 445 assert.equal( mockSetting.id, settingId ); 446 446 } ); 447 447 448 module( 'Dynamically-created Customizer Section Model' );448 QUnit.module( 'Dynamically-created Customizer Section Model' ); 449 449 450 450 sectionId = 'mock_title_tagline'; 451 451 sectionContent = '<li id="accordion-section-mock_title_tagline" class="accordion-section control-section control-section-default"> <h3 class="accordion-section-title" tabindex="0"> Section Fixture <span class="screen-reader-text">Press return or enter to open</span> </h3> <ul class="accordion-section-content"> <li class="customize-section-description-container"> <div class="customize-section-title"> <button class="customize-section-back" tabindex="-1"> <span class="screen-reader-text">Back</span> </button> <h3> <span class="customize-action">Customizing ▸ Fixture Panel</span> Section Fixture </h3> </div> </li> </ul> </li>'; … … 467 467 468 468 testCustomizerModel( mockSection, sectionExpectedValues ); 469 469 470 test( 'Section has been embedded', function () {471 equal( mockSection.deferred.embedded.state(), 'resolved' );470 QUnit.test( 'Section has been embedded', function( assert ) { 471 assert.equal( mockSection.deferred.embedded.state(), 'resolved' ); 472 472 } ); 473 473 474 474 wp.customize.section.add( sectionId, mockSection ); 475 475 476 test( 'Section instance added to the wp.customize.section object', function () {477 ok( wp.customize.section.has( sectionId ) );476 QUnit.test( 'Section instance added to the wp.customize.section object', function( assert ) { 477 assert.ok( wp.customize.section.has( sectionId ) ); 478 478 }); 479 479 480 480 sectionInstance = wp.customize.section( sectionId ); 481 481 482 test( 'Section instance has right content when accessed from wp.customize.section()', function () {483 equal( sectionInstance.params.content, sectionContent );482 QUnit.test( 'Section instance has right content when accessed from wp.customize.section()', function( assert ) { 483 assert.equal( sectionInstance.params.content, sectionContent ); 484 484 }); 485 485 486 test( 'Section instance has no children yet', function () {487 equal( sectionInstance.controls().length, 0 );486 QUnit.test( 'Section instance has no children yet', function( assert ) { 487 assert.equal( sectionInstance.controls().length, 0 ); 488 488 }); 489 489 490 module( 'Dynamically-created Customizer Control Model' );490 QUnit.module( 'Dynamically-created Customizer Control Model' ); 491 491 492 492 controlId = 'new_blogname'; 493 493 controlLabel = 'Site Title'; … … 520 520 521 521 testCustomizerModel( mockControl, controlExpectedValues ); 522 522 523 test( 'Control instance does not yet belong to a section.', function () {524 equal( mockControl.section(), undefined );523 QUnit.test( 'Control instance does not yet belong to a section.', function( assert ) { 524 assert.equal( mockControl.section(), undefined ); 525 525 }); 526 test( 'Control has not been embedded yet', function () {527 equal( mockControl.deferred.embedded.state(), 'pending' );526 QUnit.test( 'Control has not been embedded yet', function( assert ) { 527 assert.equal( mockControl.deferred.embedded.state(), 'pending' ); 528 528 } ); 529 529 530 test( 'Control instance has the right selector.', function () {531 equal( mockControl.selector, '#customize-control-new_blogname' );530 QUnit.test( 'Control instance has the right selector.', function( assert ) { 531 assert.equal( mockControl.selector, '#customize-control-new_blogname' ); 532 532 }); 533 533 534 534 wp.customize.control.add( controlId, mockControl ); 535 535 536 test( 'Control instance was added to the control class.', function () {537 ok( wp.customize.control.has( controlId ) );536 QUnit.test( 'Control instance was added to the control class.', function( assert ) { 537 assert.ok( wp.customize.control.has( controlId ) ); 538 538 }); 539 539 540 540 mockControlInstance = wp.customize.control( controlId ); 541 541 542 test( 'Control instance has the right id when accessed from api.control().', function () {543 equal( mockControlInstance.id, controlId );542 QUnit.test( 'Control instance has the right id when accessed from api.control().', function( assert ) { 543 assert.equal( mockControlInstance.id, controlId ); 544 544 }); 545 545 546 test( 'Control section can be set as expected', function () {546 QUnit.test( 'Control section can be set as expected', function( assert ) { 547 547 mockControl.section( mockSection.id ); 548 equal( mockControl.section(), mockSection.id );548 assert.equal( mockControl.section(), mockSection.id ); 549 549 }); 550 test( 'Associating a control with a section allows it to be embedded', function () {551 equal( mockControl.deferred.embedded.state(), 'resolved' );550 QUnit.test( 'Associating a control with a section allows it to be embedded', function( assert ) { 551 assert.equal( mockControl.deferred.embedded.state(), 'resolved' ); 552 552 }); 553 553 554 test( 'Control is now available on section.controls()', function () {555 equal( sectionInstance.controls().length, 1 );556 equal( sectionInstance.controls()[0], mockControl );554 QUnit.test( 'Control is now available on section.controls()', function( assert ) { 555 assert.equal( sectionInstance.controls().length, 1 ); 556 assert.equal( sectionInstance.controls()[0], mockControl ); 557 557 }); 558 558 559 module( 'Dynamically-created Customizer Panel Model' );559 QUnit.module( 'Dynamically-created Customizer Panel Model' ); 560 560 561 561 panelId = 'mockPanelId'; 562 562 panelTitle = 'Mock Panel Title'; … … 584 584 585 585 testCustomizerModel( mockPanel, panelExpectedValues ); 586 586 587 test( 'Panel instance is not contextuallyActive', function () {588 equal( mockPanel.isContextuallyActive(), false );587 QUnit.test( 'Panel instance is not contextuallyActive', function( assert ) { 588 assert.equal( mockPanel.isContextuallyActive(), false ); 589 589 }); 590 590 591 module( 'Test wp.customize.findControlsForSettings' );592 test( 'findControlsForSettings(blogname)', function() {591 QUnit.module( 'Test wp.customize.findControlsForSettings' ); 592 QUnit.test( 'findControlsForSettings(blogname)', function( assert ) { 593 593 var controlsForSettings, settingId = 'fixture-setting', controlId = 'fixture-control'; 594 ok( wp.customize.control.has( controlId ) );595 ok( wp.customize.has( settingId ) );594 assert.ok( wp.customize.control.has( controlId ) ); 595 assert.ok( wp.customize.has( settingId ) ); 596 596 controlsForSettings = wp.customize.findControlsForSettings( [ settingId ] ); 597 ok( _.isObject( controlsForSettings ), 'Response is object' );598 ok( _.isArray( controlsForSettings['fixture-setting'] ), 'Response has a fixture-setting array' );599 equal( 1, controlsForSettings['fixture-setting'].length );600 equal( wp.customize.control( controlId ), controlsForSettings['fixture-setting'][0] );597 assert.ok( _.isObject( controlsForSettings ), 'Response is object' ); 598 assert.ok( _.isArray( controlsForSettings['fixture-setting'] ), 'Response has a fixture-setting array' ); 599 assert.equal( 1, controlsForSettings['fixture-setting'].length ); 600 assert.equal( wp.customize.control( controlId ), controlsForSettings['fixture-setting'][0] ); 601 601 } ); 602 602 603 module( 'Customize Controls wp.customize.dirtyValues' );604 test( 'dirtyValues() returns expected values', function() {603 QUnit.module( 'Customize Controls wp.customize.dirtyValues' ); 604 QUnit.test( 'dirtyValues() returns expected values', function( assert ) { 605 605 wp.customize.state( 'changesetStatus' ).set( 'auto-draft' ); 606 606 wp.customize.each( function( setting ) { 607 607 setting._dirty = false; 608 608 } ); 609 ok( _.isEmpty( wp.customize.dirtyValues() ) );610 ok( _.isEmpty( wp.customize.dirtyValues( { unsaved: false } ) ) );609 assert.ok( _.isEmpty( wp.customize.dirtyValues() ) ); 610 assert.ok( _.isEmpty( wp.customize.dirtyValues( { unsaved: false } ) ) ); 611 611 612 612 wp.customize( 'fixture-setting' )._dirty = true; 613 ok( ! _.isEmpty( wp.customize.dirtyValues() ) );614 ok( _.isEmpty( wp.customize.dirtyValues( { unsaved: true } ) ) );613 assert.ok( ! _.isEmpty( wp.customize.dirtyValues() ) ); 614 assert.ok( _.isEmpty( wp.customize.dirtyValues( { unsaved: true } ) ) ); 615 615 616 616 wp.customize( 'fixture-setting' ).set( 'Modified' ); 617 ok( ! _.isEmpty( wp.customize.dirtyValues() ) );618 ok( ! _.isEmpty( wp.customize.dirtyValues( { unsaved: true } ) ) );619 equal( 'Modified', wp.customize.dirtyValues()['fixture-setting'] );617 assert.ok( ! _.isEmpty( wp.customize.dirtyValues() ) ); 618 assert.ok( ! _.isEmpty( wp.customize.dirtyValues( { unsaved: true } ) ) ); 619 assert.equal( 'Modified', wp.customize.dirtyValues()['fixture-setting'] ); 620 620 621 621 // When the changeset does not exist, all dirty settings are necessarily unsaved. 622 622 wp.customize.state( 'changesetStatus' ).set( '' ); 623 623 wp.customize( 'fixture-setting' )._dirty = true; 624 ok( ! _.isEmpty( wp.customize.dirtyValues() ) );625 ok( ! _.isEmpty( wp.customize.dirtyValues( { unsaved: true } ) ) );624 assert.ok( ! _.isEmpty( wp.customize.dirtyValues() ) ); 625 assert.ok( ! _.isEmpty( wp.customize.dirtyValues( { unsaved: true } ) ) ); 626 626 } ); 627 627 628 module( 'Customize Controls: wp.customize.requestChangesetUpdate()' );629 test( 'requestChangesetUpdate makes request and returns promise', function( assert ) {628 QUnit.module( 'Customize Controls: wp.customize.requestChangesetUpdate()' ); 629 QUnit.test( 'requestChangesetUpdate makes request and returns promise', function( assert ) { 630 630 var request, originalBeforeSetup = jQuery.ajaxSettings.beforeSend; 631 631 632 632 jQuery.ajaxSetup( { … … 677 677 } ); 678 678 } ); 679 679 680 module( 'Customize Utils: wp.customize.utils.getRemainingTime()' );681 test( 'utils.getRemainingTime calculates time correctly', function( assert ) {680 QUnit.module( 'Customize Utils: wp.customize.utils.getRemainingTime()' ); 681 QUnit.test( 'utils.getRemainingTime calculates time correctly', function( assert ) { 682 682 var datetime = '2599-08-06 12:12:13', timeRemaining, timeRemainingWithDateInstance, timeRemaingingWithTimestamp; 683 683 684 684 timeRemaining = wp.customize.utils.getRemainingTime( datetime ); … … 692 692 assert.deepEqual( timeRemaining, timeRemaingingWithTimestamp ); 693 693 }); 694 694 695 module( 'Customize Utils: wp.customize.utils.getCurrentTimestamp()' );696 test( 'utils.getCurrentTimestamp returns timestamp', function( assert ) {695 QUnit.module( 'Customize Utils: wp.customize.utils.getCurrentTimestamp()' ); 696 QUnit.test( 'utils.getCurrentTimestamp returns timestamp', function( assert ) { 697 697 var currentTimeStamp; 698 698 currentTimeStamp = wp.customize.utils.getCurrentTimestamp(); 699 699 assert.equal( typeof currentTimeStamp, 'number' ); 700 700 }); 701 701 702 module( 'Customize Controls: wp.customize.DateTimeControl' );703 test( 'Test DateTimeControl creation and its methods', function( assert ) {702 QUnit.module( 'Customize Controls: wp.customize.DateTimeControl' ); 703 QUnit.test( 'Test DateTimeControl creation and its methods', function( assert ) { 704 704 var control, controlId = 'date_time', section, sectionId = 'fixture-section', 705 705 datetime = '2599-08-06 18:12:13', dateTimeArray, dateTimeArrayInampm, timeString, 706 706 day, year, month, minute, meridian, hour; … … 864 864 wp.customize.control.remove( controlId ); 865 865 }); 866 866 867 module( 'Customize Sections: wp.customize.OuterSection' );868 test( 'Test OuterSection', function( assert ) {867 QUnit.module( 'Customize Sections: wp.customize.OuterSection' ); 868 QUnit.test( 'Test OuterSection', function( assert ) { 869 869 var section, sectionId = 'test_outer_section', body = jQuery( 'body' ), 870 870 defaultSection, defaultSectionId = 'fixture-section'; 871 871 … … 899 899 wp.customize.section.remove( sectionId ); 900 900 }); 901 901 902 module( 'Customize Controls: PreviewLinkControl' );903 test( 'Test PreviewLinkControl creation and its methods', function( assert ) {902 QUnit.module( 'Customize Controls: PreviewLinkControl' ); 903 QUnit.test( 'Test PreviewLinkControl creation and its methods', function( assert ) { 904 904 var section, sectionId = 'publish_settings', newLink; 905 905 906 906 section = wp.customize.section( sectionId ); -
tests/qunit/wp-admin/js/customize-header.js
1 1 /* global wp, sinon */ 2 2 3 3 jQuery( function() { 4 module('Custom Header: ChoiceList', {5 setup: function() {4 QUnit.module('Custom Header: ChoiceList', { 5 beforeEach: function() { 6 6 wp.customize.HeaderTool.currentHeader = new wp.customize.HeaderTool.ImageModel(); 7 7 this.apiStub = sinon.stub(wp.customize, 'get').returns('foo'); 8 8 this.choiceList = new wp.customize.HeaderTool.ChoiceList(); 9 9 }, 10 teardown: function() {10 afterEach: function() { 11 11 this.apiStub.restore(); 12 12 } 13 13 }); 14 14 15 test('should parse _wpCustomizeHeader.uploads into itself', function() {16 equal(this.choiceList.length, 4);15 QUnit.test('should parse _wpCustomizeHeader.uploads into itself', function( assert ) { 16 assert.equal(this.choiceList.length, 4); 17 17 }); 18 18 19 test('should sort by newest first', function() {20 equal(this.choiceList.at(2).get('header').attachment_id, 1);21 equal(this.choiceList.first().get('header').attachment_id, 3);19 QUnit.test('should sort by newest first', function( assert ) { 20 assert.equal(this.choiceList.at(2).get('header').attachment_id, 1); 21 assert.equal(this.choiceList.first().get('header').attachment_id, 3); 22 22 }); 23 23 24 module('Custom Header: DefaultsList', {25 setup: function() {24 QUnit.module('Custom Header: DefaultsList', { 25 beforeEach: function() { 26 26 wp.customize.HeaderTool.currentHeader = new wp.customize.HeaderTool.ImageModel(); 27 27 this.apiStub = sinon.stub(wp.customize, 'get').returns('foo'); 28 28 this.choiceList = new wp.customize.HeaderTool.DefaultsList(); 29 29 }, 30 teardown: function() {30 afterEach: function() { 31 31 this.apiStub.restore(); 32 32 } 33 33 }); 34 34 35 test('it should parse _wpCustomizeHeader.defaults into itself', function() {36 equal(this.choiceList.length, 4);35 QUnit.test('it should parse _wpCustomizeHeader.defaults into itself', function( assert ) { 36 assert.equal(this.choiceList.length, 4); 37 37 }); 38 38 39 test('it parses the default image names', function() {40 equal(this.choiceList.first().get('header').defaultName, 'circle');41 equal(this.choiceList.at(2).get('header').defaultName, 'star');39 QUnit.test('it parses the default image names', function( assert ) { 40 assert.equal(this.choiceList.first().get('header').defaultName, 'circle'); 41 assert.equal(this.choiceList.at(2).get('header').defaultName, 'star'); 42 42 }); 43 43 44 module('Custom Header: HeaderImage shouldBeCropped()', {45 setup: function() {44 QUnit.module('Custom Header: HeaderImage shouldBeCropped()', { 45 beforeEach: function() { 46 46 wp.customize.HeaderTool.currentHeader = new wp.customize.HeaderTool.ImageModel(); 47 47 this.model = new wp.customize.HeaderTool.ImageModel(); 48 48 this.model.set({ … … 52 52 } 53 53 }); 54 54 55 test('should not be cropped when the theme does not support flex width or height and the image has the same dimensions of the theme image', function() {55 QUnit.test('should not be cropped when the theme does not support flex width or height and the image has the same dimensions of the theme image', function( assert ) { 56 56 this.model.set({ 57 57 themeFlexWidth: false, 58 58 themeFlexHeight: false, … … 60 60 imageHeight: 200 61 61 }); 62 62 63 equal(this.model.shouldBeCropped(), false);63 assert.equal(this.model.shouldBeCropped(), false); 64 64 }); 65 65 66 test('should be cropped when the image has the same dimensions of the theme image', function() {66 QUnit.test('should be cropped when the image has the same dimensions of the theme image', function( assert ) { 67 67 this.model.set({ 68 68 themeFlexWidth: false, 69 69 themeFlexHeight: false, … … 71 71 imageHeight: 400 72 72 }); 73 73 74 equal(this.model.shouldBeCropped(), true);74 assert.equal(this.model.shouldBeCropped(), true); 75 75 }); 76 76 77 test('should not be cropped when the theme only supports flex width and the image has the same height as the theme image', function() {77 QUnit.test('should not be cropped when the theme only supports flex width and the image has the same height as the theme image', function( assert ) { 78 78 this.model.set({ 79 79 themeFlexWidth: true, 80 80 themeFlexHeight: false, … … 82 82 imageHeight: 200 83 83 }); 84 84 85 equal(this.model.shouldBeCropped(), false);85 assert.equal(this.model.shouldBeCropped(), false); 86 86 }); 87 87 88 test('should not be cropped when the theme only supports flex height and the image has the same width as the theme image', function() {88 QUnit.test('should not be cropped when the theme only supports flex height and the image has the same width as the theme image', function( assert ) { 89 89 this.model.set({ 90 90 themeFlexWidth: false, 91 91 themeFlexHeight: true, … … 93 93 imageHeight: 600 94 94 }); 95 95 96 equal(this.model.shouldBeCropped(), false);96 assert.equal(this.model.shouldBeCropped(), false); 97 97 }); 98 98 99 test('should not be cropped when the theme supports flex height AND width', function() {99 QUnit.test('should not be cropped when the theme supports flex height AND width', function( assert ) { 100 100 this.model.set({ 101 101 themeFlexWidth: true, 102 102 themeFlexHeight: true, … … 104 104 imageHeight: 8600 105 105 }); 106 106 107 equal(this.model.shouldBeCropped(), false);107 assert.equal(this.model.shouldBeCropped(), false); 108 108 }); 109 109 110 test('should not be cropped when the image width is smaller than or equal to theme width', function() {110 QUnit.test('should not be cropped when the image width is smaller than or equal to theme width', function( assert ) { 111 111 this.model.set({ 112 112 themeFlexWidth: false, 113 113 themeFlexHeight: false, … … 115 115 imageHeight: 100 116 116 }); 117 117 118 equal(this.model.shouldBeCropped(), false);118 assert.equal(this.model.shouldBeCropped(), false); 119 119 }); 120 120 121 test('should not be cropped when the image width is smaller than or equal to theme width, theme supports flex height and width', function() {121 QUnit.test('should not be cropped when the image width is smaller than or equal to theme width, theme supports flex height and width', function( assert ) { 122 122 this.model.set({ 123 123 themeFlexWidth: true, 124 124 themeFlexHeight: true, … … 126 126 imageHeight: 100 127 127 }); 128 128 129 equal(this.model.shouldBeCropped(), false);129 assert.equal(this.model.shouldBeCropped(), false); 130 130 }); 131 131 }); -
tests/qunit/wp-admin/js/customize-nav-menus.js
1 1 /* global wp */ 2 jQuery( window ).load( function (){2 jQuery( window ).load( function() { 3 3 4 4 var api = wp.customize, 5 5 primaryMenuId = 3, 6 6 socialMenuId = 2; 7 7 8 module( 'Customize Nav Menus' );8 QUnit.module( 'Customize Nav Menus' ); 9 9 10 10 /** 11 11 * Generate 20 IDs and verify they are all unique. 12 12 */ 13 test( 'generatePlaceholderAutoIncrementId generates unique IDs', function() {13 QUnit.test( 'generatePlaceholderAutoIncrementId generates unique IDs', function( assert ) { 14 14 var testIterations = 20, 15 15 ids = [ api.Menus.generatePlaceholderAutoIncrementId() ]; 16 16 … … 17 17 while ( testIterations ) { 18 18 var placeholderID = api.Menus.generatePlaceholderAutoIncrementId(); 19 19 20 ok( -1 === ids.indexOf( placeholderID ) );20 assert.ok( -1 === ids.indexOf( placeholderID ) ); 21 21 ids.push( placeholderID ); 22 22 testIterations -= 1; 23 23 } … … 24 24 25 25 } ); 26 26 27 test( 'it should parse _wpCustomizeMenusSettings.defaults into itself', function() {28 deepEqual( window._wpCustomizeNavMenusSettings, api.Menus.data );27 QUnit.test( 'it should parse _wpCustomizeMenusSettings.defaults into itself', function( assert ) { 28 assert.deepEqual( window._wpCustomizeNavMenusSettings, api.Menus.data ); 29 29 } ); 30 30 31 test( 'empty menus should have no Menu Item Controls', function() {32 ok( 0 === wp.customize.Menus.getMenuControl( socialMenuId ).getMenuItemControls().length, 'empty menus' );31 QUnit.test( 'empty menus should have no Menu Item Controls', function( assert ) { 32 assert.ok( 0 === wp.customize.Menus.getMenuControl( socialMenuId ).getMenuItemControls().length, 'empty menus' ); 33 33 } ); 34 34 35 test( 'populated menus should have no Menu Item Controls', function() {36 ok( 0 !== wp.customize.Menus.getMenuControl( primaryMenuId ).getMenuItemControls().length, 'non-empty menus' );35 QUnit.test( 'populated menus should have no Menu Item Controls', function( assert ) { 36 assert.ok( 0 !== wp.customize.Menus.getMenuControl( primaryMenuId ).getMenuItemControls().length, 'non-empty menus' ); 37 37 } ); 38 38 39 39 // @todo Add tests for api.Menus.AvailableMenuItemsPanelView 40 40 // (and api.Menus.AvailableItemCollection, api.Menus.AvailableItemModel). 41 41 42 test( 'there is a properly configured MenusPanel', function() {42 QUnit.test( 'there is a properly configured MenusPanel', function( assert ) { 43 43 var panel, sections; 44 44 45 45 panel = api.panel( 'nav_menus' ); 46 ok( panel );47 ok( panel.extended( api.Menus.MenusPanel ) );46 assert.ok( panel ); 47 assert.ok( panel.extended( api.Menus.MenusPanel ) ); 48 48 49 49 sections = panel.sections(); 50 ok( 'menu_locations' === sections[0].id, 'first section is menu_locations' );51 ok( sections[1].extended( api.Menus.MenuSection ), 'second section is MenuSection' );52 ok( sections[ sections.length - 1 ].extended( api.Menus.NewMenuSection ), 'last section is NewMenuSection' );50 assert.ok( 'menu_locations' === sections[0].id, 'first section is menu_locations' ); 51 assert.ok( sections[1].extended( api.Menus.MenuSection ), 'second section is MenuSection' ); 52 assert.ok( sections[ sections.length - 1 ].extended( api.Menus.NewMenuSection ), 'last section is NewMenuSection' ); 53 53 } ); 54 54 // @todo Add more tests for api.Menus.MenusPanel behaviors. 55 55 56 test( 'there an expected MenuSection for the primary menu', function() {56 QUnit.test( 'there an expected MenuSection for the primary menu', function( assert ) { 57 57 var section, controls, lastControl; 58 58 59 59 section = api.section( 'nav_menu[' + primaryMenuId + ']' ); 60 ok( section, 'section exists' );61 ok( section.extended( api.Menus.MenuSection ), 'section is a api.Menus.MenuSection' );62 ok( section.deferred.initSortables, 'has section.deferred.initSortables' );63 ok( section.active(), 'section active() is true' );64 ok( section.active.set( false ).get(), 'section active() cannot be set false' );60 assert.ok( section, 'section exists' ); 61 assert.ok( section.extended( api.Menus.MenuSection ), 'section is a api.Menus.MenuSection' ); 62 assert.ok( section.deferred.initSortables, 'has section.deferred.initSortables' ); 63 assert.ok( section.active(), 'section active() is true' ); 64 assert.ok( section.active.set( false ).get(), 'section active() cannot be set false' ); 65 65 66 66 controls = section.controls(); 67 ok( controls[0].extended( api.Menus.MenuNameControl ), 'first control in menu section is MenuNameControl' );68 ok( controls[1].extended( api.Menus.MenuItemControl ), 'second control in menu section is MenuItemControl' );67 assert.ok( controls[0].extended( api.Menus.MenuNameControl ), 'first control in menu section is MenuNameControl' ); 68 assert.ok( controls[1].extended( api.Menus.MenuItemControl ), 'second control in menu section is MenuItemControl' ); 69 69 70 70 lastControl = controls[ controls.length - 1 ]; 71 ok( lastControl.extended( api.Control ), 'last control in menu section is a base Control' );72 ok( lastControl.params.templateId === 'nav-menu-delete-button', 'last control in menu section has a delete-button template' );71 assert.ok( lastControl.extended( api.Control ), 'last control in menu section is a base Control' ); 72 assert.ok( lastControl.params.templateId === 'nav-menu-delete-button', 'last control in menu section has a delete-button template' ); 73 73 } ); 74 74 // @todo Add more tests for api.Menus.MenuSection behaviors. 75 75 76 test( 'changing a MenuNameControl change the corresponding menu value', function() {76 QUnit.test( 'changing a MenuNameControl change the corresponding menu value', function( assert ) { 77 77 var section, control; 78 78 79 79 section = api.section( 'nav_menu[' + primaryMenuId + ']' ); 80 80 control = section.controls()[0]; 81 ok( control.extended( api.Menus.MenuNameControl ), 'control is a MenuNameControl' );82 equal( control.setting().name, 'Primary menu' );83 ok( ! control.setting._dirty );81 assert.ok( control.extended( api.Menus.MenuNameControl ), 'control is a MenuNameControl' ); 82 assert.equal( control.setting().name, 'Primary menu' ); 83 assert.ok( ! control.setting._dirty ); 84 84 control.container.find( 'input[type=text]:first' ).val( 'Main menu' ).trigger( 'change' ); 85 equal( control.setting().name, 'Main menu' );86 ok( control.setting._dirty );85 assert.equal( control.setting().name, 'Main menu' ); 86 assert.ok( control.setting._dirty ); 87 87 } ); 88 88 // @todo Add more tests for api.Menus.MenuNameControl 89 89 90 test( 'manipulating a MenuItemControl works', function() {90 QUnit.test( 'manipulating a MenuItemControl works', function( assert ) { 91 91 var section, control, value; 92 92 section = api.section( 'nav_menu[' + primaryMenuId + ']' ); 93 ok( section );93 assert.ok( section ); 94 94 95 95 control = section.controls()[1]; 96 ok( control.extended( api.Menus.MenuItemControl ), 'control is a MenuItemControl' );96 assert.ok( control.extended( api.Menus.MenuItemControl ), 'control is a MenuItemControl' ); 97 97 98 98 control.actuallyEmbed(); 99 99 100 100 control.container.find( '.edit-menu-item-title' ).val( 'Hello World' ).trigger( 'change' ); 101 equal( control.setting().title, 'Hello World' );101 assert.equal( control.setting().title, 'Hello World' ); 102 102 value = _.clone( control.setting() ); 103 103 value.title = 'Hola Mundo'; 104 equal( control.container.find( '.edit-menu-item-title' ).val(), 'Hello World' );105 equal( value.position, 1 );106 equal( control.priority(), 1 );104 assert.equal( control.container.find( '.edit-menu-item-title' ).val(), 'Hello World' ); 105 assert.equal( value.position, 1 ); 106 assert.equal( control.priority(), 1 ); 107 107 108 108 // @todo Test control.moveDown(). 109 109 } ); … … 118 118 // @todo Add tests for api.Menus.focusMenuItemControl. 119 119 // @todo Add tests for api.Menus.createNavMenu. 120 120 121 test( 'api.Menus.getMenuControl() should return the expected control', function() {121 QUnit.test( 'api.Menus.getMenuControl() should return the expected control', function( assert ) { 122 122 var control = api.Menus.getMenuControl( primaryMenuId ); 123 ok( !! control, 'control is returned' );124 ok( control.extended( api.Menus.MenuControl ), 'control is a MenuControl' );123 assert.ok( !! control, 'control is returned' ); 124 assert.ok( control.extended( api.Menus.MenuControl ), 'control is a MenuControl' ); 125 125 } ); 126 126 127 test( 'api.Menus.getMenuItemControl() should return the expected control', function() {127 QUnit.test( 'api.Menus.getMenuItemControl() should return the expected control', function( assert ) { 128 128 var control = api.Menus.getMenuItemControl( 2000 ); 129 ok( !! control, 'control is returned' );130 ok( control.extended( api.Menus.MenuItemControl ), 'control is a MenuItemControl' );129 assert.ok( !! control, 'control is returned' ); 130 assert.ok( control.extended( api.Menus.MenuItemControl ), 'control is a MenuItemControl' ); 131 131 } ); 132 132 133 133 } ); -
tests/qunit/wp-admin/js/customize-widgets.js
3 3 4 4 var api = wp.customize, $ = jQuery; 5 5 6 module( 'Customize Widgets' );6 QUnit.module( 'Customize Widgets' ); 7 7 8 test( 'fixtures should be present', function() {8 QUnit.test( 'fixtures should be present', function( assert ) { 9 9 var widgetControl; 10 ok( api.panel( 'widgets' ) );11 ok( api.section( 'sidebar-widgets-sidebar-1' ) );10 assert.ok( api.panel( 'widgets' ) ); 11 assert.ok( api.section( 'sidebar-widgets-sidebar-1' ) ); 12 12 widgetControl = api.control( 'widget_search[2]' ); 13 ok( widgetControl );14 ok( api.control( 'sidebars_widgets[sidebar-1]' ) );15 ok( api( 'widget_search[2]' ) );16 ok( api( 'sidebars_widgets[sidebar-1]' ) );17 ok( widgetControl.params.content );18 ok( widgetControl.params.widget_control );19 ok( widgetControl.params.widget_content );20 ok( widgetControl.params.widget_id );21 ok( widgetControl.params.widget_id_base );13 assert.ok( widgetControl ); 14 assert.ok( api.control( 'sidebars_widgets[sidebar-1]' ) ); 15 assert.ok( api( 'widget_search[2]' ) ); 16 assert.ok( api( 'sidebars_widgets[sidebar-1]' ) ); 17 assert.ok( widgetControl.params.content ); 18 assert.ok( widgetControl.params.widget_control ); 19 assert.ok( widgetControl.params.widget_content ); 20 assert.ok( widgetControl.params.widget_id ); 21 assert.ok( widgetControl.params.widget_id_base ); 22 22 }); 23 23 24 test( 'widget contents should embed (with widget-added event) when section and control expand', function() {24 QUnit.test( 'widget contents should embed (with widget-added event) when section and control expand', function( assert ) { 25 25 var control, section, widgetAddedEvent = null, widgetControlRootElement = null; 26 26 control = api.control( 'widget_search[2]' ); 27 27 section = api.section( 'sidebar-widgets-sidebar-1' ); … … 31 31 widgetControlRootElement = widgetElement; 32 32 }); 33 33 34 ok( ! section.expanded() );35 ok( 0 === control.container.find( '> .widget' ).length );34 assert.ok( ! section.expanded() ); 35 assert.ok( 0 === control.container.find( '> .widget' ).length ); 36 36 37 37 // Preview sets the active state. 38 38 section.active.set( true ); … … 40 40 api.control( 'sidebars_widgets[sidebar-1]' ).active.set( true ); 41 41 42 42 section.expand(); 43 ok( ! widgetAddedEvent, 'expected widget added event not fired' );44 ok( 1 === control.container.find( '> .widget' ).length, 'expected there to be one .widget element in the container' );45 ok( 0 === control.container.find( '.widget-content' ).children().length );43 assert.ok( ! widgetAddedEvent, 'expected widget added event not fired' ); 44 assert.ok( 1 === control.container.find( '> .widget' ).length, 'expected there to be one .widget element in the container' ); 45 assert.ok( 0 === control.container.find( '.widget-content' ).children().length ); 46 46 47 47 control.expand(); 48 ok( 1 === control.container.find( '.widget-content' ).children().length );49 ok( widgetAddedEvent );50 ok( widgetControlRootElement.is( control.container.find( '> .widget' ) ) );51 ok( 1 === control.container.find( '.widget-content #widget-search-2-title' ).length );48 assert.ok( 1 === control.container.find( '.widget-content' ).children().length ); 49 assert.ok( widgetAddedEvent ); 50 assert.ok( widgetControlRootElement.is( control.container.find( '> .widget' ) ) ); 51 assert.ok( 1 === control.container.find( '.widget-content #widget-search-2-title' ).length ); 52 52 53 53 $( document ).off( 'widget-added' ); 54 54 }); 55 55 56 test( 'widgets panel should have notice', function() {56 QUnit.test( 'widgets panel should have notice', function( assert ) { 57 57 var panel = api.panel( 'widgets' ); 58 ok( panel.extended( api.Widgets.WidgetsPanel ) );58 assert.ok( panel.extended( api.Widgets.WidgetsPanel ) ); 59 59 60 60 panel.deferred.embedded.done( function() { 61 ok( 1 === panel.contentContainer.find( '.no-widget-areas-rendered-notice' ).length );62 ok( panel.contentContainer.find( '.no-widget-areas-rendered-notice' ).is( ':visible' ) );61 assert.ok( 1 === panel.contentContainer.find( '.no-widget-areas-rendered-notice' ).length ); 62 assert.ok( panel.contentContainer.find( '.no-widget-areas-rendered-notice' ).is( ':visible' ) ); 63 63 api.section( 'sidebar-widgets-sidebar-1' ).active( true ); 64 64 api.control( 'sidebars_widgets[sidebar-1]' ).active( true ); 65 65 api.trigger( 'pane-contents-reflowed' ); 66 ok( ! panel.contentContainer.find( '.no-widget-areas-rendered-notice' ).is( ':visible' ) );66 assert.ok( ! panel.contentContainer.find( '.no-widget-areas-rendered-notice' ).is( ':visible' ) ); 67 67 } ); 68 68 69 expect( 4 );69 assert.expect( 4 ); 70 70 }); 71 71 }); -
tests/qunit/wp-admin/js/nav-menu.js
6 6 eventsFired = 0; 7 7 8 8 // Fail if we don't see the expected number of events triggered in 3 seconds. 9 setTimeout( function( ) {9 setTimeout( function( assert ) { 10 10 // QUnit may load this file without running it, in which case `assert` 11 11 // will never be set to `assertPassed` below. 12 12 assert && assert.equal( -
tests/qunit/wp-admin/js/password-strength-meter.js
1 1 /* global passwordStrength, wp, jQuery */ 2 2 jQuery( function() { 3 module( 'password-strength-meter' );3 QUnit.module( 'password-strength-meter' ); 4 4 5 test( 'mismatched passwords should return 5', function() {6 equal( passwordStrength( 'password1', 'username', 'password2' ), 5, 'mismatched passwords return 5' );5 QUnit.test( 'mismatched passwords should return 5', function( assert ) { 6 assert.equal( passwordStrength( 'password1', 'username', 'password2' ), 5, 'mismatched passwords return 5' ); 7 7 }); 8 8 9 test( 'passwords shorter than 4 characters should return 0', function() {10 equal( passwordStrength( 'abc', 'username', 'abc' ), 0, 'short passwords return 0' );9 QUnit.test( 'passwords shorter than 4 characters should return 0', function( assert ) { 10 assert.equal( passwordStrength( 'abc', 'username', 'abc' ), 0, 'short passwords return 0' ); 11 11 }); 12 12 13 test( 'long complicated passwords should return 4', function() {13 QUnit.test( 'long complicated passwords should return 4', function( assert ) { 14 14 var password = function( length ) { 15 15 var i, n, retVal = '', 16 16 possibility = 'abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; … … 21 21 }, 22 22 twofifty = password( 250 ); 23 23 24 equal( passwordStrength( twofifty, 'username', twofifty ), 4, '250 character complicated password returns 4' );24 assert.equal( passwordStrength( twofifty, 'username', twofifty ), 4, '250 character complicated password returns 4' ); 25 25 }); 26 26 27 test( 'short uncomplicated passwords should return 0', function() {27 QUnit.test( 'short uncomplicated passwords should return 0', function( assert ) { 28 28 var letters = 'aaaa', 29 29 numbers = '1111', 30 30 password = 'password', 31 31 uppercase = 'AAAA'; 32 equal( passwordStrength( letters, 'username', letters ), 0, 'password of `' + letters + '` returns 0' );33 equal( passwordStrength( numbers, 'username', numbers ), 0, 'password of `' + numbers + '` returns 0' );34 equal( passwordStrength( uppercase, 'username', uppercase ), 0, 'password of `' + uppercase + '` returns 0' );35 equal( passwordStrength( password, 'username', password ), 0, 'password of `' + password + '` returns 0' );32 assert.equal( passwordStrength( letters, 'username', letters ), 0, 'password of `' + letters + '` returns 0' ); 33 assert.equal( passwordStrength( numbers, 'username', numbers ), 0, 'password of `' + numbers + '` returns 0' ); 34 assert.equal( passwordStrength( uppercase, 'username', uppercase ), 0, 'password of `' + uppercase + '` returns 0' ); 35 assert.equal( passwordStrength( password, 'username', password ), 0, 'password of `' + password + '` returns 0' ); 36 36 }); 37 37 38 test( 'zxcvbn password tests should return the score we expect', function() {38 QUnit.test( 'zxcvbn password tests should return the score we expect', function( assert ) { 39 39 var passwords, i; 40 40 passwords = [ 41 41 { pw: 'zxcvbn', score: 0 }, … … 76 76 ]; 77 77 78 78 for ( i = 0; i < passwords.length; i++ ) { 79 equal( passwordStrength( passwords[i].pw, 'username', passwords[i].pw ), passwords[i].score, 'password of `' + passwords[i].pw + '` returns ' + passwords[i].score );79 assert.equal( passwordStrength( passwords[i].pw, 'username', passwords[i].pw ), passwords[i].score, 'password of `' + passwords[i].pw + '` returns ' + passwords[i].score ); 80 80 } 81 81 }); 82 82 83 test( 'blacklisted words in password should be penalized', function() {83 QUnit.test( 'blacklisted words in password should be penalized', function( assert ) { 84 84 var allowedPasswordScore, penalizedPasswordScore, 85 85 allowedPassword = 'a[janedoefoe]4', 86 86 penalizedPassword = 'a[johndoefoe]4', … … 89 89 allowedPasswordScore = passwordStrength( allowedPassword, blacklist, allowedPassword ); 90 90 penalizedPasswordScore = passwordStrength( penalizedPassword, blacklist, penalizedPassword ); 91 91 92 ok( penalizedPasswordScore < allowedPasswordScore, 'Penalized password scored ' + penalizedPasswordScore + '; allowed password scored: ' + allowedPasswordScore );92 assert.ok( penalizedPasswordScore < allowedPasswordScore, 'Penalized password scored ' + penalizedPasswordScore + '; allowed password scored: ' + allowedPasswordScore ); 93 93 }); 94 94 95 test( 'user input blacklist array should contain expected words', function() {95 QUnit.test( 'user input blacklist array should contain expected words', function( assert ) { 96 96 var blacklist = wp.passwordStrength.userInputBlacklist(); 97 97 98 ok( jQuery.isArray( blacklist ), 'blacklist is an array' );99 ok( jQuery.inArray( 'WordPress', blacklist ) > -1, 'blacklist contains "WordPress" from page title' );100 ok( jQuery.inArray( 'tests', blacklist ) > -1, 'blacklist contains "tests" from site URL' );98 assert.ok( jQuery.isArray( blacklist ), 'blacklist is an array' ); 99 assert.ok( jQuery.inArray( 'WordPress', blacklist ) > -1, 'blacklist contains "WordPress" from page title' ); 100 assert.ok( jQuery.inArray( 'tests', blacklist ) > -1, 'blacklist contains "tests" from site URL' ); 101 101 }); 102 102 }); -
tests/qunit/wp-admin/js/widgets/test-media-gallery-widget.js
6 6 ( function() { 7 7 'use strict'; 8 8 9 module( 'Gallery Media Widget' );9 QUnit.module( 'Gallery Media Widget' ); 10 10 11 test( 'gallery widget control', function() {11 QUnit.test( 'gallery widget control', function( assert ) { 12 12 var GalleryWidgetControl; 13 equal( typeof wp.mediaWidgets.controlConstructors.media_gallery, 'function', 'wp.mediaWidgets.controlConstructors.media_gallery is a function' );13 assert.equal( typeof wp.mediaWidgets.controlConstructors.media_gallery, 'function', 'wp.mediaWidgets.controlConstructors.media_gallery is a function' ); 14 14 GalleryWidgetControl = wp.mediaWidgets.controlConstructors.media_gallery; 15 ok( GalleryWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_gallery subclasses wp.mediaWidgets.MediaWidgetControl' );15 assert.ok( GalleryWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_gallery subclasses wp.mediaWidgets.MediaWidgetControl' ); 16 16 }); 17 17 18 test( 'gallery media model', function() {18 QUnit.test( 'gallery media model', function( assert ) { 19 19 var GalleryWidgetModel, galleryWidgetModelInstance; 20 equal( typeof wp.mediaWidgets.modelConstructors.media_gallery, 'function', 'wp.mediaWidgets.modelConstructors.media_gallery is a function' );20 assert.equal( typeof wp.mediaWidgets.modelConstructors.media_gallery, 'function', 'wp.mediaWidgets.modelConstructors.media_gallery is a function' ); 21 21 GalleryWidgetModel = wp.mediaWidgets.modelConstructors.media_gallery; 22 ok( GalleryWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_gallery subclasses wp.mediaWidgets.MediaWidgetModel' );22 assert.ok( GalleryWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_gallery subclasses wp.mediaWidgets.MediaWidgetModel' ); 23 23 24 24 galleryWidgetModelInstance = new GalleryWidgetModel(); 25 25 _.each( galleryWidgetModelInstance.attributes, function( value, key ) { 26 equal( value, GalleryWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key );26 assert.equal( value, GalleryWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key ); 27 27 }); 28 28 }); 29 29 -
tests/qunit/wp-admin/js/widgets/test-media-image-widget.js
6 6 ( function() { 7 7 'use strict'; 8 8 9 module( 'Image Media Widget' );9 QUnit.module( 'Image Media Widget' ); 10 10 11 test( 'image widget control', function() {11 QUnit.test( 'image widget control', function( assert ) { 12 12 var ImageWidgetControl, imageWidgetControlInstance, imageWidgetModelInstance, mappedProps, testImageUrl, templateProps; 13 13 testImageUrl = 'http://s.w.org/style/images/wp-header-logo.png'; 14 equal( typeof wp.mediaWidgets.controlConstructors.media_image, 'function', 'wp.mediaWidgets.controlConstructors.media_image is a function' );14 assert.equal( typeof wp.mediaWidgets.controlConstructors.media_image, 'function', 'wp.mediaWidgets.controlConstructors.media_image is a function' ); 15 15 ImageWidgetControl = wp.mediaWidgets.controlConstructors.media_image; 16 ok( ImageWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_image subclasses wp.mediaWidgets.MediaWidgetControl' );16 assert.ok( ImageWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_image subclasses wp.mediaWidgets.MediaWidgetControl' ); 17 17 18 18 imageWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_image(); 19 19 imageWidgetControlInstance = new ImageWidgetControl({ … … 24 24 25 25 // Test mapModelToPreviewTemplateProps() when no data is set. 26 26 templateProps = imageWidgetControlInstance.mapModelToPreviewTemplateProps(); 27 equal( templateProps.caption, undefined, 'mapModelToPreviewTemplateProps should not return attributes that are should_preview_update false' );28 equal( templateProps.attachment_id, 0, 'mapModelToPreviewTemplateProps should return default values' );29 equal( templateProps.currentFilename, '', 'mapModelToPreviewTemplateProps should return a currentFilename' );27 assert.equal( templateProps.caption, undefined, 'mapModelToPreviewTemplateProps should not return attributes that are should_preview_update false' ); 28 assert.equal( templateProps.attachment_id, 0, 'mapModelToPreviewTemplateProps should return default values' ); 29 assert.equal( templateProps.currentFilename, '', 'mapModelToPreviewTemplateProps should return a currentFilename' ); 30 30 31 31 // Test mapModelToPreviewTemplateProps() when data is set on model. 32 32 imageWidgetControlInstance.model.set( { url: testImageUrl, alt: 'some alt text', link_type: 'none' } ); 33 33 templateProps = imageWidgetControlInstance.mapModelToPreviewTemplateProps(); 34 equal( templateProps.currentFilename, 'wp-header-logo.png', 'mapModelToPreviewTemplateProps should set currentFilename based off of url' );35 equal( templateProps.url, testImageUrl, 'mapModelToPreviewTemplateProps should return the proper url' );36 equal( templateProps.alt, 'some alt text', 'mapModelToPreviewTemplateProps should return the proper alt text' );37 equal( templateProps.link_type, undefined, 'mapModelToPreviewTemplateProps should ignore attributes that are not needed in the preview' );38 equal( templateProps.error, false, 'mapModelToPreviewTemplateProps should return error state' );34 assert.equal( templateProps.currentFilename, 'wp-header-logo.png', 'mapModelToPreviewTemplateProps should set currentFilename based off of url' ); 35 assert.equal( templateProps.url, testImageUrl, 'mapModelToPreviewTemplateProps should return the proper url' ); 36 assert.equal( templateProps.alt, 'some alt text', 'mapModelToPreviewTemplateProps should return the proper alt text' ); 37 assert.equal( templateProps.link_type, undefined, 'mapModelToPreviewTemplateProps should ignore attributes that are not needed in the preview' ); 38 assert.equal( templateProps.error, false, 'mapModelToPreviewTemplateProps should return error state' ); 39 39 40 40 // Test mapModelToPreviewTemplateProps() when error is set on model. 41 41 imageWidgetControlInstance.model.set( 'error', 'missing_attachment' ); 42 42 templateProps = imageWidgetControlInstance.mapModelToPreviewTemplateProps(); 43 equal( templateProps.error, 'missing_attachment', 'mapModelToPreviewTemplateProps should return error string' );43 assert.equal( templateProps.error, 'missing_attachment', 'mapModelToPreviewTemplateProps should return error string' ); 44 44 45 45 // Reset model. 46 46 imageWidgetControlInstance.model.set({ error: false, attachment_id: 0, url: null }); 47 47 48 48 // Test isSelected(). 49 equal( imageWidgetControlInstance.isSelected(), false, 'media_image.isSelected() should return false when no media is selected' );49 assert.equal( imageWidgetControlInstance.isSelected(), false, 'media_image.isSelected() should return false when no media is selected' ); 50 50 imageWidgetControlInstance.model.set({ error: 'missing_attachment', attachment_id: 777 }); 51 equal( imageWidgetControlInstance.isSelected(), false, 'media_image.isSelected() should return false when media is selected and error is set' );51 assert.equal( imageWidgetControlInstance.isSelected(), false, 'media_image.isSelected() should return false when media is selected and error is set' ); 52 52 imageWidgetControlInstance.model.set({ error: false, attachment_id: 777 }); 53 equal( imageWidgetControlInstance.isSelected(), true, 'media_image.isSelected() should return true when media is selected and no error exists' );53 assert.equal( imageWidgetControlInstance.isSelected(), true, 'media_image.isSelected() should return true when media is selected and no error exists' ); 54 54 imageWidgetControlInstance.model.set({ error: false, attachment_id: 0, url: testImageUrl }); 55 equal( imageWidgetControlInstance.isSelected(), true, 'media_image.isSelected() should return true when url is set and no error exists' );55 assert.equal( imageWidgetControlInstance.isSelected(), true, 'media_image.isSelected() should return true when url is set and no error exists' ); 56 56 57 57 // Reset model. 58 58 imageWidgetControlInstance.model.set({ error: false, attachment_id: 0, url: null }); … … 60 60 // Test editing of widget title. 61 61 imageWidgetControlInstance.render(); 62 62 imageWidgetControlInstance.$el.find( '.title' ).val( 'Chicken and Ribs' ).trigger( 'input' ); 63 equal( imageWidgetModelInstance.get( 'title' ), 'Chicken and Ribs', 'Changing title should update model title attribute' );63 assert.equal( imageWidgetModelInstance.get( 'title' ), 'Chicken and Ribs', 'Changing title should update model title attribute' ); 64 64 65 65 // Test mapMediaToModelProps. 66 66 mappedProps = imageWidgetControlInstance.mapMediaToModelProps( { link: 'file', url: testImageUrl } ); 67 equal( mappedProps.link_url, testImageUrl, 'mapMediaToModelProps should set file link_url according to mediaFrameProps.link' );67 assert.equal( mappedProps.link_url, testImageUrl, 'mapMediaToModelProps should set file link_url according to mediaFrameProps.link' ); 68 68 mappedProps = imageWidgetControlInstance.mapMediaToModelProps( { link: 'post', postUrl: 'https://wordpress.org/image-2/' } ); 69 equal( mappedProps.link_url, 'https://wordpress.org/image-2/', 'mapMediaToModelProps should set file link_url according to mediaFrameProps.link' );69 assert.equal( mappedProps.link_url, 'https://wordpress.org/image-2/', 'mapMediaToModelProps should set file link_url according to mediaFrameProps.link' ); 70 70 mappedProps = imageWidgetControlInstance.mapMediaToModelProps( { link: 'custom', linkUrl: 'https://wordpress.org' } ); 71 equal( mappedProps.link_url, 'https://wordpress.org', 'mapMediaToModelProps should set custom link_url according to mediaFrameProps.linkUrl' );71 assert.equal( mappedProps.link_url, 'https://wordpress.org', 'mapMediaToModelProps should set custom link_url according to mediaFrameProps.linkUrl' ); 72 72 73 73 // Test mapModelToMediaFrameProps(). 74 74 imageWidgetControlInstance.model.set({ error: false, url: testImageUrl, 'link_type': 'custom', 'link_url': 'https://wordpress.org', 'size': 'custom', 'width': 100, 'height': 150, 'title': 'widget title', 'image_title': 'title of image' }); 75 75 mappedProps = imageWidgetControlInstance.mapModelToMediaFrameProps( imageWidgetControlInstance.model.toJSON() ); 76 equal( mappedProps.linkUrl, 'https://wordpress.org', 'mapModelToMediaFrameProps should set linkUrl from model.link_url' );77 equal( mappedProps.link, 'custom', 'mapModelToMediaFrameProps should set link from model.link_type' );78 equal( mappedProps.width, 100, 'mapModelToMediaFrameProps should set width when model.size is custom' );79 equal( mappedProps.height, 150, 'mapModelToMediaFrameProps should set height when model.size is custom' );80 equal( mappedProps.title, 'title of image', 'mapModelToMediaFrameProps should set title from model.image_title' );76 assert.equal( mappedProps.linkUrl, 'https://wordpress.org', 'mapModelToMediaFrameProps should set linkUrl from model.link_url' ); 77 assert.equal( mappedProps.link, 'custom', 'mapModelToMediaFrameProps should set link from model.link_type' ); 78 assert.equal( mappedProps.width, 100, 'mapModelToMediaFrameProps should set width when model.size is custom' ); 79 assert.equal( mappedProps.height, 150, 'mapModelToMediaFrameProps should set height when model.size is custom' ); 80 assert.equal( mappedProps.title, 'title of image', 'mapModelToMediaFrameProps should set title from model.image_title' ); 81 81 }); 82 82 83 test( 'image widget control renderPreview', function( assert ) {83 QUnit.test( 'image widget control renderPreview', function( assert ) { 84 84 var imageWidgetControlInstance, imageWidgetModelInstance, done; 85 85 done = assert.async(); 86 86 … … 90 90 syncContainer: jQuery( '<div></div>' ), 91 91 model: imageWidgetModelInstance 92 92 }); 93 equal( imageWidgetControlInstance.$el.find( 'img' ).length, 0, 'No images should be rendered' );93 assert.equal( imageWidgetControlInstance.$el.find( 'img' ).length, 0, 'No images should be rendered' ); 94 94 imageWidgetControlInstance.model.set({ error: false, url: 'http://s.w.org/style/images/wp-header-logo.png' }); 95 95 96 96 // Due to renderPreview being deferred. 97 97 setTimeout( function() { 98 equal( imageWidgetControlInstance.$el.find( 'img[src="http://s.w.org/style/images/wp-header-logo.png"]' ).length, 1, 'One image should be rendered' );98 assert.equal( imageWidgetControlInstance.$el.find( 'img[src="http://s.w.org/style/images/wp-header-logo.png"]' ).length, 1, 'One image should be rendered' ); 99 99 done(); 100 100 }, 50 ); 101 101 102 start();102 done(); 103 103 }); 104 104 105 test( 'image media model', function() {105 QUnit.test( 'image media model', function( assert ) { 106 106 var ImageWidgetModel, imageWidgetModelInstance; 107 equal( typeof wp.mediaWidgets.modelConstructors.media_image, 'function', 'wp.mediaWidgets.modelConstructors.media_image is a function' );107 assert.equal( typeof wp.mediaWidgets.modelConstructors.media_image, 'function', 'wp.mediaWidgets.modelConstructors.media_image is a function' ); 108 108 ImageWidgetModel = wp.mediaWidgets.modelConstructors.media_image; 109 ok( ImageWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_image subclasses wp.mediaWidgets.MediaWidgetModel' );109 assert.ok( ImageWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_image subclasses wp.mediaWidgets.MediaWidgetModel' ); 110 110 111 111 imageWidgetModelInstance = new ImageWidgetModel(); 112 112 _.each( imageWidgetModelInstance.attributes, function( value, key ) { 113 equal( value, ImageWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key );113 assert.equal( value, ImageWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key ); 114 114 }); 115 115 }); 116 116 -
tests/qunit/wp-admin/js/widgets/test-media-video-widget.js
6 6 ( function() { 7 7 'use strict'; 8 8 9 module( 'Video Media Widget' );9 QUnit.module( 'Video Media Widget' ); 10 10 11 test( 'video widget control', function() {11 QUnit.test( 'video widget control', function( assert ) { 12 12 var VideoWidgetControl, videoWidgetControlInstance, videoWidgetModelInstance, mappedProps, testVideoUrl; 13 13 testVideoUrl = 'https://videos.files.wordpress.com/AHz0Ca46/wp4-7-vaughan-r8-mastered_hd.mp4'; 14 equal( typeof wp.mediaWidgets.controlConstructors.media_video, 'function', 'wp.mediaWidgets.controlConstructors.media_video is a function' );14 assert.equal( typeof wp.mediaWidgets.controlConstructors.media_video, 'function', 'wp.mediaWidgets.controlConstructors.media_video is a function' ); 15 15 VideoWidgetControl = wp.mediaWidgets.controlConstructors.media_video; 16 ok( VideoWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_video subclasses wp.mediaWidgets.MediaWidgetControl' );16 assert.ok( VideoWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_video subclasses wp.mediaWidgets.MediaWidgetControl' ); 17 17 18 18 videoWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_video(); 19 19 videoWidgetControlInstance = new VideoWidgetControl({ … … 25 25 // Test mapModelToMediaFrameProps(). 26 26 videoWidgetControlInstance.model.set({ error: false, url: testVideoUrl, loop: false, preload: 'meta' }); 27 27 mappedProps = videoWidgetControlInstance.mapModelToMediaFrameProps( videoWidgetControlInstance.model.toJSON() ); 28 equal( mappedProps.url, testVideoUrl, 'mapModelToMediaFrameProps should set url' );29 equal( mappedProps.loop, false, 'mapModelToMediaFrameProps should set loop' );30 equal( mappedProps.preload, 'meta', 'mapModelToMediaFrameProps should set preload' );28 assert.equal( mappedProps.url, testVideoUrl, 'mapModelToMediaFrameProps should set url' ); 29 assert.equal( mappedProps.loop, false, 'mapModelToMediaFrameProps should set loop' ); 30 assert.equal( mappedProps.preload, 'meta', 'mapModelToMediaFrameProps should set preload' ); 31 31 32 32 // Test mapMediaToModelProps(). 33 33 mappedProps = videoWidgetControlInstance.mapMediaToModelProps( { loop: false, preload: 'meta', url: testVideoUrl, title: 'random movie file title' } ); 34 equal( mappedProps.title, undefined, 'mapMediaToModelProps should ignore title inputs' );35 equal( mappedProps.loop, false, 'mapMediaToModelProps should set loop' );36 equal( mappedProps.preload, 'meta', 'mapMediaToModelProps should set preload' );34 assert.equal( mappedProps.title, undefined, 'mapMediaToModelProps should ignore title inputs' ); 35 assert.equal( mappedProps.loop, false, 'mapMediaToModelProps should set loop' ); 36 assert.equal( mappedProps.preload, 'meta', 'mapMediaToModelProps should set preload' ); 37 37 }); 38 38 39 test( 'video widget control renderPreview', function( assert ) {39 QUnit.test( 'video widget control renderPreview', function( assert ) { 40 40 var videoWidgetControlInstance, videoWidgetModelInstance, done; 41 41 done = assert.async(); 42 42 … … 46 46 syncContainer: jQuery( '<div></div>' ), 47 47 model: videoWidgetModelInstance 48 48 }); 49 equal( videoWidgetControlInstance.$el.find( 'a' ).length, 0, 'No video links should be rendered' );49 assert.equal( videoWidgetControlInstance.$el.find( 'a' ).length, 0, 'No video links should be rendered' ); 50 50 videoWidgetControlInstance.model.set({ error: false, url: 'https://videos.files.wordpress.com/AHz0Ca46/wp4-7-vaughan-r8-mastered_hd.mp4' }); 51 51 52 52 // Due to renderPreview being deferred. 53 53 setTimeout( function() { 54 equal( videoWidgetControlInstance.$el.find( 'a[href="https://videos.files.wordpress.com/AHz0Ca46/wp4-7-vaughan-r8-mastered_hd.mp4"]' ).length, 1, 'One video link should be rendered' );54 assert.equal( videoWidgetControlInstance.$el.find( 'a[href="https://videos.files.wordpress.com/AHz0Ca46/wp4-7-vaughan-r8-mastered_hd.mp4"]' ).length, 1, 'One video link should be rendered' ); 55 55 done(); 56 56 }, 50 ); 57 start(); 57 58 done(); 58 59 }); 59 60 60 test( 'video media model', function() {61 QUnit.test( 'video media model', function( assert ) { 61 62 var VideoWidgetModel, videoWidgetModelInstance; 62 equal( typeof wp.mediaWidgets.modelConstructors.media_video, 'function', 'wp.mediaWidgets.modelConstructors.media_video is a function' );63 assert.equal( typeof wp.mediaWidgets.modelConstructors.media_video, 'function', 'wp.mediaWidgets.modelConstructors.media_video is a function' ); 63 64 VideoWidgetModel = wp.mediaWidgets.modelConstructors.media_video; 64 ok( VideoWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_video subclasses wp.mediaWidgets.MediaWidgetModel' );65 assert.ok( VideoWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_video subclasses wp.mediaWidgets.MediaWidgetModel' ); 65 66 66 67 videoWidgetModelInstance = new VideoWidgetModel(); 67 68 _.each( videoWidgetModelInstance.attributes, function( value, key ) { 68 equal( value, VideoWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key );69 assert.equal( value, VideoWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key ); 69 70 }); 70 71 }); 71 72 -
tests/qunit/wp-admin/js/widgets/test-media-widgets.js
2 2 /* jshint qunit: true */ 3 3 /* eslint-env qunit */ 4 4 5 ( function() {5 ( function() { 6 6 'use strict'; 7 7 8 module( 'Media Widgets' );8 QUnit.module( 'Media Widgets' ); 9 9 10 test( 'namespace', function() {11 equal( typeof wp.mediaWidgets, 'object', 'wp.mediaWidgets is an object' );12 equal( typeof wp.mediaWidgets.controlConstructors, 'object', 'wp.mediaWidgets.controlConstructors is an object' );13 equal( typeof wp.mediaWidgets.modelConstructors, 'object', 'wp.mediaWidgets.modelConstructors is an object' );14 equal( typeof wp.mediaWidgets.widgetControls, 'object', 'wp.mediaWidgets.widgetControls is an object' );15 equal( typeof wp.mediaWidgets.handleWidgetAdded, 'function', 'wp.mediaWidgets.handleWidgetAdded is an function' );16 equal( typeof wp.mediaWidgets.handleWidgetUpdated, 'function', 'wp.mediaWidgets.handleWidgetUpdated is an function' );17 equal( typeof wp.mediaWidgets.init, 'function', 'wp.mediaWidgets.init is an function' );10 QUnit.test( 'namespace', function( assert ) { 11 assert.equal( typeof wp.mediaWidgets, 'object', 'wp.mediaWidgets is an object' ); 12 assert.equal( typeof wp.mediaWidgets.controlConstructors, 'object', 'wp.mediaWidgets.controlConstructors is an object' ); 13 assert.equal( typeof wp.mediaWidgets.modelConstructors, 'object', 'wp.mediaWidgets.modelConstructors is an object' ); 14 assert.equal( typeof wp.mediaWidgets.widgetControls, 'object', 'wp.mediaWidgets.widgetControls is an object' ); 15 assert.equal( typeof wp.mediaWidgets.handleWidgetAdded, 'function', 'wp.mediaWidgets.handleWidgetAdded is an function' ); 16 assert.equal( typeof wp.mediaWidgets.handleWidgetUpdated, 'function', 'wp.mediaWidgets.handleWidgetUpdated is an function' ); 17 assert.equal( typeof wp.mediaWidgets.init, 'function', 'wp.mediaWidgets.init is an function' ); 18 18 }); 19 19 20 test( 'media widget control', function() {21 equal( typeof wp.mediaWidgets.MediaWidgetControl, 'function', 'wp.mediaWidgets.MediaWidgetControl' );22 ok( wp.mediaWidgets.MediaWidgetControl.prototype instanceof Backbone.View, 'wp.mediaWidgets.MediaWidgetControl subclasses Backbone.View' );20 QUnit.test( 'media widget control', function( assert ) { 21 assert.equal( typeof wp.mediaWidgets.MediaWidgetControl, 'function', 'wp.mediaWidgets.MediaWidgetControl' ); 22 assert.ok( wp.mediaWidgets.MediaWidgetControl.prototype instanceof Backbone.View, 'wp.mediaWidgets.MediaWidgetControl subclasses Backbone.View' ); 23 23 }); 24 24 25 test( 'media widget model', function() {25 QUnit.test( 'media widget model', function( assert ) { 26 26 var widgetModelInstance; 27 equal( typeof wp.mediaWidgets.MediaWidgetModel, 'function', 'wp.mediaWidgets.MediaWidgetModel is a function' );28 ok( wp.mediaWidgets.MediaWidgetModel.prototype instanceof Backbone.Model, 'wp.mediaWidgets.MediaWidgetModel subclasses Backbone.Model' );27 assert.equal( typeof wp.mediaWidgets.MediaWidgetModel, 'function', 'wp.mediaWidgets.MediaWidgetModel is a function' ); 28 assert.ok( wp.mediaWidgets.MediaWidgetModel.prototype instanceof Backbone.Model, 'wp.mediaWidgets.MediaWidgetModel subclasses Backbone.Model' ); 29 29 30 30 widgetModelInstance = new wp.mediaWidgets.MediaWidgetModel(); 31 equal( widgetModelInstance.get( 'title' ), '', 'wp.mediaWidgets.MediaWidgetModel defaults title to empty string' );32 equal( widgetModelInstance.get( 'attachment_id' ), 0, 'wp.mediaWidgets.MediaWidgetModel defaults attachment_id to 0' );33 equal( widgetModelInstance.get( 'url' ), 0, 'wp.mediaWidgets.MediaWidgetModel defaults url to empty string' );31 assert.equal( widgetModelInstance.get( 'title' ), '', 'wp.mediaWidgets.MediaWidgetModel defaults title to empty string' ); 32 assert.equal( widgetModelInstance.get( 'attachment_id' ), 0, 'wp.mediaWidgets.MediaWidgetModel defaults attachment_id to 0' ); 33 assert.equal( widgetModelInstance.get( 'url' ), 0, 'wp.mediaWidgets.MediaWidgetModel defaults url to empty string' ); 34 34 35 35 widgetModelInstance.set({ 36 36 title: 'chicken and ribs', … … 37 37 attachment_id: '1', 38 38 url: 'https://wordpress.org' 39 39 }); 40 equal( widgetModelInstance.get( 'title' ), 'chicken and ribs', 'wp.mediaWidgets.MediaWidgetModel properly sets the title attribute' );41 equal( widgetModelInstance.get( 'url' ), 'https://wordpress.org', 'wp.mediaWidgets.MediaWidgetModel properly sets the url attribute' );42 equal( widgetModelInstance.get( 'attachment_id' ), 1, 'wp.mediaWidgets.MediaWidgetModel properly sets and casts the attachment_id attribute' );40 assert.equal( widgetModelInstance.get( 'title' ), 'chicken and ribs', 'wp.mediaWidgets.MediaWidgetModel properly sets the title attribute' ); 41 assert.equal( widgetModelInstance.get( 'url' ), 'https://wordpress.org', 'wp.mediaWidgets.MediaWidgetModel properly sets the url attribute' ); 42 assert.equal( widgetModelInstance.get( 'attachment_id' ), 1, 'wp.mediaWidgets.MediaWidgetModel properly sets and casts the attachment_id attribute' ); 43 43 }); 44 44 45 45 })(); -
tests/qunit/wp-includes/js/shortcode.js
1 1 /* global wp, jQuery */ 2 2 jQuery( function() { 3 module( 'shortcode' );3 QUnit.module( 'shortcode' ); 4 4 5 test( 'next() should find the shortcode', function() {5 QUnit.test( 'next() should find the shortcode', function( assert ) { 6 6 var result; 7 7 8 8 // Basic. 9 9 result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode' ); 10 equal( result.index, 13, 'foo shortcode found at index 13' );10 assert.equal( result.index, 13, 'foo shortcode found at index 13' ); 11 11 12 12 result = wp.shortcode.next( 'foo', 'this has the [foo param="foo"] shortcode' ); 13 equal( result.index, 13, 'foo shortcode with params found at index 13' );13 assert.equal( result.index, 13, 'foo shortcode with params found at index 13' ); 14 14 }); 15 15 16 test( 'next() should not find shortcodes that are not there', function() {16 QUnit.test( 'next() should not find shortcodes that are not there', function( assert ) { 17 17 var result; 18 18 19 19 // Not found. 20 20 result = wp.shortcode.next( 'bar', 'this has the [foo] shortcode' ); 21 equal( result, undefined, 'bar shortcode not found' );21 assert.equal( result, undefined, 'bar shortcode not found' ); 22 22 23 23 result = wp.shortcode.next( 'bar', 'this has the [foo param="bar"] shortcode' ); 24 equal( result, undefined, 'bar shortcode not found with params' );24 assert.equal( result, undefined, 'bar shortcode not found with params' ); 25 25 }); 26 26 27 test( 'next() should find the shortcode when told to start looking beyond the start of the string', function() {27 QUnit.test( 'next() should find the shortcode when told to start looking beyond the start of the string', function( assert ) { 28 28 var result; 29 29 30 30 // Starting at indices. 31 31 result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode', 12 ); 32 equal( result.index, 13, 'foo shortcode found before index 13' );32 assert.equal( result.index, 13, 'foo shortcode found before index 13' ); 33 33 34 34 result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode', 13 ); 35 equal( result.index, 13, 'foo shortcode found at index 13' );35 assert.equal( result.index, 13, 'foo shortcode found at index 13' ); 36 36 37 37 result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode', 14 ); 38 equal( result, undefined, 'foo shortcode not found after index 13' );38 assert.equal( result, undefined, 'foo shortcode not found after index 13' ); 39 39 }); 40 40 41 test( 'next() should find the second instances of the shortcode when the starting indice is after the start of the first one', function() {41 QUnit.test( 'next() should find the second instances of the shortcode when the starting indice is after the start of the first one', function( assert ) { 42 42 var result; 43 43 44 44 result = wp.shortcode.next( 'foo', 'this has the [foo] shortcode [foo] twice', 14 ); 45 equal( result.index, 29, 'foo shortcode found the second foo at index 29' );45 assert.equal( result.index, 29, 'foo shortcode found the second foo at index 29' ); 46 46 }); 47 47 48 48 49 test( 'next() should not find escaped shortcodes', function() {49 QUnit.test( 'next() should not find escaped shortcodes', function( assert ) { 50 50 var result; 51 51 52 52 // Escaped. 53 53 result = wp.shortcode.next( 'foo', 'this has the [[foo]] shortcode' ); 54 equal( result, undefined, 'foo shortcode not found when escaped' );54 assert.equal( result, undefined, 'foo shortcode not found when escaped' ); 55 55 56 56 result = wp.shortcode.next( 'foo', 'this has the [[foo param="foo"]] shortcode' ); 57 equal( result, undefined, 'foo shortcode not found when escaped with params' );57 assert.equal( result, undefined, 'foo shortcode not found when escaped with params' ); 58 58 }); 59 59 60 test( 'next() should find shortcodes that are incorrectly escaped by newlines', function() {60 QUnit.test( 'next() should find shortcodes that are incorrectly escaped by newlines', function( assert ) { 61 61 var result; 62 62 63 63 result = wp.shortcode.next( 'foo', 'this has the [\n[foo]] shortcode' ); 64 equal( result.index, 15, 'shortcode found when incorrectly escaping the start of it' );64 assert.equal( result.index, 15, 'shortcode found when incorrectly escaping the start of it' ); 65 65 66 66 result = wp.shortcode.next( 'foo', 'this has the [[foo]\n] shortcode' ); 67 equal( result.index, 14, 'shortcode found when incorrectly escaping the end of it' );67 assert.equal( result.index, 14, 'shortcode found when incorrectly escaping the end of it' ); 68 68 }); 69 69 70 test( 'next() should still work when there are not equal ammounts of square brackets', function() {70 QUnit.test( 'next() should still work when there are not equal ammounts of square brackets', function( assert ) { 71 71 var result; 72 72 73 73 result = wp.shortcode.next( 'foo', 'this has the [[foo] shortcode' ); 74 equal( result.index, 14, 'shortcode found when there are offset square brackets' );74 assert.equal( result.index, 14, 'shortcode found when there are offset square brackets' ); 75 75 76 76 result = wp.shortcode.next( 'foo', 'this has the [foo]] shortcode' ); 77 equal( result.index, 13, 'shortcode found when there are offset square brackets' );77 assert.equal( result.index, 13, 'shortcode found when there are offset square brackets' ); 78 78 }); 79 79 80 test( 'next() should find the second instances of the shortcode when the first one is escaped', function() {80 QUnit.test( 'next() should find the second instances of the shortcode when the first one is escaped', function( assert ) { 81 81 var result; 82 82 83 83 84 84 result = wp.shortcode.next( 'foo', 'this has the [[foo]] shortcode [foo] twice' ); 85 equal( result.index, 31, 'foo shortcode found the non-escaped foo at index 31' );85 assert.equal( result.index, 31, 'foo shortcode found the non-escaped foo at index 31' ); 86 86 }); 87 87 88 test( 'next() should not find shortcodes that are not full matches', function() {88 QUnit.test( 'next() should not find shortcodes that are not full matches', function( assert ) { 89 89 var result; 90 90 91 91 // Stubs. 92 92 result = wp.shortcode.next( 'foo', 'this has the [foobar] shortcode' ); 93 equal( result, undefined, 'stub does not trigger match' );93 assert.equal( result, undefined, 'stub does not trigger match' ); 94 94 95 95 result = wp.shortcode.next( 'foobar', 'this has the [foo] shortcode' ); 96 equal( result, undefined, 'stub does not trigger match' );96 assert.equal( result, undefined, 'stub does not trigger match' ); 97 97 }); 98 98 99 test( 'replace() should replace the shortcode', function() {99 QUnit.test( 'replace() should replace the shortcode', function( assert ) { 100 100 var result; 101 101 102 102 // Basic. 103 103 result = wp.shortcode.replace( 'foo', 'this has the [foo] shortcode', shortcodeReplaceCallback ); 104 equal( result, 'this has the bar shortcode', 'foo replaced with bar' );104 assert.equal( result, 'this has the bar shortcode', 'foo replaced with bar' ); 105 105 106 106 result = wp.shortcode.replace( 'foo', 'this has the [foo param="foo"] shortcode', shortcodeReplaceCallback ); 107 equal( result, 'this has the bar shortcode', 'foo and params replaced with bar' );107 assert.equal( result, 'this has the bar shortcode', 'foo and params replaced with bar' ); 108 108 }); 109 109 110 test( 'replace() should not replace the shortcode when it does not match', function() {110 QUnit.test( 'replace() should not replace the shortcode when it does not match', function( assert ) { 111 111 var result; 112 112 113 113 // Not found. 114 114 result = wp.shortcode.replace( 'bar', 'this has the [foo] shortcode', shortcodeReplaceCallback ); 115 equal( result, 'this has the [foo] shortcode', 'bar not found' );115 assert.equal( result, 'this has the [foo] shortcode', 'bar not found' ); 116 116 117 117 result = wp.shortcode.replace( 'bar', 'this has the [foo param="bar"] shortcode', shortcodeReplaceCallback ); 118 equal( result, 'this has the [foo param="bar"] shortcode', 'bar not found with params' );118 assert.equal( result, 'this has the [foo param="bar"] shortcode', 'bar not found with params' ); 119 119 }); 120 120 121 test( 'replace() should replace the shortcode in all instances of its use', function() {121 QUnit.test( 'replace() should replace the shortcode in all instances of its use', function( assert ) { 122 122 var result; 123 123 124 124 // Multiple instances. 125 125 result = wp.shortcode.replace( 'foo', 'this has the [foo] shortcode [foo] twice', shortcodeReplaceCallback ); 126 equal( result, 'this has the bar shortcode bar twice', 'foo replaced with bar twice' );126 assert.equal( result, 'this has the bar shortcode bar twice', 'foo replaced with bar twice' ); 127 127 128 128 result = wp.shortcode.replace( 'foo', 'this has the [foo param="foo"] shortcode [foo] twice', shortcodeReplaceCallback ); 129 equal( result, 'this has the bar shortcode bar twice', 'foo and params replaced with bar twice' );129 assert.equal( result, 'this has the bar shortcode bar twice', 'foo and params replaced with bar twice' ); 130 130 }); 131 131 132 test( 'replace() should not replace the escaped shortcodes', function() {132 QUnit.test( 'replace() should not replace the escaped shortcodes', function( assert ) { 133 133 var result; 134 134 135 135 // Escaped. 136 136 result = wp.shortcode.replace( 'foo', 'this has the [[foo]] shortcode', shortcodeReplaceCallback ); 137 equal( result, 'this has the [[foo]] shortcode', 'escaped foo not replaced' );137 assert.equal( result, 'this has the [[foo]] shortcode', 'escaped foo not replaced' ); 138 138 139 139 result = wp.shortcode.replace( 'foo', 'this has the [[foo param="bar"]] shortcode', shortcodeReplaceCallback ); 140 equal( result, 'this has the [[foo param="bar"]] shortcode', 'escaped foo with params not replaced' );140 assert.equal( result, 'this has the [[foo param="bar"]] shortcode', 'escaped foo with params not replaced' ); 141 141 142 142 result = wp.shortcode.replace( 'foo', 'this [foo] has the [[foo param="bar"]] shortcode escaped', shortcodeReplaceCallback ); 143 equal( result, 'this bar has the [[foo param="bar"]] shortcode escaped', 'escaped foo with params not replaced but unescaped foo replaced' );143 assert.equal( result, 'this bar has the [[foo param="bar"]] shortcode escaped', 'escaped foo with params not replaced but unescaped foo replaced' ); 144 144 }); 145 145 146 test( 'replace() should replace improperly escaped shortcodes that include newlines', function() {146 QUnit.test( 'replace() should replace improperly escaped shortcodes that include newlines', function( assert ) { 147 147 var result; 148 148 149 149 result = wp.shortcode.replace( 'foo', 'this [foo] has the [[foo param="bar"]\n] shortcode ', shortcodeReplaceCallback ); 150 equal( result, 'this bar has the [bar\n] shortcode ', 'escaping with newlines should not actually escape the content' );150 assert.equal( result, 'this bar has the [bar\n] shortcode ', 'escaping with newlines should not actually escape the content' ); 151 151 152 152 result = wp.shortcode.replace( 'foo', 'this [foo] has the [\n[foo param="bar"]] shortcode ', shortcodeReplaceCallback ); 153 equal( result, 'this bar has the [\nbar] shortcode ', 'escaping with newlines should not actually escape the content' );153 assert.equal( result, 'this bar has the [\nbar] shortcode ', 'escaping with newlines should not actually escape the content' ); 154 154 }); 155 155 156 test( 'replace() should not replace the shortcode when it is an incomplete match', function() {156 QUnit.test( 'replace() should not replace the shortcode when it is an incomplete match', function( assert ) { 157 157 var result; 158 158 159 159 // Stubs. 160 160 result = wp.shortcode.replace( 'foo', 'this has the [foobar] shortcode', shortcodeReplaceCallback ); 161 equal( result, 'this has the [foobar] shortcode', 'stub not replaced' );161 assert.equal( result, 'this has the [foobar] shortcode', 'stub not replaced' ); 162 162 163 163 result = wp.shortcode.replace( 'foobar', 'this has the [foo] shortcode', shortcodeReplaceCallback ); 164 equal( result, 'this has the [foo] shortcode', 'stub not replaced' );164 assert.equal( result, 'this has the [foo] shortcode', 'stub not replaced' ); 165 165 }); 166 166 167 167 /** … … 171 171 return 'bar'; 172 172 } 173 173 174 test( 'attrs() should return named attributes created with single, double, and no quotes', function() {174 QUnit.test( 'attrs() should return named attributes created with single, double, and no quotes', function( assert ) { 175 175 var expected = { 176 176 'named': { 177 177 'param': 'foo', … … 180 180 }, 'numeric' : [] 181 181 }; 182 182 183 deepEqual( wp.shortcode.attrs('param="foo" another=\'bar\' andagain=baz'), expected, 'attr parsed all three named types');183 assert.deepEqual( wp.shortcode.attrs('param="foo" another=\'bar\' andagain=baz'), expected, 'attr parsed all three named types'); 184 184 }); 185 185 186 test( 'attrs() should return numeric attributes in the order they are used', function() {186 QUnit.test( 'attrs() should return numeric attributes in the order they are used', function( assert ) { 187 187 var expected = { 188 188 'named': {}, 'numeric' : ['foo', 'bar', 'baz'] 189 189 }; 190 190 191 deepEqual( wp.shortcode.attrs('foo bar baz'), expected, 'attr parsed numeric attributes');191 assert.deepEqual( wp.shortcode.attrs('foo bar baz'), expected, 'attr parsed numeric attributes'); 192 192 }); 193 193 194 test( 'attrs() should return numeric attributes in the order they are used when they have named attributes in between', function() {194 QUnit.test( 'attrs() should return numeric attributes in the order they are used when they have named attributes in between', function( assert ) { 195 195 var expected = { 196 196 'named': { 'not': 'a blocker' }, 'numeric' : ['foo', 'bar', 'baz'] 197 197 }; 198 198 199 deepEqual( wp.shortcode.attrs('foo not="a blocker" bar baz'), expected, 'attr parsed numeric attributes');199 assert.deepEqual( wp.shortcode.attrs('foo not="a blocker" bar baz'), expected, 'attr parsed numeric attributes'); 200 200 }); 201 201 202 test( 'attrs() should return numeric attributes created with single, double, and no quotes', function() {202 QUnit.test( 'attrs() should return numeric attributes created with single, double, and no quotes', function( assert ) { 203 203 var expected = { 204 204 'named': {}, 'numeric' : ['foo', 'bar', 'baz'] 205 205 }; 206 206 207 deepEqual( wp.shortcode.attrs('foo "bar" \'baz\''), expected, 'attr parsed numeric attributes');207 assert.deepEqual( wp.shortcode.attrs('foo "bar" \'baz\''), expected, 'attr parsed numeric attributes'); 208 208 }); 209 209 210 test( 'attrs() should return mixed attributes created with single, double, and no quotes', function() {210 QUnit.test( 'attrs() should return mixed attributes created with single, double, and no quotes', function( assert ) { 211 211 var expected = { 212 212 'named': { a: 'foo', b: 'bar', c: 'baz' }, 'numeric' : ['foo', 'bar', 'baz'] 213 213 }; 214 214 215 deepEqual( wp.shortcode.attrs('a="foo" b=\'bar\' c=baz foo "bar" \'baz\''), expected, 'attr parsed numeric attributes');215 assert.deepEqual( wp.shortcode.attrs('a="foo" b=\'bar\' c=baz foo "bar" \'baz\''), expected, 'attr parsed numeric attributes'); 216 216 }); 217 217 218 test( 'string() should accept attrs in any order', function() {218 QUnit.test( 'string() should accept attrs in any order', function( assert ) { 219 219 var expected = '[short abc123 foo="bar"]'; 220 220 var result; 221 221 … … 227 227 numeric : [ 'abc123' ] 228 228 } 229 229 }); 230 deepEqual( result, expected, 'attributes are accepted in any order' );230 assert.deepEqual( result, expected, 'attributes are accepted in any order' ); 231 231 232 232 result = wp.shortcode.string({ 233 233 tag : 'short', … … 237 237 named : { foo : 'bar' } 238 238 } 239 239 }); 240 deepEqual( result, expected, 'attributes are accepted in any order' );240 assert.deepEqual( result, expected, 'attributes are accepted in any order' ); 241 241 }); 242 242 }); -
tests/qunit/wp-includes/js/tinymce/tinymce-obsolete.js
81 81 82 82 // Ref: http://www.w3.org/TR/html5/obsolete.html, http://developers.whatwg.org/obsolete.html 83 83 84 QUnit.test('HTML elements non-conforming to HTML 5.0', function( ) {84 QUnit.test('HTML elements non-conforming to HTML 5.0', function( assert ) { 85 85 var testString; 86 86 87 87 /* … … 109 109 The rest are still supported in TinyMCE but "...must not be used by authors". 110 110 */ 111 111 112 expect(6);112 assert.expect(6); 113 113 114 114 text = 'acronym'; 115 115 testString = '<p><acronym title="www">WWW</acronym></p>'; 116 116 editor.setContent( testString ); 117 equal( editor.getContent(), testString, text );117 assert.equal( editor.getContent(), testString, text ); 118 118 119 119 text = 'strike, converted to span'; 120 120 editor.setContent( '<strike>test</strike>' ); 121 equal( editor.getContent(), '<p><span style="text-decoration: line-through;">test</span></p>', text );121 assert.equal( editor.getContent(), '<p><span style="text-decoration: line-through;">test</span></p>', text ); 122 122 123 123 text = 'big'; 124 124 testString = '<p><big>test</big></p>'; 125 125 editor.setContent( testString ); 126 equal( editor.getContent(), testString, text );126 assert.equal( editor.getContent(), testString, text ); 127 127 128 128 text = 'center'; 129 129 testString = '<center>test</center>'; 130 130 editor.setContent( testString ); 131 equal( editor.getContent(), testString, text );131 assert.equal( editor.getContent(), testString, text ); 132 132 133 133 text = 'font, converted to span'; 134 134 editor.setContent( '<p><font size="4">test</font></p>' ); 135 equal( editor.getContent(), '<p><span style="font-size: large;">test</span></p>', text );135 assert.equal( editor.getContent(), '<p><span style="font-size: large;">test</span></p>', text ); 136 136 137 137 text = 'tt'; 138 138 testString = '<p><tt>test</tt></p>'; 139 139 editor.setContent( testString ); 140 equal( editor.getContent(), testString, text );140 assert.equal( editor.getContent(), testString, text ); 141 141 }); 142 142 143 QUnit.test('Obsolete (but still conforming) HTML attributes', function( ) {143 QUnit.test('Obsolete (but still conforming) HTML attributes', function( assert ) { 144 144 var testString; 145 145 146 expect(3);146 assert.expect(3); 147 147 148 148 text = 'border on <img>'; 149 149 testString = '<p><img src="../../test.gif" alt="" border="5" /></p>'; 150 150 editor.setContent( testString ); 151 equal( editor.getContent(), testString, text );151 assert.equal( editor.getContent(), testString, text ); 152 152 153 153 text = 'Old style anchors'; 154 154 testString = '<p><a name="test"></a></p>'; 155 155 editor.setContent( testString ); 156 equal( editor.getContent(), testString, text );156 assert.equal( editor.getContent(), testString, text ); 157 157 158 158 text = 'maxlength, size on input type="number"'; 159 159 testString = '<p><input maxlength="5" size="10" type="number" value="" /></p>'; 160 160 editor.setContent( testString ); 161 ok( hasAttr( editor.getContent(), { input: 'maxlength size' } ), text );161 assert.ok( hasAttr( editor.getContent(), { input: 'maxlength size' } ), text ); 162 162 }); 163 163 164 QUnit.test('Obsolete attributes in HTML 5.0', function( ) {164 QUnit.test('Obsolete attributes in HTML 5.0', function( assert ) { 165 165 var testString, text; 166 166 167 expect(22);167 assert.expect(22); 168 168 169 169 text = 'charset, rev, shape, coords on <a> elements'; 170 170 testString = '<p><a href="javascript;:" charset="en" rev="made" shape="rect" coords="5,5">test</a></p>'; 171 171 editor.setContent( testString ); 172 ok( hasAttr( editor.getContent(), { a: 'charset rev shape coords' } ), text );172 assert.ok( hasAttr( editor.getContent(), { a: 'charset rev shape coords' } ), text ); 173 173 174 174 text = 'name, align, hspace, vspace on img elements'; 175 175 testString = '<p><img src="../../test.gif" alt="" name="test" align="left" hspace="5" vspace="5" /></p>'; 176 176 editor.setContent( testString ); 177 ok( hasAttr( editor.getContent(), { img: 'name align hspace vspace' } ), text );177 assert.ok( hasAttr( editor.getContent(), { img: 'name align hspace vspace' } ), text ); 178 178 179 179 text = 'name, align, hspace, vspace, on embed elements'; 180 180 testString = '<p><embed width="100" height="100" src="test.swf" vspace="5" hspace="5" align="left" name="test"></embed></p>'; 181 181 editor.setContent( testString ); 182 ok( hasAttr( editor.getContent(), { embed: 'name align hspace vspace' } ), text );182 assert.ok( hasAttr( editor.getContent(), { embed: 'name align hspace vspace' } ), text ); 183 183 184 184 text = 'archive, classid, code, codebase, codetype, declare, standby on object elements'; 185 185 testString = '<p><object width="100" height="100" classid="clsid" codebase="clsid" standby="standby" codetype="1" code="1" archive="1" declare="declare"></object></p>'; 186 186 editor.setContent( testString ); 187 ok( hasAttr( editor.getContent(), { object: 'archive classid code codebase codetype declare standby' } ), text );187 assert.ok( hasAttr( editor.getContent(), { object: 'archive classid code codebase codetype declare standby' } ), text ); 188 188 189 189 text = 'type, valuetype on param elements'; 190 190 testString = '<p><object width="100" height="100"><param type="" valuetype="" /></object></p>'; 191 191 editor.setContent( testString ); 192 ok( hasAttr( editor.getContent(), { param: 'type valuetype' } ), text );192 assert.ok( hasAttr( editor.getContent(), { param: 'type valuetype' } ), text ); 193 193 194 194 text = 'align, bgcolor, border, cellpadding, cellspacing, frame, rules, summary, width on table elements'; 195 195 testString = '<table border="1" summary="" width="100" frame="" rules="" cellspacing="5" cellpadding="5" align="left" bgcolor="blue"><tbody><tr><td>test</td></tr></tbody></table>'; 196 196 editor.setContent( testString ); 197 ok( hasAttr( editor.getContent(), { table: 'align bgcolor border cellpadding cellspacing frame rules summary width' } ), text );197 assert.ok( hasAttr( editor.getContent(), { table: 'align bgcolor border cellpadding cellspacing frame rules summary width' } ), text ); 198 198 199 199 text = 'align, char, charoff, valign on tbody, thead, and tfoot elements'; 200 200 testString = '<table><thead align="left" char="" charoff="" valign="top"></thead><tfoot align="left" char="" charoff="" valign="top"></tfoot><tbody align="left" char="" charoff="" valign="top"><tr><th>test</th><td>test</td></tr></tbody></table>'; 201 201 editor.setContent( testString ); 202 ok( hasAttr( editor.getContent(), {202 assert.ok( hasAttr( editor.getContent(), { 203 203 thead: 'align char charoff valign', 204 204 tfoot: 'align char charoff valign', 205 205 tbody: 'align char charoff valign' … … 208 208 text = 'axis, align, bgcolor, char, charoff, height, nowrap, valign, width on td and th elements, scope on td elements'; 209 209 testString = '<table><tbody><tr><th axis="" align="left" char="" charoff="" valign="top" nowrap="nowrap" bgcolor="blue" width="100" height="10">test</th><td axis="" align="left" char="" charoff="" valign="top" nowrap="nowrap" bgcolor="blue" width="100" height="10" scope="">test</td></tr></tbody></table>'; 210 210 editor.setContent( testString ); 211 ok( hasAttr( editor.getContent(), {211 assert.ok( hasAttr( editor.getContent(), { 212 212 th: 'axis align bgcolor char charoff height nowrap valign width', 213 213 td: 'axis align bgcolor char charoff height nowrap valign width scope' 214 214 } ), text ); … … 216 216 text = 'align, bgcolor, char, charoff, valign on tr elements'; 217 217 testString = '<table><tbody><tr align="left" char="" charoff="" valign="top" bgcolor="blue"><td>test</td></tr></tbody></table>'; 218 218 editor.setContent( testString ); 219 ok( hasAttr( editor.getContent(), { tr: 'align bgcolor char charoff valign' } ), text );219 assert.ok( hasAttr( editor.getContent(), { tr: 'align bgcolor char charoff valign' } ), text ); 220 220 221 221 text = 'clear on br elements'; 222 222 testString = '<p>test<br clear="all" />test</p>'; 223 223 editor.setContent( testString ); 224 equal( editor.getContent(), testString, text );224 assert.equal( editor.getContent(), testString, text ); 225 225 226 226 text = 'align on caption elements'; 227 227 testString = '<table><caption align="left">test</caption><tbody><tr><td>test</td></tr></tbody></table>'; 228 228 editor.setContent( testString ); 229 equal( editor.getContent(), testString, text );229 assert.equal( editor.getContent(), testString, text ); 230 230 231 231 text = 'align, char, charoff, valign, width on col elements'; 232 232 testString = '<table><colgroup><col width="100" align="left" char="a" charoff="1" valign="top" /><col /></colgroup><tbody><tr><td>test</td><td>test</td></tr></tbody></table>'; 233 233 editor.setContent( testString ); 234 ok( hasAttr( editor.getContent(), { col: 'align char charoff valign width' } ), text );234 assert.ok( hasAttr( editor.getContent(), { col: 'align char charoff valign width' } ), text ); 235 235 236 236 text = 'align on div, h1—h6, input, legend, p elements'; 237 237 testString = '<div align="left">1</div><h3 align="left">1</h3><p align="left">1</p><form><fieldset><legend align="left">test</legend><input type="text" align="left" /></fieldset></form>'; 238 238 editor.setContent( testString ); 239 equal( editor.getContent(), testString, text );239 assert.equal( editor.getContent(), testString, text ); 240 240 241 241 text = 'compact on dl elements'; 242 242 testString = '<dl compact="compact"><dd>1</dd></dl>'; 243 243 editor.setContent( testString ); 244 equal( editor.getContent(), testString, text );244 assert.equal( editor.getContent(), testString, text ); 245 245 246 246 text = 'align, hspace, vspace on embed elements'; 247 247 testString = '<p><embed width="100" height="100" vspace="5" hspace="5" align="left"></embed></p>'; 248 248 editor.setContent( testString ); 249 ok( hasAttr( editor.getContent(), { embed: 'align hspace vspace' } ), text );249 assert.ok( hasAttr( editor.getContent(), { embed: 'align hspace vspace' } ), text ); 250 250 251 251 text = 'align, noshade, size, width on hr elements'; 252 252 testString = '<hr align="left" noshade="noshade" size="1" width="100" />'; 253 253 editor.setContent( testString ); 254 ok( hasAttr( editor.getContent(), { hr: 'align noshade size width' } ), text );254 assert.ok( hasAttr( editor.getContent(), { hr: 'align noshade size width' } ), text ); 255 255 256 256 text = 'align, frameborder, marginheight, marginwidth, scrolling on iframe elements'; 257 257 testString = '<p><iframe width="100" height="100" frameborder="1" marginwidth="5" marginheight="5" scrolling="" align="left"></iframe></p>'; 258 258 editor.setContent( testString ); 259 ok( hasAttr( editor.getContent(), { iframe: 'align frameborder marginheight marginwidth scrolling' } ), text );259 assert.ok( hasAttr( editor.getContent(), { iframe: 'align frameborder marginheight marginwidth scrolling' } ), text ); 260 260 261 261 text = 'type on li elements'; 262 262 testString = '<ul><li type="disc">test</li></ul>'; 263 263 editor.setContent( testString ); 264 equal( editor.getContent(), testString, text );264 assert.equal( editor.getContent(), testString, text ); 265 265 266 266 text = 'align, border, hspace, vspace on object elements'; 267 267 testString = '<p><object width="100" height="100" border="1" vspace="5" hspace="5" align="left"></object></p>'; 268 268 editor.setContent( testString ); 269 ok( hasAttr( editor.getContent(), { object: 'align border hspace vspace' } ), text );269 assert.ok( hasAttr( editor.getContent(), { object: 'align border hspace vspace' } ), text ); 270 270 271 271 text = 'compact on ol elements'; 272 272 testString = '<ol compact="compact"><li>test</li></ol>'; 273 273 editor.setContent( testString ); 274 equal( editor.getContent(), testString, text );274 assert.equal( editor.getContent(), testString, text ); 275 275 276 276 text = 'compact, type on ul elements'; 277 277 testString = '<ul type="disc" compact="compact"><li>test</li></ul>'; 278 278 editor.setContent( testString ); 279 ok( hasAttr( editor.getContent(), { ul: 'compact type' } ), text );279 assert.ok( hasAttr( editor.getContent(), { ul: 'compact type' } ), text ); 280 280 281 281 text = 'width on pre elements'; 282 282 testString = '<pre width="100">1</pre>'; 283 283 editor.setContent( testString ); 284 equal( editor.getContent(), testString, text );284 assert.equal( editor.getContent(), testString, text ); 285 285 }); 286 286 287 287 } )( window.jQuery, window.QUnit, window.tinymce ); -
tests/qunit/wp-includes/js/wp-api.js
1 1 /* global wp, JSON */ 2 2 ( function( QUnit ) { 3 module( 'wpapi' );3 QUnit.module( 'wpapi' ); 4 4 5 5 QUnit.test( 'API Loaded correctly', function( assert ) { 6 6 var done = assert.async(); … … 122 122 123 123 _.each( modelsWithIdsClassNames, function( className ) { 124 124 125 QUnit.test( 'Checking ' + className + ' model.' 125 QUnit.test( 'Checking ' + className + ' model.', function( assert ) { 126 126 var done = assert.async(); 127 127 128 128 assert.expect( 2 ); … … 160 160 161 161 _.each( modelsWithIndexes, function( className ) { 162 162 163 QUnit.test( 'Testing ' + className + ' model.' 163 QUnit.test( 'Testing ' + className + ' model.', function( assert ) { 164 164 var done = assert.async(); 165 165 166 166 assert.expect( 2 ); … … 296 296 297 297 // Check that we have and can get each model type. 298 298 _.each( customClasses, function( className ) { 299 QUnit.test( 'Checking ' + className + ' class name.' 299 QUnit.test( 'Checking ' + className + ' class name.', function( assert ) { 300 300 var done = assert.async(); 301 301 302 302 assert.expect( 2 ); … … 316 316 317 317 // Check connecting to a second URL. 318 318 wp.api.loadPromise.done( function() { 319 QUnit.test( 'Checking connecting to a remote url.' 319 QUnit.test( 'Checking connecting to a remote url.', function( assert ) { 320 320 var done = assert.async(); 321 321 322 322 wp.api.init({