";
874 |
875 | runLoggingCallbacks( "log", QUnit, details );
876 |
877 | config.current.assertions.push({
878 | result: false,
879 | message: output
880 | });
881 | },
882 |
883 | url: function( params ) {
884 | params = extend( extend( {}, QUnit.urlParams ), params );
885 | var key,
886 | querystring = "?";
887 |
888 | for ( key in params ) {
889 | if ( !hasOwn.call( params, key ) ) {
890 | continue;
891 | }
892 | querystring += encodeURIComponent( key ) + "=" +
893 | encodeURIComponent( params[ key ] ) + "&";
894 | }
895 | return window.location.pathname + querystring.slice( 0, -1 );
896 | },
897 |
898 | extend: extend,
899 | id: id,
900 | addEvent: addEvent
901 | // load, equiv, jsDump, diff: Attached later
902 | });
903 |
904 | /**
905 | * @deprecated: Created for backwards compatibility with test runner that set the hook function
906 | * into QUnit.{hook}, instead of invoking it and passing the hook function.
907 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
908 | * Doing this allows us to tell if the following methods have been overwritten on the actual
909 | * QUnit object.
910 | */
911 | extend( QUnit.constructor.prototype, {
912 |
913 | // Logging callbacks; all receive a single argument with the listed properties
914 | // run test/logs.html for any related changes
915 | begin: registerLoggingCallback( "begin" ),
916 |
917 | // done: { failed, passed, total, runtime }
918 | done: registerLoggingCallback( "done" ),
919 |
920 | // log: { result, actual, expected, message }
921 | log: registerLoggingCallback( "log" ),
922 |
923 | // testStart: { name }
924 | testStart: registerLoggingCallback( "testStart" ),
925 |
926 | // testDone: { name, failed, passed, total }
927 | testDone: registerLoggingCallback( "testDone" ),
928 |
929 | // moduleStart: { name }
930 | moduleStart: registerLoggingCallback( "moduleStart" ),
931 |
932 | // moduleDone: { name, failed, passed, total }
933 | moduleDone: registerLoggingCallback( "moduleDone" )
934 | });
935 |
936 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
937 | config.autorun = true;
938 | }
939 |
940 | QUnit.load = function() {
941 | runLoggingCallbacks( "begin", QUnit, {} );
942 |
943 | // Initialize the config, saving the execution queue
944 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
945 | numModules = 0,
946 | moduleFilterHtml = "",
947 | urlConfigHtml = "",
948 | oldconfig = extend( {}, config );
949 |
950 | QUnit.init();
951 | extend(config, oldconfig);
952 |
953 | config.blocking = false;
954 |
955 | len = config.urlConfig.length;
956 |
957 | for ( i = 0; i < len; i++ ) {
958 | val = config.urlConfig[i];
959 | if ( typeof val === "string" ) {
960 | val = {
961 | id: val,
962 | label: val,
963 | tooltip: "[no tooltip available]"
964 | };
965 | }
966 | config[ val.id ] = QUnit.urlParams[ val.id ];
967 | urlConfigHtml += "";
968 | }
969 |
970 | moduleFilterHtml += "";
978 |
979 | // `userAgent` initialized at top of scope
980 | userAgent = id( "qunit-userAgent" );
981 | if ( userAgent ) {
982 | userAgent.innerHTML = navigator.userAgent;
983 | }
984 |
985 | // `banner` initialized at top of scope
986 | banner = id( "qunit-header" );
987 | if ( banner ) {
988 | banner.innerHTML = "" + banner.innerHTML + " ";
989 | }
990 |
991 | // `toolbar` initialized at top of scope
992 | toolbar = id( "qunit-testrunner-toolbar" );
993 | if ( toolbar ) {
994 | // `filter` initialized at top of scope
995 | filter = document.createElement( "input" );
996 | filter.type = "checkbox";
997 | filter.id = "qunit-filter-pass";
998 |
999 | addEvent( filter, "click", function() {
1000 | var tmp,
1001 | ol = document.getElementById( "qunit-tests" );
1002 |
1003 | if ( filter.checked ) {
1004 | ol.className = ol.className + " hidepass";
1005 | } else {
1006 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
1007 | ol.className = tmp.replace( / hidepass /, " " );
1008 | }
1009 | if ( defined.sessionStorage ) {
1010 | if (filter.checked) {
1011 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
1012 | } else {
1013 | sessionStorage.removeItem( "qunit-filter-passed-tests" );
1014 | }
1015 | }
1016 | });
1017 |
1018 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
1019 | filter.checked = true;
1020 | // `ol` initialized at top of scope
1021 | ol = document.getElementById( "qunit-tests" );
1022 | ol.className = ol.className + " hidepass";
1023 | }
1024 | toolbar.appendChild( filter );
1025 |
1026 | // `label` initialized at top of scope
1027 | label = document.createElement( "label" );
1028 | label.setAttribute( "for", "qunit-filter-pass" );
1029 | label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
1030 | label.innerHTML = "Hide passed tests";
1031 | toolbar.appendChild( label );
1032 |
1033 | urlConfigCheckboxes = document.createElement( 'span' );
1034 | urlConfigCheckboxes.innerHTML = urlConfigHtml;
1035 | addEvent( urlConfigCheckboxes, "change", function( event ) {
1036 | var params = {};
1037 | params[ event.target.name ] = event.target.checked ? true : undefined;
1038 | window.location = QUnit.url( params );
1039 | });
1040 | toolbar.appendChild( urlConfigCheckboxes );
1041 |
1042 | if (numModules > 1) {
1043 | moduleFilter = document.createElement( 'span' );
1044 | moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
1045 | moduleFilter.innerHTML = moduleFilterHtml;
1046 | addEvent( moduleFilter, "change", function() {
1047 | var selectBox = moduleFilter.getElementsByTagName("select")[0],
1048 | selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
1049 |
1050 | window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
1051 | });
1052 | toolbar.appendChild(moduleFilter);
1053 | }
1054 | }
1055 |
1056 | // `main` initialized at top of scope
1057 | main = id( "qunit-fixture" );
1058 | if ( main ) {
1059 | config.fixture = main.innerHTML;
1060 | }
1061 |
1062 | if ( config.autostart ) {
1063 | QUnit.start();
1064 | }
1065 | };
1066 |
1067 | addEvent( window, "load", QUnit.load );
1068 |
1069 | // `onErrorFnPrev` initialized at top of scope
1070 | // Preserve other handlers
1071 | onErrorFnPrev = window.onerror;
1072 |
1073 | // Cover uncaught exceptions
1074 | // Returning true will surpress the default browser handler,
1075 | // returning false will let it run.
1076 | window.onerror = function ( error, filePath, linerNr ) {
1077 | var ret = false;
1078 | if ( onErrorFnPrev ) {
1079 | ret = onErrorFnPrev( error, filePath, linerNr );
1080 | }
1081 |
1082 | // Treat return value as window.onerror itself does,
1083 | // Only do our handling if not surpressed.
1084 | if ( ret !== true ) {
1085 | if ( QUnit.config.current ) {
1086 | if ( QUnit.config.current.ignoreGlobalErrors ) {
1087 | return true;
1088 | }
1089 | QUnit.pushFailure( error, filePath + ":" + linerNr );
1090 | } else {
1091 | QUnit.test( "global failure", extend( function() {
1092 | QUnit.pushFailure( error, filePath + ":" + linerNr );
1093 | }, { validTest: validTest } ) );
1094 | }
1095 | return false;
1096 | }
1097 |
1098 | return ret;
1099 | };
1100 |
1101 | function done() {
1102 | config.autorun = true;
1103 |
1104 | // Log the last module results
1105 | if ( config.currentModule ) {
1106 | runLoggingCallbacks( "moduleDone", QUnit, {
1107 | name: config.currentModule,
1108 | failed: config.moduleStats.bad,
1109 | passed: config.moduleStats.all - config.moduleStats.bad,
1110 | total: config.moduleStats.all
1111 | });
1112 | }
1113 |
1114 | var i, key,
1115 | banner = id( "qunit-banner" ),
1116 | tests = id( "qunit-tests" ),
1117 | runtime = +new Date() - config.started,
1118 | passed = config.stats.all - config.stats.bad,
1119 | html = [
1120 | "Tests completed in ",
1121 | runtime,
1122 | " milliseconds. ",
1123 | "",
1124 | passed,
1125 | " tests of ",
1126 | config.stats.all,
1127 | " passed, ",
1128 | config.stats.bad,
1129 | " failed."
1130 | ].join( "" );
1131 |
1132 | if ( banner ) {
1133 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
1134 | }
1135 |
1136 | if ( tests ) {
1137 | id( "qunit-testresult" ).innerHTML = html;
1138 | }
1139 |
1140 | if ( config.altertitle && typeof document !== "undefined" && document.title ) {
1141 | // show ✖ for good, ✔ for bad suite result in title
1142 | // use escape sequences in case file gets loaded with non-utf-8-charset
1143 | document.title = [
1144 | ( config.stats.bad ? "\u2716" : "\u2714" ),
1145 | document.title.replace( /^[\u2714\u2716] /i, "" )
1146 | ].join( " " );
1147 | }
1148 |
1149 | // clear own sessionStorage items if all tests passed
1150 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
1151 | // `key` & `i` initialized at top of scope
1152 | for ( i = 0; i < sessionStorage.length; i++ ) {
1153 | key = sessionStorage.key( i++ );
1154 | if ( key.indexOf( "qunit-test-" ) === 0 ) {
1155 | sessionStorage.removeItem( key );
1156 | }
1157 | }
1158 | }
1159 |
1160 | // scroll back to top to show results
1161 | if ( window.scrollTo ) {
1162 | window.scrollTo(0, 0);
1163 | }
1164 |
1165 | runLoggingCallbacks( "done", QUnit, {
1166 | failed: config.stats.bad,
1167 | passed: passed,
1168 | total: config.stats.all,
1169 | runtime: runtime
1170 | });
1171 | }
1172 |
1173 | /** @return Boolean: true if this test should be ran */
1174 | function validTest( test ) {
1175 | var include,
1176 | filter = config.filter && config.filter.toLowerCase(),
1177 | module = config.module && config.module.toLowerCase(),
1178 | fullName = (test.module + ": " + test.testName).toLowerCase();
1179 |
1180 | // Internally-generated tests are always valid
1181 | if ( test.callback && test.callback.validTest === validTest ) {
1182 | delete test.callback.validTest;
1183 | return true;
1184 | }
1185 |
1186 | if ( config.testNumber ) {
1187 | return test.testNumber === config.testNumber;
1188 | }
1189 |
1190 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
1191 | return false;
1192 | }
1193 |
1194 | if ( !filter ) {
1195 | return true;
1196 | }
1197 |
1198 | include = filter.charAt( 0 ) !== "!";
1199 | if ( !include ) {
1200 | filter = filter.slice( 1 );
1201 | }
1202 |
1203 | // If the filter matches, we need to honour include
1204 | if ( fullName.indexOf( filter ) !== -1 ) {
1205 | return include;
1206 | }
1207 |
1208 | // Otherwise, do the opposite
1209 | return !include;
1210 | }
1211 |
1212 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
1213 | // Later Safari and IE10 are supposed to support error.stack as well
1214 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1215 | function extractStacktrace( e, offset ) {
1216 | offset = offset === undefined ? 3 : offset;
1217 |
1218 | var stack, include, i, regex;
1219 |
1220 | if ( e.stacktrace ) {
1221 | // Opera
1222 | return e.stacktrace.split( "\n" )[ offset + 3 ];
1223 | } else if ( e.stack ) {
1224 | // Firefox, Chrome
1225 | stack = e.stack.split( "\n" );
1226 | if (/^error$/i.test( stack[0] ) ) {
1227 | stack.shift();
1228 | }
1229 | if ( fileName ) {
1230 | include = [];
1231 | for ( i = offset; i < stack.length; i++ ) {
1232 | if ( stack[ i ].indexOf( fileName ) !== -1 ) {
1233 | break;
1234 | }
1235 | include.push( stack[ i ] );
1236 | }
1237 | if ( include.length ) {
1238 | return include.join( "\n" );
1239 | }
1240 | }
1241 | return stack[ offset ];
1242 | } else if ( e.sourceURL ) {
1243 | // Safari, PhantomJS
1244 | // hopefully one day Safari provides actual stacktraces
1245 | // exclude useless self-reference for generated Error objects
1246 | if ( /qunit.js$/.test( e.sourceURL ) ) {
1247 | return;
1248 | }
1249 | // for actual exceptions, this is useful
1250 | return e.sourceURL + ":" + e.line;
1251 | }
1252 | }
1253 | function sourceFromStacktrace( offset ) {
1254 | try {
1255 | throw new Error();
1256 | } catch ( e ) {
1257 | return extractStacktrace( e, offset );
1258 | }
1259 | }
1260 |
1261 | function escapeInnerText( s ) {
1262 | if ( !s ) {
1263 | return "";
1264 | }
1265 | s = s + "";
1266 | return s.replace( /[\&<>]/g, function( s ) {
1267 | switch( s ) {
1268 | case "&": return "&";
1269 | case "<": return "<";
1270 | case ">": return ">";
1271 | default: return s;
1272 | }
1273 | });
1274 | }
1275 |
1276 | function synchronize( callback, last ) {
1277 | config.queue.push( callback );
1278 |
1279 | if ( config.autorun && !config.blocking ) {
1280 | process( last );
1281 | }
1282 | }
1283 |
1284 | function process( last ) {
1285 | function next() {
1286 | process( last );
1287 | }
1288 | var start = new Date().getTime();
1289 | config.depth = config.depth ? config.depth + 1 : 1;
1290 |
1291 | while ( config.queue.length && !config.blocking ) {
1292 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
1293 | config.queue.shift()();
1294 | } else {
1295 | window.setTimeout( next, 13 );
1296 | break;
1297 | }
1298 | }
1299 | config.depth--;
1300 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
1301 | done();
1302 | }
1303 | }
1304 |
1305 | function saveGlobal() {
1306 | config.pollution = [];
1307 |
1308 | if ( config.noglobals ) {
1309 | for ( var key in window ) {
1310 | // in Opera sometimes DOM element ids show up here, ignore them
1311 | if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
1312 | continue;
1313 | }
1314 | config.pollution.push( key );
1315 | }
1316 | }
1317 | }
1318 |
1319 | function checkPollution( name ) {
1320 | var newGlobals,
1321 | deletedGlobals,
1322 | old = config.pollution;
1323 |
1324 | saveGlobal();
1325 |
1326 | newGlobals = diff( config.pollution, old );
1327 | if ( newGlobals.length > 0 ) {
1328 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
1329 | }
1330 |
1331 | deletedGlobals = diff( old, config.pollution );
1332 | if ( deletedGlobals.length > 0 ) {
1333 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
1334 | }
1335 | }
1336 |
1337 | // returns a new Array with the elements that are in a but not in b
1338 | function diff( a, b ) {
1339 | var i, j,
1340 | result = a.slice();
1341 |
1342 | for ( i = 0; i < result.length; i++ ) {
1343 | for ( j = 0; j < b.length; j++ ) {
1344 | if ( result[i] === b[j] ) {
1345 | result.splice( i, 1 );
1346 | i--;
1347 | break;
1348 | }
1349 | }
1350 | }
1351 | return result;
1352 | }
1353 |
1354 | function extend( a, b ) {
1355 | for ( var prop in b ) {
1356 | if ( b[ prop ] === undefined ) {
1357 | delete a[ prop ];
1358 |
1359 | // Avoid "Member not found" error in IE8 caused by setting window.constructor
1360 | } else if ( prop !== "constructor" || a !== window ) {
1361 | a[ prop ] = b[ prop ];
1362 | }
1363 | }
1364 |
1365 | return a;
1366 | }
1367 |
1368 | function addEvent( elem, type, fn ) {
1369 | if ( elem.addEventListener ) {
1370 | elem.addEventListener( type, fn, false );
1371 | } else if ( elem.attachEvent ) {
1372 | elem.attachEvent( "on" + type, fn );
1373 | } else {
1374 | fn();
1375 | }
1376 | }
1377 |
1378 | function hasClass( elem, name ) {
1379 | return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
1380 | }
1381 |
1382 | function addClass( elem, name ) {
1383 | if ( !hasClass( elem, name ) ) {
1384 | elem.className += (elem.className ? " " : "") + name;
1385 | }
1386 | }
1387 |
1388 | function removeClass( elem, name ) {
1389 | var set = " " + elem.className + " ";
1390 | // Class name may appear multiple times
1391 | while ( set.indexOf(" " + name + " ") > -1 ) {
1392 | set = set.replace(" " + name + " " , " ");
1393 | }
1394 | // If possible, trim it for prettiness, but not neccecarily
1395 | elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
1396 | }
1397 |
1398 | function id( name ) {
1399 | return !!( typeof document !== "undefined" && document && document.getElementById ) &&
1400 | document.getElementById( name );
1401 | }
1402 |
1403 | function registerLoggingCallback( key ) {
1404 | return function( callback ) {
1405 | config[key].push( callback );
1406 | };
1407 | }
1408 |
1409 | // Supports deprecated method of completely overwriting logging callbacks
1410 | function runLoggingCallbacks( key, scope, args ) {
1411 | //debugger;
1412 | var i, callbacks;
1413 | if ( QUnit.hasOwnProperty( key ) ) {
1414 | QUnit[ key ].call(scope, args );
1415 | } else {
1416 | callbacks = config[ key ];
1417 | for ( i = 0; i < callbacks.length; i++ ) {
1418 | callbacks[ i ].call( scope, args );
1419 | }
1420 | }
1421 | }
1422 |
1423 | // Test for equality any JavaScript type.
1424 | // Author: Philippe Rathé
1425 | QUnit.equiv = (function() {
1426 |
1427 | // Call the o related callback with the given arguments.
1428 | function bindCallbacks( o, callbacks, args ) {
1429 | var prop = QUnit.objectType( o );
1430 | if ( prop ) {
1431 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1432 | return callbacks[ prop ].apply( callbacks, args );
1433 | } else {
1434 | return callbacks[ prop ]; // or undefined
1435 | }
1436 | }
1437 | }
1438 |
1439 | // the real equiv function
1440 | var innerEquiv,
1441 | // stack to decide between skip/abort functions
1442 | callers = [],
1443 | // stack to avoiding loops from circular referencing
1444 | parents = [],
1445 |
1446 | getProto = Object.getPrototypeOf || function ( obj ) {
1447 | return obj.__proto__;
1448 | },
1449 | callbacks = (function () {
1450 |
1451 | // for string, boolean, number and null
1452 | function useStrictEquality( b, a ) {
1453 | /*jshint eqeqeq:false */
1454 | if ( b instanceof a.constructor || a instanceof b.constructor ) {
1455 | // to catch short annotaion VS 'new' annotation of a
1456 | // declaration
1457 | // e.g. var i = 1;
1458 | // var j = new Number(1);
1459 | return a == b;
1460 | } else {
1461 | return a === b;
1462 | }
1463 | }
1464 |
1465 | return {
1466 | "string": useStrictEquality,
1467 | "boolean": useStrictEquality,
1468 | "number": useStrictEquality,
1469 | "null": useStrictEquality,
1470 | "undefined": useStrictEquality,
1471 |
1472 | "nan": function( b ) {
1473 | return isNaN( b );
1474 | },
1475 |
1476 | "date": function( b, a ) {
1477 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1478 | },
1479 |
1480 | "regexp": function( b, a ) {
1481 | return QUnit.objectType( b ) === "regexp" &&
1482 | // the regex itself
1483 | a.source === b.source &&
1484 | // and its modifers
1485 | a.global === b.global &&
1486 | // (gmi) ...
1487 | a.ignoreCase === b.ignoreCase &&
1488 | a.multiline === b.multiline &&
1489 | a.sticky === b.sticky;
1490 | },
1491 |
1492 | // - skip when the property is a method of an instance (OOP)
1493 | // - abort otherwise,
1494 | // initial === would have catch identical references anyway
1495 | "function": function() {
1496 | var caller = callers[callers.length - 1];
1497 | return caller !== Object && typeof caller !== "undefined";
1498 | },
1499 |
1500 | "array": function( b, a ) {
1501 | var i, j, len, loop;
1502 |
1503 | // b could be an object literal here
1504 | if ( QUnit.objectType( b ) !== "array" ) {
1505 | return false;
1506 | }
1507 |
1508 | len = a.length;
1509 | if ( len !== b.length ) {
1510 | // safe and faster
1511 | return false;
1512 | }
1513 |
1514 | // track reference to avoid circular references
1515 | parents.push( a );
1516 | for ( i = 0; i < len; i++ ) {
1517 | loop = false;
1518 | for ( j = 0; j < parents.length; j++ ) {
1519 | if ( parents[j] === a[i] ) {
1520 | loop = true;// dont rewalk array
1521 | }
1522 | }
1523 | if ( !loop && !innerEquiv(a[i], b[i]) ) {
1524 | parents.pop();
1525 | return false;
1526 | }
1527 | }
1528 | parents.pop();
1529 | return true;
1530 | },
1531 |
1532 | "object": function( b, a ) {
1533 | var i, j, loop,
1534 | // Default to true
1535 | eq = true,
1536 | aProperties = [],
1537 | bProperties = [];
1538 |
1539 | // comparing constructors is more strict than using
1540 | // instanceof
1541 | if ( a.constructor !== b.constructor ) {
1542 | // Allow objects with no prototype to be equivalent to
1543 | // objects with Object as their constructor.
1544 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
1545 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
1546 | return false;
1547 | }
1548 | }
1549 |
1550 | // stack constructor before traversing properties
1551 | callers.push( a.constructor );
1552 | // track reference to avoid circular references
1553 | parents.push( a );
1554 |
1555 | for ( i in a ) { // be strict: don't ensures hasOwnProperty
1556 | // and go deep
1557 | loop = false;
1558 | for ( j = 0; j < parents.length; j++ ) {
1559 | if ( parents[j] === a[i] ) {
1560 | // don't go down the same path twice
1561 | loop = true;
1562 | }
1563 | }
1564 | aProperties.push(i); // collect a's properties
1565 |
1566 | if (!loop && !innerEquiv( a[i], b[i] ) ) {
1567 | eq = false;
1568 | break;
1569 | }
1570 | }
1571 |
1572 | callers.pop(); // unstack, we are done
1573 | parents.pop();
1574 |
1575 | for ( i in b ) {
1576 | bProperties.push( i ); // collect b's properties
1577 | }
1578 |
1579 | // Ensures identical properties name
1580 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1581 | }
1582 | };
1583 | }());
1584 |
1585 | innerEquiv = function() { // can take multiple arguments
1586 | var args = [].slice.apply( arguments );
1587 | if ( args.length < 2 ) {
1588 | return true; // end transition
1589 | }
1590 |
1591 | return (function( a, b ) {
1592 | if ( a === b ) {
1593 | return true; // catch the most you can
1594 | } else if ( a === null || b === null || typeof a === "undefined" ||
1595 | typeof b === "undefined" ||
1596 | QUnit.objectType(a) !== QUnit.objectType(b) ) {
1597 | return false; // don't lose time with error prone cases
1598 | } else {
1599 | return bindCallbacks(a, callbacks, [ b, a ]);
1600 | }
1601 |
1602 | // apply transition with (1..n) arguments
1603 | }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
1604 | };
1605 |
1606 | return innerEquiv;
1607 | }());
1608 |
1609 | /**
1610 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1611 | * http://flesler.blogspot.com Licensed under BSD
1612 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1613 | *
1614 | * @projectDescription Advanced and extensible data dumping for Javascript.
1615 | * @version 1.0.0
1616 | * @author Ariel Flesler
1617 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1618 | */
1619 | QUnit.jsDump = (function() {
1620 | function quote( str ) {
1621 | return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
1622 | }
1623 | function literal( o ) {
1624 | return o + "";
1625 | }
1626 | function join( pre, arr, post ) {
1627 | var s = jsDump.separator(),
1628 | base = jsDump.indent(),
1629 | inner = jsDump.indent(1);
1630 | if ( arr.join ) {
1631 | arr = arr.join( "," + s + inner );
1632 | }
1633 | if ( !arr ) {
1634 | return pre + post;
1635 | }
1636 | return [ pre, inner + arr, base + post ].join(s);
1637 | }
1638 | function array( arr, stack ) {
1639 | var i = arr.length, ret = new Array(i);
1640 | this.up();
1641 | while ( i-- ) {
1642 | ret[i] = this.parse( arr[i] , undefined , stack);
1643 | }
1644 | this.down();
1645 | return join( "[", ret, "]" );
1646 | }
1647 |
1648 | var reName = /^function (\w+)/,
1649 | jsDump = {
1650 | // type is used mostly internally, you can fix a (custom)type in advance
1651 | parse: function( obj, type, stack ) {
1652 | stack = stack || [ ];
1653 | var inStack, res,
1654 | parser = this.parsers[ type || this.typeOf(obj) ];
1655 |
1656 | type = typeof parser;
1657 | inStack = inArray( obj, stack );
1658 |
1659 | if ( inStack !== -1 ) {
1660 | return "recursion(" + (inStack - stack.length) + ")";
1661 | }
1662 | if ( type === "function" ) {
1663 | stack.push( obj );
1664 | res = parser.call( this, obj, stack );
1665 | stack.pop();
1666 | return res;
1667 | }
1668 | return ( type === "string" ) ? parser : this.parsers.error;
1669 | },
1670 | typeOf: function( obj ) {
1671 | var type;
1672 | if ( obj === null ) {
1673 | type = "null";
1674 | } else if ( typeof obj === "undefined" ) {
1675 | type = "undefined";
1676 | } else if ( QUnit.is( "regexp", obj) ) {
1677 | type = "regexp";
1678 | } else if ( QUnit.is( "date", obj) ) {
1679 | type = "date";
1680 | } else if ( QUnit.is( "function", obj) ) {
1681 | type = "function";
1682 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
1683 | type = "window";
1684 | } else if ( obj.nodeType === 9 ) {
1685 | type = "document";
1686 | } else if ( obj.nodeType ) {
1687 | type = "node";
1688 | } else if (
1689 | // native arrays
1690 | toString.call( obj ) === "[object Array]" ||
1691 | // NodeList objects
1692 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1693 | ) {
1694 | type = "array";
1695 | } else if ( obj.constructor === Error.prototype.constructor ) {
1696 | type = "error";
1697 | } else {
1698 | type = typeof obj;
1699 | }
1700 | return type;
1701 | },
1702 | separator: function() {
1703 | return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
1704 | },
1705 | // extra can be a number, shortcut for increasing-calling-decreasing
1706 | indent: function( extra ) {
1707 | if ( !this.multiline ) {
1708 | return "";
1709 | }
1710 | var chr = this.indentChar;
1711 | if ( this.HTML ) {
1712 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1713 | }
1714 | return new Array( this._depth_ + (extra||0) ).join(chr);
1715 | },
1716 | up: function( a ) {
1717 | this._depth_ += a || 1;
1718 | },
1719 | down: function( a ) {
1720 | this._depth_ -= a || 1;
1721 | },
1722 | setParser: function( name, parser ) {
1723 | this.parsers[name] = parser;
1724 | },
1725 | // The next 3 are exposed so you can use them
1726 | quote: quote,
1727 | literal: literal,
1728 | join: join,
1729 | //
1730 | _depth_: 1,
1731 | // This is the list of parsers, to modify them, use jsDump.setParser
1732 | parsers: {
1733 | window: "[Window]",
1734 | document: "[Document]",
1735 | error: function(error) {
1736 | return "Error(\"" + error.message + "\")";
1737 | },
1738 | unknown: "[Unknown]",
1739 | "null": "null",
1740 | "undefined": "undefined",
1741 | "function": function( fn ) {
1742 | var ret = "function",
1743 | // functions never have name in IE
1744 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
1745 |
1746 | if ( name ) {
1747 | ret += " " + name;
1748 | }
1749 | ret += "( ";
1750 |
1751 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
1752 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
1753 | },
1754 | array: array,
1755 | nodelist: array,
1756 | "arguments": array,
1757 | object: function( map, stack ) {
1758 | var ret = [ ], keys, key, val, i;
1759 | QUnit.jsDump.up();
1760 | keys = [];
1761 | for ( key in map ) {
1762 | keys.push( key );
1763 | }
1764 | keys.sort();
1765 | for ( i = 0; i < keys.length; i++ ) {
1766 | key = keys[ i ];
1767 | val = map[ key ];
1768 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
1769 | }
1770 | QUnit.jsDump.down();
1771 | return join( "{", ret, "}" );
1772 | },
1773 | node: function( node ) {
1774 | var a, val,
1775 | open = QUnit.jsDump.HTML ? "<" : "<",
1776 | close = QUnit.jsDump.HTML ? ">" : ">",
1777 | tag = node.nodeName.toLowerCase(),
1778 | ret = open + tag;
1779 |
1780 | for ( a in QUnit.jsDump.DOMAttrs ) {
1781 | val = node[ QUnit.jsDump.DOMAttrs[a] ];
1782 | if ( val ) {
1783 | ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
1784 | }
1785 | }
1786 | return ret + close + open + "/" + tag + close;
1787 | },
1788 | // function calls it internally, it's the arguments part of the function
1789 | functionArgs: function( fn ) {
1790 | var args,
1791 | l = fn.length;
1792 |
1793 | if ( !l ) {
1794 | return "";
1795 | }
1796 |
1797 | args = new Array(l);
1798 | while ( l-- ) {
1799 | // 97 is 'a'
1800 | args[l] = String.fromCharCode(97+l);
1801 | }
1802 | return " " + args.join( ", " ) + " ";
1803 | },
1804 | // object calls it internally, the key part of an item in a map
1805 | key: quote,
1806 | // function calls it internally, it's the content of the function
1807 | functionCode: "[code]",
1808 | // node calls it internally, it's an html attribute value
1809 | attribute: quote,
1810 | string: quote,
1811 | date: quote,
1812 | regexp: literal,
1813 | number: literal,
1814 | "boolean": literal
1815 | },
1816 | DOMAttrs: {
1817 | //attributes to dump from nodes, name=>realName
1818 | id: "id",
1819 | name: "name",
1820 | "class": "className"
1821 | },
1822 | // if true, entities are escaped ( <, >, \t, space and \n )
1823 | HTML: false,
1824 | // indentation unit
1825 | indentChar: " ",
1826 | // if true, items in a collection, are separated by a \n, else just a space.
1827 | multiline: true
1828 | };
1829 |
1830 | return jsDump;
1831 | }());
1832 |
1833 | // from Sizzle.js
1834 | function getText( elems ) {
1835 | var i, elem,
1836 | ret = "";
1837 |
1838 | for ( i = 0; elems[i]; i++ ) {
1839 | elem = elems[i];
1840 |
1841 | // Get the text from text nodes and CDATA nodes
1842 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1843 | ret += elem.nodeValue;
1844 |
1845 | // Traverse everything else, except comment nodes
1846 | } else if ( elem.nodeType !== 8 ) {
1847 | ret += getText( elem.childNodes );
1848 | }
1849 | }
1850 |
1851 | return ret;
1852 | }
1853 |
1854 | // from jquery.js
1855 | function inArray( elem, array ) {
1856 | if ( array.indexOf ) {
1857 | return array.indexOf( elem );
1858 | }
1859 |
1860 | for ( var i = 0, length = array.length; i < length; i++ ) {
1861 | if ( array[ i ] === elem ) {
1862 | return i;
1863 | }
1864 | }
1865 |
1866 | return -1;
1867 | }
1868 |
1869 | /*
1870 | * Javascript Diff Algorithm
1871 | * By John Resig (http://ejohn.org/)
1872 | * Modified by Chu Alan "sprite"
1873 | *
1874 | * Released under the MIT license.
1875 | *
1876 | * More Info:
1877 | * http://ejohn.org/projects/javascript-diff-algorithm/
1878 | *
1879 | * Usage: QUnit.diff(expected, actual)
1880 | *
1881 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
1882 | */
1883 | QUnit.diff = (function() {
1884 | /*jshint eqeqeq:false, eqnull:true */
1885 | function diff( o, n ) {
1886 | var i,
1887 | ns = {},
1888 | os = {};
1889 |
1890 | for ( i = 0; i < n.length; i++ ) {
1891 | if ( ns[ n[i] ] == null ) {
1892 | ns[ n[i] ] = {
1893 | rows: [],
1894 | o: null
1895 | };
1896 | }
1897 | ns[ n[i] ].rows.push( i );
1898 | }
1899 |
1900 | for ( i = 0; i < o.length; i++ ) {
1901 | if ( os[ o[i] ] == null ) {
1902 | os[ o[i] ] = {
1903 | rows: [],
1904 | n: null
1905 | };
1906 | }
1907 | os[ o[i] ].rows.push( i );
1908 | }
1909 |
1910 | for ( i in ns ) {
1911 | if ( !hasOwn.call( ns, i ) ) {
1912 | continue;
1913 | }
1914 | if ( ns[i].rows.length === 1 && os[i] !== undefined && os[i].rows.length === 1 ) {
1915 | n[ ns[i].rows[0] ] = {
1916 | text: n[ ns[i].rows[0] ],
1917 | row: os[i].rows[0]
1918 | };
1919 | o[ os[i].rows[0] ] = {
1920 | text: o[ os[i].rows[0] ],
1921 | row: ns[i].rows[0]
1922 | };
1923 | }
1924 | }
1925 |
1926 | for ( i = 0; i < n.length - 1; i++ ) {
1927 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
1928 | n[ i + 1 ] == o[ n[i].row + 1 ] ) {
1929 |
1930 | n[ i + 1 ] = {
1931 | text: n[ i + 1 ],
1932 | row: n[i].row + 1
1933 | };
1934 | o[ n[i].row + 1 ] = {
1935 | text: o[ n[i].row + 1 ],
1936 | row: i + 1
1937 | };
1938 | }
1939 | }
1940 |
1941 | for ( i = n.length - 1; i > 0; i-- ) {
1942 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
1943 | n[ i - 1 ] == o[ n[i].row - 1 ]) {
1944 |
1945 | n[ i - 1 ] = {
1946 | text: n[ i - 1 ],
1947 | row: n[i].row - 1
1948 | };
1949 | o[ n[i].row - 1 ] = {
1950 | text: o[ n[i].row - 1 ],
1951 | row: i - 1
1952 | };
1953 | }
1954 | }
1955 |
1956 | return {
1957 | o: o,
1958 | n: n
1959 | };
1960 | }
1961 |
1962 | return function( o, n ) {
1963 | o = o.replace( /\s+$/, "" );
1964 | n = n.replace( /\s+$/, "" );
1965 |
1966 | var i, pre,
1967 | str = "",
1968 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
1969 | oSpace = o.match(/\s+/g),
1970 | nSpace = n.match(/\s+/g);
1971 |
1972 | if ( oSpace == null ) {
1973 | oSpace = [ " " ];
1974 | }
1975 | else {
1976 | oSpace.push( " " );
1977 | }
1978 |
1979 | if ( nSpace == null ) {
1980 | nSpace = [ " " ];
1981 | }
1982 | else {
1983 | nSpace.push( " " );
1984 | }
1985 |
1986 | if ( out.n.length === 0 ) {
1987 | for ( i = 0; i < out.o.length; i++ ) {
1988 | str += "" + out.o[i] + oSpace[i] + "";
1989 | }
1990 | }
1991 | else {
1992 | if ( out.n[0].text == null ) {
1993 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
1994 | str += "" + out.o[n] + oSpace[n] + "";
1995 | }
1996 | }
1997 |
1998 | for ( i = 0; i < out.n.length; i++ ) {
1999 | if (out.n[i].text == null) {
2000 | str += "" + out.n[i] + nSpace[i] + "";
2001 | }
2002 | else {
2003 | // `pre` initialized at top of scope
2004 | pre = "";
2005 |
2006 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
2007 | pre += "" + out.o[n] + oSpace[n] + "";
2008 | }
2009 | str += " " + out.n[i].text + nSpace[i] + pre;
2010 | }
2011 | }
2012 | }
2013 |
2014 | return str;
2015 | };
2016 | }());
2017 |
2018 | // for CommonJS enviroments, export everything
2019 | if ( typeof exports !== "undefined" ) {
2020 | extend( exports, QUnit );
2021 | }
2022 |
2023 | // get at whatever the global object is, like window in browsers
2024 | }( (function() {return this;}.call()) ));
2025 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | // Run tests in order
2 | QUnit.config.reorder = false
3 |
4 | $(function() {
5 | // Set up a div for us to mess around with
6 | $$('body').append('')
7 | var $dom = $('#testContext')
8 |
9 | test('get', function() {
10 | deepEqual($$('#testContext div').length, 0, 'There should be 0 divs')
11 | deepEqual($$('div', '#testContext').length, 0, 'There should be 0 divs')
12 | $dom.append('')
13 | deepEqual($$('#testContext div').length, 0, 'After adding a div, there should still be 0, because we are loading from cache')
14 | deepEqual($$('div', '#testContext').length, 0, 'After adding a div, there should still be 0, because we are loading from cache')
15 |
16 | deepEqual($('#testContext').$$('div').length, 0, 'Make sure this weird stuff uses the cache')
17 | deepEqual($$('div', $$('#testContext')).length, 0, 'Make sure this weird stuff uses the cache')
18 | deepEqual($$('#testContext').find('div').length, 1, 'non-cache should find it')
19 |
20 | deepEqual($$('body').$$('#testContext').length, 1, 'More than 1 context should work [object Object] and $("div")!=$("div") bugs')
21 | })
22 |
23 | test('clear', function() {
24 | deepEqual($$('#testContext div').length, 0, 'There should still be 0 divs in cache')
25 | deepEqual($$('div', '#testContext').length, 0, 'There should still be 0 divs in cache')
26 | $$clear('#testContext div')
27 | deepEqual($$('#testContext div').length, 1, 'After clearing the cache, we should have found the new div.')
28 | deepEqual($$('div', '#testContext').length, 0, 'After clearing the cache, we should not have found the new div for context.')
29 | $$('#testContext').$$clear()
30 | deepEqual($$('div', '#testContext').length, 1, 'After clearing the context cache, we should have found the new div for context.')
31 | deepEqual($$('#testContext').$$('div').length, 1, 'After clearing the cache, we should have found the new div.')
32 |
33 | $$('#testContext').append('
')
34 | deepEqual($$('#testContext p').length, 1)
35 | deepEqual($$('p', '#testContext').length, 1)
36 | $$('#tmp').append('')
37 | $$clear('#testContext')
38 | deepEqual($$('#testContext p').length, 1, 'wasn\'t cleared')
39 | deepEqual($$('p', '#testContext').length, 2)
40 | deepEqual($$('#testContext').$$('p').length, 2)
41 | $$('#testContext #tmp').remove()
42 | })
43 |
44 | test('fresh', function() {
45 | deepEqual($$('#testContext div').length, 1, 'There should still be 1 div in cache')
46 | deepEqual($$('div', '#testContext').length, 1, 'There should still be 1 div in cache')
47 | $$('#testContext div').remove()
48 | deepEqual($$('#testContext div').length, 1, 'div has been removed, but it should still be in cache')
49 | deepEqual($$fresh('#testContext div').length, 0, 'Doing a fresh call should find nothing')
50 | deepEqual($$('div', '#testContext').length, 1, 'div has been removed, but it should still be in cache')
51 | deepEqual($$fresh('div', '#testContext').length, 0, 'Doing a fresh call should find nothing')
52 | })
53 | })
54 |
--------------------------------------------------------------------------------