Changeset 27679
- Timestamp:
- 03/24/2014 05:59:45 AM (11 years ago)
- Location:
- trunk/tests/qunit/editor
- Files:
-
- 57 added
- 116 deleted
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/tests/qunit/editor/index.html
r27155 r27679 2 2 <html> 3 3 <head> 4 <meta charset="UTF-8" /> 5 <title>Test Runner</title> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 7 <link rel="stylesheet" href="js/qunit/testrunner.css" type="text/css" /> 8 <script src="js/qunit/testrunner.js"></script> 9 <script> 10 TestRunner.addSuites([ 11 "tinymce/tests.js", 12 "tinymce/dom/tests.js", 13 "tinymce/html/tests.js", 14 "tinymce/ui/tests.js", 15 "tinymce/util/tests.js", 16 "plugins/tests.js" 17 ]); 18 </script> 4 <meta charset="UTF-8" /> 5 <title>TinyMCE QUnit tests</title> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 7 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" /> 8 <title>QUnit tests</title> 9 <link rel="stylesheet" href="js/qunit/qunit.css" type="text/css" /> 10 <link rel="stylesheet" href="../../../src/wp-includes/js/tinymce/skins/lightgray/skin.min.css" type="text/css" /> 11 <link rel="stylesheet" href="tinymce/ui/css/ui-overrides.css" type="text/css" /> 12 <style> 13 #qunit-modulefilter-container { float: none; } 14 </style> 19 15 </head> 20 16 <body> 17 <div id="qunit"></div> 18 <div id="qunit-fixture"></div> 19 <div id="view" style="position: absolute; right: 0; top: 0"></div> 20 21 <script src="http://www.google.com/jsapi"></script> 22 <script>google.load("jquery", "1");</script> 23 <script src="js/qunit/qunit.js"></script> 24 <script src="../../../src/wp-includes/js/tinymce/tinymce.min.js"></script> 25 <!--<script src="../js/tinymce/classes/jquery.tinymce.js"></script> --> 26 <script src="js/utils.js"></script> 27 <script src="js/init.js"></script> 28 29 <!-- tinymce.dom.* --> 30 <script src="tinymce/dom/DOMUtils.js"></script> 31 <script src="tinymce/dom/EventUtils.js"></script> 32 <script src="tinymce/dom/Range.js"></script> 33 <script src="tinymce/dom/Selection.js"></script> 34 <script src="tinymce/dom/Serializer.js"></script> 35 <script src="tinymce/dom/TridentSelection.js"></script> 36 37 <!-- tinymce.html.* --> 38 <script src="tinymce/html/DomParser.js"></script> 39 <script src="tinymce/html/Entities.js"></script> 40 <script src="tinymce/html/Node.js"></script> 41 <script src="tinymce/html/SaxParser.js"></script> 42 <script src="tinymce/html/Schema.js"></script> 43 <script src="tinymce/html/Obsolete.js"></script> 44 <script src="tinymce/html/Styles.js"></script> 45 <script src="tinymce/html/Writer.js"></script> 46 47 <!-- tnymce.ui.* --> 48 <script src="tinymce/ui/AbsoluteLayout.js"></script> 49 <script src="tinymce/ui/Button.js"></script> 50 <script src="tinymce/ui/Collection.js"></script> 51 <script src="tinymce/ui/ColorButton.js"></script> 52 <script src="tinymce/ui/Control.js"></script> 53 <script src="tinymce/ui/FitLayout.js"></script> 54 <script src="tinymce/ui/FlexLayout.js"></script> 55 <script src="tinymce/ui/GridLayout.js"></script> 56 <script src="tinymce/ui/MenuButton.js"></script> 57 <script src="tinymce/ui/Panel.js"></script> 58 <script src="tinymce/ui/Selector.js"></script> 59 <script src="tinymce/ui/SplitButton.js"></script> 60 <script src="tinymce/ui/TabPanel.js"></script> 61 <script src="tinymce/ui/TextBox.js"></script> 62 <script src="tinymce/ui/Window.js"></script> 63 64 <!-- tinymce.util.* --> 65 <script src="tinymce/util/JSON.js"></script> 66 <script src="tinymce/util/JSONRequest.js"></script> 67 <script src="tinymce/util/LocalStorage.js"></script> 68 <script src="tinymce/util/Quirks_webkit.js"></script> 69 <script src="tinymce/util/URI.js"></script> 70 <script src="tinymce/util/XHR.js"></script> 71 72 <!-- tinymce.* --> 73 <script src="tinymce/Editor.js"></script> 74 <script src="tinymce/EditorCommands.js"></script> 75 <script src="tinymce/EnterKey.js"></script> 76 <script src="tinymce/ForceBlocks.js"></script> 77 <script src="tinymce/Formatter_apply.js"></script> 78 <script src="tinymce/Formatter_check.js"></script> 79 <script src="tinymce/Formatter_remove.js"></script> 80 <script src="tinymce/UndoManager.js"></script> 81 82 <!-- tinymce.plugins.* --> 83 <!--<script src="plugins/autosave.js"></script> 84 <script src="plugins/fullpage.js"></script> 85 <script src="plugins/jquery_plugin.js"></script> 86 <script src="plugins/legacyoutput.js"></script> 87 <script src="plugins/lists.js"></script> --> 88 <script src="plugins/media.js"></script> 89 <!--<script src="plugins/noneditable.js"></script> --> 90 <script src="plugins/paste.js"></script> 91 <!--<script src="plugins/searchreplace.js"></script> 92 <script src="plugins/spellchecker.js"></script> 93 <script src="plugins/table.js"></script> 94 <script src="plugins/wordcount.js"></script> --> 21 95 </body> 22 96 </html> -
trunk/tests/qunit/editor/js/qunit/qunit.css
r27155 r27679 1 2 ol#qunit-tests { 3 font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 4 margin:0; 5 padding:0; 6 list-style-position:inside; 7 1 /*! 2 * QUnit 1.14.0 3 * http://qunitjs.com/ 4 * 5 * Copyright 2013 jQuery Foundation and other contributors 6 * Released under the MIT license 7 * http://jquery.org/license 8 * 9 * Date: 2014-01-31T16:40Z 10 */ 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 0 0.5em 2em; 66 color: #5E740B; 67 background-color: #EEE; 68 overflow: hidden; 69 } 70 71 #qunit-userAgent { 72 padding: 0.5em 0 0.5em 2.5em; 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 } 81 82 /** Tests: Pass/Fail */ 83 84 #qunit-tests { 85 list-style-position: inside; 86 } 87 88 #qunit-tests li { 89 padding: 0.4em 0.5em 0.4em 2.5em; 90 border-bottom: 1px solid #FFF; 91 list-style-position: inside; 92 } 93 94 #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 95 display: none; 96 } 97 98 #qunit-tests li strong { 99 cursor: pointer; 100 } 101 102 #qunit-tests li a { 103 padding: 0.5em; 104 color: #C2CCD1; 105 text-decoration: none; 106 } 107 #qunit-tests li a:hover, 108 #qunit-tests li a:focus { 109 color: #000; 110 } 111 112 #qunit-tests li .runtime { 113 float: right; 8 114 font-size: smaller; 9 115 } 10 ol#qunit-tests li{ 11 padding:0.4em 0.5em 0.4em 2.5em; 12 border-bottom:1px solid #fff; 13 font-size:small; 14 list-style-position:inside; 15 } 16 ol#qunit-tests li ol{ 17 box-shadow: inset 0px 2px 13px #999; 18 -moz-box-shadow: inset 0px 2px 13px #999; 19 -webkit-box-shadow: inset 0px 2px 13px #999; 20 margin-top:0.5em; 21 margin-left:0; 22 padding:0.5em; 23 background-color:#fff; 24 border-radius:15px; 25 -moz-border-radius: 15px; 26 -webkit-border-radius: 15px; 27 } 28 ol#qunit-tests li li{ 29 border-bottom:none; 30 margin:0.5em; 31 background-color:#fff; 116 117 .qunit-assert-list { 118 margin-top: 0.5em; 119 padding: 0.5em; 120 121 background-color: #FFF; 122 123 border-radius: 5px; 124 } 125 126 .qunit-collapsed { 127 display: none; 128 } 129 130 #qunit-tests table { 131 border-collapse: collapse; 132 margin-top: 0.2em; 133 } 134 135 #qunit-tests th { 136 text-align: right; 137 vertical-align: top; 138 padding: 0 0.5em 0 0; 139 } 140 141 #qunit-tests td { 142 vertical-align: top; 143 } 144 145 #qunit-tests pre { 146 margin: 0; 147 white-space: pre-wrap; 148 word-wrap: break-word; 149 } 150 151 #qunit-tests del { 152 background-color: #E0F2BE; 153 color: #374E0C; 154 text-decoration: none; 155 } 156 157 #qunit-tests ins { 158 background-color: #FFCACA; 159 color: #500; 160 text-decoration: none; 161 } 162 163 /*** Test Counts */ 164 165 #qunit-tests b.counts { color: #000; } 166 #qunit-tests b.passed { color: #5E740B; } 167 #qunit-tests b.failed { color: #710909; } 168 169 #qunit-tests li li { 170 padding: 5px; 171 background-color: #FFF; 172 border-bottom: none; 32 173 list-style-position: inside; 33 padding:0.4em 0.5em 0.4em 0.5em; 34 } 35 36 ol#qunit-tests li li.pass{ 37 border-left:26px solid #C6E746; 38 background-color:#fff; 39 color:#5E740B; 40 } 41 ol#qunit-tests li li.fail{ 42 border-left:26px solid #EE5757; 43 background-color:#fff; 44 color:#710909; 45 } 46 ol#qunit-tests li.pass{ 47 background-color:#D2E0E6; 48 color:#528CE0; 49 } 50 ol#qunit-tests li.fail{ 51 background-color:#EE5757; 52 color:#000; 53 } 54 ol#qunit-tests li strong { 55 cursor:pointer; 56 } 57 h1#qunit-header{ 58 background-color:#0d3349; 59 margin:0; 60 padding:0.5em 0 0.5em 1em; 61 color:#fff; 62 font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 63 border-top-right-radius:15px; 64 border-top-left-radius:15px; 65 -moz-border-radius-topright:15px; 66 -moz-border-radius-topleft:15px; 67 -webkit-border-top-right-radius:15px; 68 -webkit-border-top-left-radius:15px; 69 text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; 70 } 71 h2#qunit-banner{ 72 font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 73 height:5px; 74 margin:0; 75 padding:0; 76 } 77 h2#qunit-banner.qunit-pass{ 78 background-color:#C6E746; 79 } 80 h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { 81 background-color:#EE5757; 82 } 83 #qunit-testrunner-toolbar { 84 font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 85 padding:0; 86 /*width:80%;*/ 87 padding:0em 0 0.5em 2em; 88 font-size: small; 89 } 90 h2#qunit-userAgent { 91 font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 92 background-color:#2b81af; 93 margin:0; 94 padding:0; 95 color:#fff; 96 font-size: small; 97 padding:0.5em 0 0.5em 2.5em; 98 text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 99 } 100 p#qunit-testresult{ 101 font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 102 margin:0; 103 font-size: small; 104 color:#2b81af; 105 border-bottom-right-radius:15px; 106 border-bottom-left-radius:15px; 107 -moz-border-radius-bottomright:15px; 108 -moz-border-radius-bottomleft:15px; 109 -webkit-border-bottom-right-radius:15px; 110 -webkit-border-bottom-left-radius:15px; 111 background-color:#D2E0E6; 112 padding:0.5em 0.5em 0.5em 2.5em; 113 } 114 strong b.fail{ 115 color:#710909; 116 } 117 strong b.pass{ 118 color:#5E740B; 119 } 174 } 175 176 /*** Passing Styles */ 177 178 #qunit-tests li li.pass { 179 color: #3C510C; 180 background-color: #FFF; 181 border-left: 10px solid #C6E746; 182 } 183 184 #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 185 #qunit-tests .pass .test-name { color: #366097; } 186 187 #qunit-tests .pass .test-actual, 188 #qunit-tests .pass .test-expected { color: #999; } 189 190 #qunit-banner.qunit-pass { background-color: #C6E746; } 191 192 /*** Failing Styles */ 193 194 #qunit-tests li li.fail { 195 color: #710909; 196 background-color: #FFF; 197 border-left: 10px solid #EE5757; 198 white-space: pre; 199 } 200 201 #qunit-tests > li:last-child { 202 border-radius: 0 0 5px 5px; 203 } 204 205 #qunit-tests .fail { color: #000; background-color: #EE5757; } 206 #qunit-tests .fail .test-name, 207 #qunit-tests .fail .module-name { color: #000; } 208 209 #qunit-tests .fail .test-actual { color: #EE5757; } 210 #qunit-tests .fail .test-expected { color: #008000; } 211 212 #qunit-banner.qunit-fail { background-color: #EE5757; } 213 214 215 /** Result */ 216 217 #qunit-testresult { 218 padding: 0.5em 0.5em 0.5em 2.5em; 219 220 color: #2B81AF; 221 background-color: #D2E0E6; 222 223 border-bottom: 1px solid #FFF; 224 } 225 #qunit-testresult .module-name { 226 font-weight: 700; 227 } 228 229 /** Fixture */ 230 231 #qunit-fixture { 232 position: absolute; 233 top: -10000px; 234 left: -10000px; 235 width: 1000px; 236 height: 1000px; 237 } -
trunk/tests/qunit/editor/js/qunit/qunit.js
r27155 r27679 1 /* 2 * QUnit - A JavaScript Unit Testing Framework 3 * 4 * http://docs.jquery.com/QUnit 1 /*! 2 * QUnit 1.14.0 3 * http://qunitjs.com/ 5 4 * 6 * Copyright (c) 2009 John Resig, Jörn Zaefferer 7 * Dual licensed under the MIT (MIT-LICENSE.txt) 8 * and GPL (GPL-LICENSE.txt) licenses. 5 * Copyright 2013 jQuery Foundation and other contributors 6 * Released under the MIT license 7 * http://jquery.org/license 8 * 9 * Date: 2014-01-31T16:40Z 9 10 */ 10 11 11 (function(window) { 12 13 var QUnit = { 12 (function( window ) { 13 14 var QUnit, 15 assert, 16 config, 17 onErrorFnPrev, 18 testId = 0, 19 fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 20 toString = Object.prototype.toString, 21 hasOwn = Object.prototype.hasOwnProperty, 22 // Keep a local reference to Date (GH-283) 23 Date = window.Date, 24 setTimeout = window.setTimeout, 25 clearTimeout = window.clearTimeout, 26 defined = { 27 document: typeof window.document !== "undefined", 28 setTimeout: typeof window.setTimeout !== "undefined", 29 sessionStorage: (function() { 30 var x = "qunit-test-string"; 31 try { 32 sessionStorage.setItem( x, x ); 33 sessionStorage.removeItem( x ); 34 return true; 35 } catch( e ) { 36 return false; 37 } 38 }()) 39 }, 40 /** 41 * Provides a normalized error string, correcting an issue 42 * with IE 7 (and prior) where Error.prototype.toString is 43 * not properly implemented 44 * 45 * Based on http://es5.github.com/#x15.11.4.4 46 * 47 * @param {String|Error} error 48 * @return {String} error message 49 */ 50 errorString = function( error ) { 51 var name, message, 52 errorString = error.toString(); 53 if ( errorString.substring( 0, 7 ) === "[object" ) { 54 name = error.name ? error.name.toString() : "Error"; 55 message = error.message ? error.message.toString() : ""; 56 if ( name && message ) { 57 return name + ": " + message; 58 } else if ( name ) { 59 return name; 60 } else if ( message ) { 61 return message; 62 } else { 63 return "Error"; 64 } 65 } else { 66 return errorString; 67 } 68 }, 69 /** 70 * Makes a clone of an object using only Array or Object as base, 71 * and copies over the own enumerable properties. 72 * 73 * @param {Object} obj 74 * @return {Object} New object with only the own properties (recursively). 75 */ 76 objectValues = function( obj ) { 77 // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. 78 /*jshint newcap: false */ 79 var key, val, 80 vals = QUnit.is( "array", obj ) ? [] : {}; 81 for ( key in obj ) { 82 if ( hasOwn.call( obj, key ) ) { 83 val = obj[key]; 84 vals[key] = val === Object(val) ? objectValues(val) : val; 85 } 86 } 87 return vals; 88 }; 89 90 91 // Root QUnit object. 92 // `QUnit` initialized at top of scope 93 QUnit = { 14 94 15 95 // call on start of module test to prepend name to all tests 16 module: function( name, testEnvironment) {96 module: function( name, testEnvironment ) { 17 97 config.currentModule = name; 18 19 synchronize(function() { 20 if ( config.currentModule ) { 21 QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 22 } 23 24 config.currentModule = name; 25 config.moduleTestEnvironment = testEnvironment; 26 config.moduleStats = { all: 0, bad: 0 }; 27 28 QUnit.moduleStart( name, testEnvironment ); 29 }); 30 }, 31 32 asyncTest: function(testName, expected, callback) { 33 if ( arguments.length === 2 ) { 34 callback = expected; 35 expected = 0; 36 } 37 38 QUnit.test(testName, expected, callback, true); 39 }, 40 41 test: function(testName, expected, callback, async) { 42 var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg; 43 98 config.currentModuleTestEnvironment = testEnvironment; 99 config.modules[name] = true; 100 }, 101 102 asyncTest: function( testName, expected, callback ) { 44 103 if ( arguments.length === 2 ) { 45 104 callback = expected; 46 105 expected = null; 47 106 } 48 // is 2nd argument a testEnvironment? 49 if ( expected && typeof expected === 'object') { 50 testEnvironmentArg = expected; 107 108 QUnit.test( testName, expected, callback, true ); 109 }, 110 111 test: function( testName, expected, callback, async ) { 112 var test, 113 nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>"; 114 115 if ( arguments.length === 2 ) { 116 callback = expected; 51 117 expected = null; 52 118 } 53 119 54 120 if ( config.currentModule ) { 55 name = '<span class="module-name">' + config.currentModule + "</span>: " + name; 56 } 57 58 if ( !validTest(config.currentModule + ": " + testName) ) { 121 nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml; 122 } 123 124 test = new Test({ 125 nameHtml: nameHtml, 126 testName: testName, 127 expected: expected, 128 async: async, 129 callback: callback, 130 module: config.currentModule, 131 moduleTestEnvironment: config.currentModuleTestEnvironment, 132 stack: sourceFromStacktrace( 2 ) 133 }); 134 135 if ( !validTest( test ) ) { 59 136 return; 60 137 } 61 138 62 synchronize(function() { 63 64 testEnvironment = extend({ 65 setup: function() {}, 66 teardown: function() {} 67 }, config.moduleTestEnvironment); 68 if (testEnvironmentArg) { 69 extend(testEnvironment,testEnvironmentArg); 70 } 71 72 QUnit.testStart( testName, testEnvironment ); 73 74 // allow utility functions to access the current test environment 75 QUnit.current_testEnvironment = testEnvironment; 76 77 config.assertions = []; 78 config.expected = expected; 79 80 var tests = id("qunit-tests"); 81 if (tests) { 82 var b = document.createElement("strong"); 83 b.innerHTML = "Running " + name; 84 var li = document.createElement("li"); 85 li.appendChild( b ); 86 li.id = "current-test-output"; 87 tests.appendChild( li ) 88 } 89 90 try { 91 if ( !config.pollution ) { 92 saveGlobal(); 93 } 94 95 testEnvironment.setup.call(testEnvironment); 96 } catch(e) { 97 QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 98 } 99 }); 100 101 synchronize(function() { 102 if ( async ) { 103 QUnit.stop(); 104 } 105 106 try { 107 callback.call(testEnvironment); 108 } catch(e) { 109 fail("Test " + name + " died, exception and test follows", e, callback); 110 QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 111 // else next test will carry the responsibility 112 saveGlobal(); 113 114 // Restart the tests if they're blocking 115 if ( config.blocking ) { 116 start(); 117 } 118 } 119 }); 120 121 synchronize(function() { 122 try { 123 checkPollution(); 124 testEnvironment.teardown.call(testEnvironment); 125 } catch(e) { 126 QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 127 } 128 }); 129 130 synchronize(function() { 131 try { 132 QUnit.reset(); 133 } catch(e) { 134 fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 135 } 136 137 if ( config.expected && config.expected != config.assertions.length ) { 138 QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 139 } 140 141 var good = 0, bad = 0, 142 tests = id("qunit-tests"); 143 144 config.stats.all += config.assertions.length; 145 config.moduleStats.all += config.assertions.length; 146 147 if ( tests ) { 148 var ol = document.createElement("ol"); 149 150 for ( var i = 0; i < config.assertions.length; i++ ) { 151 var assertion = config.assertions[i]; 152 153 var li = document.createElement("li"); 154 li.className = assertion.result ? "pass" : "fail"; 155 li.innerHTML = assertion.message || "(no message)"; 156 ol.appendChild( li ); 157 158 if ( assertion.result ) { 159 good++; 160 } else { 161 bad++; 162 config.stats.bad++; 163 config.moduleStats.bad++; 164 } 165 } 166 if (bad == 0) { 167 ol.style.display = "none"; 168 } 169 170 var b = document.createElement("strong"); 171 b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>"; 172 173 addEvent(b, "click", function() { 174 var next = b.nextSibling, display = next.style.display; 175 next.style.display = display === "none" ? "block" : "none"; 139 test.queue(); 140 }, 141 142 // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. 143 expect: function( asserts ) { 144 if (arguments.length === 1) { 145 config.current.expected = asserts; 146 } else { 147 return config.current.expected; 148 } 149 }, 150 151 start: function( count ) { 152 // QUnit hasn't been initialized yet. 153 // Note: RequireJS (et al) may delay onLoad 154 if ( config.semaphore === undefined ) { 155 QUnit.begin(function() { 156 // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first 157 setTimeout(function() { 158 QUnit.start( count ); 176 159 }); 177 178 addEvent(b, "dblclick", function(e) { 179 var target = e && e.target ? e.target : window.event.srcElement; 180 if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 181 target = target.parentNode; 182 } 183 if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 184 window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); 185 } 186 }); 187 188 var li = id("current-test-output"); 189 li.id = ""; 190 li.className = bad ? "fail" : "pass"; 191 li.removeChild( li.firstChild ); 192 li.appendChild( b ); 193 li.appendChild( ol ); 194 195 if ( bad ) { 196 var toolbar = id("qunit-testrunner-toolbar"); 197 if ( toolbar ) { 198 toolbar.style.display = "block"; 199 id("qunit-filter-pass").disabled = null; 200 id("qunit-filter-missing").disabled = null; 201 } 202 } 203 204 } else { 205 for ( var i = 0; i < config.assertions.length; i++ ) { 206 if ( !config.assertions[i].result ) { 207 bad++; 208 config.stats.bad++; 209 config.moduleStats.bad++; 210 } 211 } 212 } 213 214 QUnit.testDone( testName, bad, config.assertions.length ); 215 216 if ( !window.setTimeout && !config.queue.length ) { 217 done(); 218 } 219 }); 220 221 if ( window.setTimeout && !config.doneTimer ) { 222 config.doneTimer = window.setTimeout(function(){ 223 if ( !config.queue.length ) { 224 done(); 225 } else { 226 synchronize( done ); 227 } 228 }, 13); 229 } 230 }, 231 232 /** 233 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 234 */ 235 expect: function(asserts) { 236 config.expected = asserts; 237 }, 238 239 /** 240 * Asserts true. 241 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 242 */ 243 ok: function(a, msg) { 244 msg = escapeHtml(msg); 245 QUnit.log(a, msg); 246 247 config.assertions.push({ 248 result: !!a, 249 message: msg 250 }); 251 }, 252 253 /** 254 * Checks that the first two arguments are equal, with an optional message. 255 * Prints out both actual and expected values. 256 * 257 * Prefered to ok( actual == expected, message ) 258 * 259 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 260 * 261 * @param Object actual 262 * @param Object expected 263 * @param String message (optional) 264 */ 265 equal: function(actual, expected, message) { 266 push(expected == actual, actual, expected, message); 267 }, 268 269 notEqual: function(actual, expected, message) { 270 push(expected != actual, actual, expected, message); 271 }, 272 273 deepEqual: function(actual, expected, message) { 274 push(QUnit.equiv(actual, expected), actual, expected, message); 275 }, 276 277 notDeepEqual: function(actual, expected, message) { 278 push(!QUnit.equiv(actual, expected), actual, expected, message); 279 }, 280 281 strictEqual: function(actual, expected, message) { 282 push(expected === actual, actual, expected, message); 283 }, 284 285 notStrictEqual: function(actual, expected, message) { 286 push(expected !== actual, actual, expected, message); 287 }, 288 289 raises: function(fn, message) { 290 try { 291 fn(); 292 ok( false, message ); 293 } 294 catch (e) { 295 ok( true, message ); 296 } 297 }, 298 299 start: function() { 160 }); 161 return; 162 } 163 164 config.semaphore -= count || 1; 165 // don't start until equal number of stop-calls 166 if ( config.semaphore > 0 ) { 167 return; 168 } 169 // ignore if start is called more often then stop 170 if ( config.semaphore < 0 ) { 171 config.semaphore = 0; 172 QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); 173 return; 174 } 300 175 // A slight delay, to avoid any current callbacks 301 if ( window.setTimeout ) { 302 window.setTimeout(function() { 176 if ( defined.setTimeout ) { 177 setTimeout(function() { 178 if ( config.semaphore > 0 ) { 179 return; 180 } 303 181 if ( config.timeout ) { 304 clearTimeout( config.timeout);182 clearTimeout( config.timeout ); 305 183 } 306 184 307 185 config.blocking = false; 308 process( );186 process( true ); 309 187 }, 13); 310 188 } else { 311 189 config.blocking = false; 312 process(); 313 } 314 }, 315 316 stop: function(timeout) { 190 process( true ); 191 } 192 }, 193 194 stop: function( count ) { 195 config.semaphore += count || 1; 317 196 config.blocking = true; 318 197 319 if ( timeout && window.setTimeout ) { 320 config.timeout = window.setTimeout(function() { 198 if ( config.testTimeout && defined.setTimeout ) { 199 clearTimeout( config.timeout ); 200 config.timeout = setTimeout(function() { 321 201 QUnit.ok( false, "Test timed out" ); 202 config.semaphore = 1; 322 203 QUnit.start(); 323 }, timeout); 324 } 325 } 326 204 }, config.testTimeout ); 205 } 206 } 327 207 }; 328 208 329 // Backwards compatibility, deprecated 330 QUnit.equals = QUnit.equal; 331 QUnit.same = QUnit.deepEqual; 332 333 // Maintain internal state 334 var config = { 209 // We use the prototype to distinguish between properties that should 210 // be exposed as globals (and in exports) and those that shouldn't 211 (function() { 212 function F() {} 213 F.prototype = QUnit; 214 QUnit = new F(); 215 // Make F QUnit's constructor so that we can add to the prototype later 216 QUnit.constructor = F; 217 }()); 218 219 /** 220 * Config object: Maintain internal state 221 * Later exposed as QUnit.config 222 * `config` initialized at top of scope 223 */ 224 config = { 335 225 // The queue of tests to run 336 226 queue: [], 337 227 338 228 // block until document ready 339 blocking: true 229 blocking: true, 230 231 // when enabled, show only failing tests 232 // gets persisted through sessionStorage and can be changed in UI via checkbox 233 hidepassed: false, 234 235 // by default, run previously failed tests first 236 // very useful in combination with "Hide passed tests" checked 237 reorder: true, 238 239 // by default, modify document.title when suite is done 240 altertitle: true, 241 242 // by default, scroll to top of the page when suite is done 243 scrolltop: true, 244 245 // when enabled, all tests must call expect() 246 requireExpects: false, 247 248 // add checkboxes that are persisted in the query-string 249 // when enabled, the id is set to `true` as a `QUnit.config` property 250 urlConfig: [ 251 { 252 id: "noglobals", 253 label: "Check for Globals", 254 tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 255 }, 256 { 257 id: "notrycatch", 258 label: "No try-catch", 259 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 260 } 261 ], 262 263 // Set of all modules. 264 modules: {}, 265 266 // logging callback queues 267 begin: [], 268 done: [], 269 log: [], 270 testStart: [], 271 testDone: [], 272 moduleStart: [], 273 moduleDone: [] 340 274 }; 341 275 342 // Load parameters276 // Initialize more QUnit.config and QUnit.urlParams 343 277 (function() { 344 var location = window.location || { search: "", protocol: "file:" }, 345 GETParams = location.search.slice(1).split('&'); 346 347 for ( var i = 0; i < GETParams.length; i++ ) { 348 GETParams[i] = decodeURIComponent( GETParams[i] ); 349 if ( GETParams[i] === "noglobals" ) { 350 GETParams.splice( i, 1 ); 351 i--; 352 config.noglobals = true; 353 } else if ( GETParams[i].search('=') > -1 ) { 354 GETParams.splice( i, 1 ); 355 i--; 356 } 357 } 358 359 // restrict modules/tests by get parameters 360 config.filters = GETParams; 361 278 var i, current, 279 location = window.location || { search: "", protocol: "file:" }, 280 params = location.search.slice( 1 ).split( "&" ), 281 length = params.length, 282 urlParams = {}; 283 284 if ( params[ 0 ] ) { 285 for ( i = 0; i < length; i++ ) { 286 current = params[ i ].split( "=" ); 287 current[ 0 ] = decodeURIComponent( current[ 0 ] ); 288 289 // allow just a key to turn on a flag, e.g., test.html?noglobals 290 current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 291 if ( urlParams[ current[ 0 ] ] ) { 292 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); 293 } else { 294 urlParams[ current[ 0 ] ] = current[ 1 ]; 295 } 296 } 297 } 298 299 QUnit.urlParams = urlParams; 300 301 // String search anywhere in moduleName+testName 302 config.filter = urlParams.filter; 303 304 // Exact match of the module name 305 config.module = urlParams.module; 306 307 config.testNumber = []; 308 if ( urlParams.testNumber ) { 309 310 // Ensure that urlParams.testNumber is an array 311 urlParams.testNumber = [].concat( urlParams.testNumber ); 312 for ( i = 0; i < urlParams.testNumber.length; i++ ) { 313 current = urlParams.testNumber[ i ]; 314 config.testNumber.push( parseInt( current, 10 ) ); 315 } 316 } 317 362 318 // Figure out if we're running the tests from a server or not 363 QUnit.isLocal = !!(location.protocol === 'file:'); 364 })(); 365 366 // Expose the API as global variables, unless an 'exports' 367 // object exists, in that case we assume we're in CommonJS 368 if ( typeof exports === "undefined" || typeof require === "undefined" ) { 369 extend(window, QUnit); 370 window.QUnit = QUnit; 371 } else { 372 extend(exports, QUnit); 373 exports.QUnit = QUnit; 374 } 375 376 // define these after exposing globals to keep them in these QUnit namespace only 377 extend(QUnit, { 319 QUnit.isLocal = location.protocol === "file:"; 320 }()); 321 322 extend( QUnit, { 323 378 324 config: config, 379 325 380 326 // Initialize the configuration options 381 327 init: function() { 382 extend( config, {328 extend( config, { 383 329 stats: { all: 0, bad: 0 }, 384 330 moduleStats: { all: 0, bad: 0 }, 385 started: +new Date ,331 started: +new Date(), 386 332 updateRate: 1000, 387 333 blocking: false, 388 334 autostart: true, 389 335 autorun: false, 390 assertions: [],391 filters: [],392 queue: []336 filter: "", 337 queue: [], 338 semaphore: 1 393 339 }); 394 340 395 var tests = id("qunit-tests"), 396 banner = id("qunit-banner"), 397 result = id("qunit-testresult"); 341 var tests, banner, result, 342 qunit = id( "qunit" ); 343 344 if ( qunit ) { 345 qunit.innerHTML = 346 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + 347 "<h2 id='qunit-banner'></h2>" + 348 "<div id='qunit-testrunner-toolbar'></div>" + 349 "<h2 id='qunit-userAgent'></h2>" + 350 "<ol id='qunit-tests'></ol>"; 351 } 352 353 tests = id( "qunit-tests" ); 354 banner = id( "qunit-banner" ); 355 result = id( "qunit-testresult" ); 398 356 399 357 if ( tests ) { … … 408 366 result.parentNode.removeChild( result ); 409 367 } 410 }, 411 412 /** 413 * Resets the test setup. Useful for tests that modify the DOM. 414 */ 368 369 if ( tests ) { 370 result = document.createElement( "p" ); 371 result.id = "qunit-testresult"; 372 result.className = "result"; 373 tests.parentNode.insertBefore( result, tests ); 374 result.innerHTML = "Running...<br/> "; 375 } 376 }, 377 378 // Resets the test setup. Useful for tests that modify the DOM. 379 /* 380 DEPRECATED: Use multiple tests instead of resetting inside a test. 381 Use testStart or testDone for custom cleanup. 382 This method will throw an error in 2.0, and will be removed in 2.1 383 */ 415 384 reset: function() { 416 if ( window.jQuery ) { 417 jQuery("#main, #qunit-fixture").html( config.fixture ); 418 } 419 }, 420 421 /** 422 * Trigger an event on an element. 423 * 424 * @example triggerEvent( document.body, "click" ); 425 * 426 * @param DOMElement elem 427 * @param String type 428 */ 429 triggerEvent: function( elem, type, event ) { 430 if ( document.createEvent ) { 431 event = document.createEvent("MouseEvents"); 432 event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 433 0, 0, 0, 0, 0, false, false, false, false, 0, null); 434 elem.dispatchEvent( event ); 435 436 } else if ( elem.fireEvent ) { 437 elem.fireEvent("on"+type); 438 } 439 }, 440 385 var fixture = id( "qunit-fixture" ); 386 if ( fixture ) { 387 fixture.innerHTML = config.fixture; 388 } 389 }, 390 441 391 // Safe object type checking 442 392 is: function( type, obj ) { 443 return QUnit.objectType( obj ) == type;444 }, 445 393 return QUnit.objectType( obj ) === type; 394 }, 395 446 396 objectType: function( obj ) { 447 if (typeof obj === "undefined") { 448 return "undefined"; 449 450 // consider: typeof null === object 451 } 452 if (obj === null) { 453 return "null"; 454 } 455 456 var type = Object.prototype.toString.call( obj ) 457 .match(/^\[object\s(.*)\]$/)[1] || ''; 458 459 switch (type) { 460 case 'Number': 461 if (isNaN(obj)) { 462 return "nan"; 463 } else { 464 return "number"; 465 } 466 case 'String': 467 case 'Boolean': 468 case 'Array': 469 case 'Date': 470 case 'RegExp': 471 case 'Function': 472 return type.toLowerCase(); 473 } 474 if (typeof obj === "object") { 475 return "object"; 397 if ( typeof obj === "undefined" ) { 398 return "undefined"; 399 } 400 401 // Consider: typeof null === object 402 if ( obj === null ) { 403 return "null"; 404 } 405 406 var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), 407 type = match && match[1] || ""; 408 409 switch ( type ) { 410 case "Number": 411 if ( isNaN(obj) ) { 412 return "nan"; 413 } 414 return "number"; 415 case "String": 416 case "Boolean": 417 case "Array": 418 case "Date": 419 case "RegExp": 420 case "Function": 421 return type.toLowerCase(); 422 } 423 if ( typeof obj === "object" ) { 424 return "object"; 476 425 } 477 426 return undefined; 478 427 }, 479 480 // Logging callbacks 481 begin: function() {}, 482 done: function(failures, total) {}, 483 log: function(result, message) {}, 484 testStart: function(name, testEnvironment) {}, 485 testDone: function(name, failures, total) {}, 486 moduleStart: function(name, testEnvironment) {}, 487 moduleDone: function(name, failures, total) {} 428 429 push: function( result, actual, expected, message ) { 430 if ( !config.current ) { 431 throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 432 } 433 434 var output, source, 435 details = { 436 module: config.current.module, 437 name: config.current.testName, 438 result: result, 439 message: message, 440 actual: actual, 441 expected: expected 442 }; 443 444 message = escapeText( message ) || ( result ? "okay" : "failed" ); 445 message = "<span class='test-message'>" + message + "</span>"; 446 output = message; 447 448 if ( !result ) { 449 expected = escapeText( QUnit.jsDump.parse(expected) ); 450 actual = escapeText( QUnit.jsDump.parse(actual) ); 451 output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; 452 453 if ( actual !== expected ) { 454 output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; 455 output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; 456 } 457 458 source = sourceFromStacktrace(); 459 460 if ( source ) { 461 details.source = source; 462 output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>"; 463 } 464 465 output += "</table>"; 466 } 467 468 runLoggingCallbacks( "log", QUnit, details ); 469 470 config.current.assertions.push({ 471 result: !!result, 472 message: output 473 }); 474 }, 475 476 pushFailure: function( message, source, actual ) { 477 if ( !config.current ) { 478 throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 479 } 480 481 var output, 482 details = { 483 module: config.current.module, 484 name: config.current.testName, 485 result: false, 486 message: message 487 }; 488 489 message = escapeText( message ) || "error"; 490 message = "<span class='test-message'>" + message + "</span>"; 491 output = message; 492 493 output += "<table>"; 494 495 if ( actual ) { 496 output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>"; 497 } 498 499 if ( source ) { 500 details.source = source; 501 output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>"; 502 } 503 504 output += "</table>"; 505 506 runLoggingCallbacks( "log", QUnit, details ); 507 508 config.current.assertions.push({ 509 result: false, 510 message: output 511 }); 512 }, 513 514 url: function( params ) { 515 params = extend( extend( {}, QUnit.urlParams ), params ); 516 var key, 517 querystring = "?"; 518 519 for ( key in params ) { 520 if ( hasOwn.call( params, key ) ) { 521 querystring += encodeURIComponent( key ) + "=" + 522 encodeURIComponent( params[ key ] ) + "&"; 523 } 524 } 525 return window.location.protocol + "//" + window.location.host + 526 window.location.pathname + querystring.slice( 0, -1 ); 527 }, 528 529 extend: extend, 530 id: id, 531 addEvent: addEvent, 532 addClass: addClass, 533 hasClass: hasClass, 534 removeClass: removeClass 535 // load, equiv, jsDump, diff: Attached later 488 536 }); 489 537 490 if ( typeof document === "undefined" || document.readyState === "complete" ) { 538 /** 539 * @deprecated: Created for backwards compatibility with test runner that set the hook function 540 * into QUnit.{hook}, instead of invoking it and passing the hook function. 541 * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 542 * Doing this allows us to tell if the following methods have been overwritten on the actual 543 * QUnit object. 544 */ 545 extend( QUnit.constructor.prototype, { 546 547 // Logging callbacks; all receive a single argument with the listed properties 548 // run test/logs.html for any related changes 549 begin: registerLoggingCallback( "begin" ), 550 551 // done: { failed, passed, total, runtime } 552 done: registerLoggingCallback( "done" ), 553 554 // log: { result, actual, expected, message } 555 log: registerLoggingCallback( "log" ), 556 557 // testStart: { name } 558 testStart: registerLoggingCallback( "testStart" ), 559 560 // testDone: { name, failed, passed, total, runtime } 561 testDone: registerLoggingCallback( "testDone" ), 562 563 // moduleStart: { name } 564 moduleStart: registerLoggingCallback( "moduleStart" ), 565 566 // moduleDone: { name, failed, passed, total } 567 moduleDone: registerLoggingCallback( "moduleDone" ) 568 }); 569 570 if ( !defined.document || document.readyState === "complete" ) { 491 571 config.autorun = true; 492 572 } 493 573 494 addEvent(window, "load",function() {495 QUnit.begin();496 574 QUnit.load = function() { 575 runLoggingCallbacks( "begin", QUnit, {} ); 576 497 577 // Initialize the config, saving the execution queue 498 var oldconfig = extend({}, config); 578 var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, 579 urlConfigContainer, moduleFilter, userAgent, 580 numModules = 0, 581 moduleNames = [], 582 moduleFilterHtml = "", 583 urlConfigHtml = "", 584 oldconfig = extend( {}, config ); 585 499 586 QUnit.init(); 500 587 extend(config, oldconfig); … … 502 589 config.blocking = false; 503 590 504 var userAgent = id("qunit-userAgent"); 591 len = config.urlConfig.length; 592 593 for ( i = 0; i < len; i++ ) { 594 val = config.urlConfig[i]; 595 if ( typeof val === "string" ) { 596 val = { 597 id: val, 598 label: val 599 }; 600 } 601 config[ val.id ] = QUnit.urlParams[ val.id ]; 602 if ( !val.value || typeof val.value === "string" ) { 603 urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) + 604 "' name='" + escapeText( val.id ) + 605 "' type='checkbox'" + 606 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) + 607 ( config[ val.id ] ? " checked='checked'" : "" ) + 608 " title='" + escapeText( val.tooltip ) + 609 "'><label for='qunit-urlconfig-" + escapeText( val.id ) + 610 "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>"; 611 } else { 612 urlConfigHtml += "<label for='qunit-urlconfig-" + escapeText( val.id ) + 613 "' title='" + escapeText( val.tooltip ) + 614 "'>" + val.label + 615 ": </label><select id='qunit-urlconfig-" + escapeText( val.id ) + 616 "' name='" + escapeText( val.id ) + 617 "' title='" + escapeText( val.tooltip ) + 618 "'><option></option>"; 619 selection = false; 620 if ( QUnit.is( "array", val.value ) ) { 621 for ( j = 0; j < val.value.length; j++ ) { 622 urlConfigHtml += "<option value='" + escapeText( val.value[j] ) + "'" + 623 ( config[ val.id ] === val.value[j] ? 624 (selection = true) && " selected='selected'" : 625 "" ) + 626 ">" + escapeText( val.value[j] ) + "</option>"; 627 } 628 } else { 629 for ( j in val.value ) { 630 if ( hasOwn.call( val.value, j ) ) { 631 urlConfigHtml += "<option value='" + escapeText( j ) + "'" + 632 ( config[ val.id ] === j ? 633 (selection = true) && " selected='selected'" : 634 "" ) + 635 ">" + escapeText( val.value[j] ) + "</option>"; 636 } 637 } 638 } 639 if ( config[ val.id ] && !selection ) { 640 urlConfigHtml += "<option value='" + escapeText( config[ val.id ] ) + 641 "' selected='selected' disabled='disabled'>" + 642 escapeText( config[ val.id ] ) + 643 "</option>"; 644 } 645 urlConfigHtml += "</select>"; 646 } 647 } 648 for ( i in config.modules ) { 649 if ( config.modules.hasOwnProperty( i ) ) { 650 moduleNames.push(i); 651 } 652 } 653 numModules = moduleNames.length; 654 moduleNames.sort( function( a, b ) { 655 return a.localeCompare( b ); 656 }); 657 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + 658 ( config.module === undefined ? "selected='selected'" : "" ) + 659 ">< All Modules ></option>"; 660 661 662 for ( i = 0; i < numModules; i++) { 663 moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " + 664 ( config.module === moduleNames[i] ? "selected='selected'" : "" ) + 665 ">" + escapeText(moduleNames[i]) + "</option>"; 666 } 667 moduleFilterHtml += "</select>"; 668 669 // `userAgent` initialized at top of scope 670 userAgent = id( "qunit-userAgent" ); 505 671 if ( userAgent ) { 506 672 userAgent.innerHTML = navigator.userAgent; 507 673 } 508 509 var toolbar = id("qunit-testrunner-toolbar"); 674 675 // `banner` initialized at top of scope 676 banner = id( "qunit-header" ); 677 if ( banner ) { 678 banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> "; 679 } 680 681 // `toolbar` initialized at top of scope 682 toolbar = id( "qunit-testrunner-toolbar" ); 510 683 if ( toolbar ) { 511 toolbar.style.display = "none"; 512 513 var filter = document.createElement("input"); 684 // `filter` initialized at top of scope 685 filter = document.createElement( "input" ); 514 686 filter.type = "checkbox"; 515 687 filter.id = "qunit-filter-pass"; 516 filter.disabled = true; 688 517 689 addEvent( filter, "click", function() { 518 var li = document.getElementsByTagName("li"); 519 for ( var i = 0; i < li.length; i++ ) { 520 if ( li[i].className.indexOf("pass") > -1 ) { 521 li[i].style.display = filter.checked ? "none" : ""; 690 var tmp, 691 ol = id( "qunit-tests" ); 692 693 if ( filter.checked ) { 694 ol.className = ol.className + " hidepass"; 695 } else { 696 tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 697 ol.className = tmp.replace( / hidepass /, " " ); 698 } 699 if ( defined.sessionStorage ) { 700 if (filter.checked) { 701 sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 702 } else { 703 sessionStorage.removeItem( "qunit-filter-passed-tests" ); 522 704 } 523 705 } 524 706 }); 707 708 if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 709 filter.checked = true; 710 // `ol` initialized at top of scope 711 ol = id( "qunit-tests" ); 712 ol.className = ol.className + " hidepass"; 713 } 525 714 toolbar.appendChild( filter ); 526 715 527 var label = document.createElement("label"); 528 label.setAttribute("for", "qunit-filter-pass"); 716 // `label` initialized at top of scope 717 label = document.createElement( "label" ); 718 label.setAttribute( "for", "qunit-filter-pass" ); 719 label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); 529 720 label.innerHTML = "Hide passed tests"; 530 721 toolbar.appendChild( label ); 531 722 532 var missing = document.createElement("input"); 533 missing.type = "checkbox"; 534 missing.id = "qunit-filter-missing"; 535 missing.disabled = true; 536 addEvent( missing, "click", function() { 537 var li = document.getElementsByTagName("li"); 538 for ( var i = 0; i < li.length; i++ ) { 539 if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 540 li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 541 } 542 } 723 urlConfigContainer = document.createElement("span"); 724 urlConfigContainer.innerHTML = urlConfigHtml; 725 // For oldIE support: 726 // * Add handlers to the individual elements instead of the container 727 // * Use "click" instead of "change" for checkboxes 728 // * Fallback from event.target to event.srcElement 729 addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { 730 var params = {}, 731 target = event.target || event.srcElement; 732 params[ target.name ] = target.checked ? 733 target.defaultValue || true : 734 undefined; 735 window.location = QUnit.url( params ); 543 736 }); 544 toolbar.appendChild( missing ); 545 546 label = document.createElement("label"); 547 label.setAttribute("for", "qunit-filter-missing"); 548 label.innerHTML = "Hide missing tests (untested code is broken code)"; 549 toolbar.appendChild( label ); 550 } 551 552 var main = id('main') || id('qunit-fixture'); 737 addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { 738 var params = {}, 739 target = event.target || event.srcElement; 740 params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; 741 window.location = QUnit.url( params ); 742 }); 743 toolbar.appendChild( urlConfigContainer ); 744 745 if (numModules > 1) { 746 moduleFilter = document.createElement( "span" ); 747 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); 748 moduleFilter.innerHTML = moduleFilterHtml; 749 addEvent( moduleFilter.lastChild, "change", function() { 750 var selectBox = moduleFilter.getElementsByTagName("select")[0], 751 selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); 752 753 window.location = QUnit.url({ 754 module: ( selectedModule === "" ) ? undefined : selectedModule, 755 // Remove any existing filters 756 filter: undefined, 757 testNumber: undefined 758 }); 759 }); 760 toolbar.appendChild(moduleFilter); 761 } 762 } 763 764 // `main` initialized at top of scope 765 main = id( "qunit-fixture" ); 553 766 if ( main ) { 554 767 config.fixture = main.innerHTML; 555 768 } 556 769 557 if ( config.autostart) {770 if ( config.autostart ) { 558 771 QUnit.start(); 559 772 } 560 }); 773 }; 774 775 if ( defined.document ) { 776 addEvent( window, "load", QUnit.load ); 777 } 778 779 // `onErrorFnPrev` initialized at top of scope 780 // Preserve other handlers 781 onErrorFnPrev = window.onerror; 782 783 // Cover uncaught exceptions 784 // Returning true will suppress the default browser handler, 785 // returning false will let it run. 786 window.onerror = function ( error, filePath, linerNr ) { 787 var ret = false; 788 if ( onErrorFnPrev ) { 789 ret = onErrorFnPrev( error, filePath, linerNr ); 790 } 791 792 // Treat return value as window.onerror itself does, 793 // Only do our handling if not suppressed. 794 if ( ret !== true ) { 795 if ( QUnit.config.current ) { 796 if ( QUnit.config.current.ignoreGlobalErrors ) { 797 return true; 798 } 799 QUnit.pushFailure( error, filePath + ":" + linerNr ); 800 } else { 801 QUnit.test( "global failure", extend( function() { 802 QUnit.pushFailure( error, filePath + ":" + linerNr ); 803 }, { validTest: validTest } ) ); 804 } 805 return false; 806 } 807 808 return ret; 809 }; 561 810 562 811 function done() { 563 if ( config.doneTimer && window.clearTimeout ) {564 window.clearTimeout( config.doneTimer );565 config.doneTimer = null;566 }567 568 if ( config.queue.length ) {569 config.doneTimer = window.setTimeout(function(){570 if ( !config.queue.length ) {571 done();572 } else {573 synchronize( done );574 }575 }, 13);576 577 return;578 }579 580 812 config.autorun = true; 581 813 582 814 // Log the last module results 583 if ( config.currentModule ) { 584 QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 585 } 586 587 var banner = id("qunit-banner"), 588 tests = id("qunit-tests"), 589 html = ['Tests completed in ', 590 +new Date - config.started, ' milliseconds.<br/>', 591 '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join(''); 815 if ( config.previousModule ) { 816 runLoggingCallbacks( "moduleDone", QUnit, { 817 name: config.previousModule, 818 failed: config.moduleStats.bad, 819 passed: config.moduleStats.all - config.moduleStats.bad, 820 total: config.moduleStats.all 821 }); 822 } 823 delete config.previousModule; 824 825 var i, key, 826 banner = id( "qunit-banner" ), 827 tests = id( "qunit-tests" ), 828 runtime = +new Date() - config.started, 829 passed = config.stats.all - config.stats.bad, 830 html = [ 831 "Tests completed in ", 832 runtime, 833 " milliseconds.<br/>", 834 "<span class='passed'>", 835 passed, 836 "</span> assertions of <span class='total'>", 837 config.stats.all, 838 "</span> passed, <span class='failed'>", 839 config.stats.bad, 840 "</span> failed." 841 ].join( "" ); 592 842 593 843 if ( banner ) { 594 banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 595 } 596 597 if ( tests ) { 598 var result = id("qunit-testresult"); 599 600 if ( !result ) { 601 result = document.createElement("p"); 602 result.id = "qunit-testresult"; 603 result.className = "result"; 604 tests.parentNode.insertBefore( result, tests.nextSibling ); 605 } 606 607 result.innerHTML = html; 608 } 609 610 QUnit.done( config.stats.bad, config.stats.all ); 611 } 612 613 function validTest( name ) { 614 var i = config.filters.length, 615 run = false; 616 617 if ( !i ) { 844 banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 845 } 846 847 if ( tests ) { 848 id( "qunit-testresult" ).innerHTML = html; 849 } 850 851 if ( config.altertitle && defined.document && document.title ) { 852 // show ✖ for good, ✔ for bad suite result in title 853 // use escape sequences in case file gets loaded with non-utf-8-charset 854 document.title = [ 855 ( config.stats.bad ? "\u2716" : "\u2714" ), 856 document.title.replace( /^[\u2714\u2716] /i, "" ) 857 ].join( " " ); 858 } 859 860 // clear own sessionStorage items if all tests passed 861 if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 862 // `key` & `i` initialized at top of scope 863 for ( i = 0; i < sessionStorage.length; i++ ) { 864 key = sessionStorage.key( i++ ); 865 if ( key.indexOf( "qunit-test-" ) === 0 ) { 866 sessionStorage.removeItem( key ); 867 } 868 } 869 } 870 871 // scroll back to top to show results 872 if ( config.scrolltop && window.scrollTo ) { 873 window.scrollTo(0, 0); 874 } 875 876 runLoggingCallbacks( "done", QUnit, { 877 failed: config.stats.bad, 878 passed: passed, 879 total: config.stats.all, 880 runtime: runtime 881 }); 882 } 883 884 /** @return Boolean: true if this test should be ran */ 885 function validTest( test ) { 886 var include, 887 filter = config.filter && config.filter.toLowerCase(), 888 module = config.module && config.module.toLowerCase(), 889 fullName = ( test.module + ": " + test.testName ).toLowerCase(); 890 891 // Internally-generated tests are always valid 892 if ( test.callback && test.callback.validTest === validTest ) { 893 delete test.callback.validTest; 618 894 return true; 619 895 } 620 621 while ( i-- ) { 622 var filter = config.filters[i], 623 not = filter.charAt(0) == '!'; 624 625 if ( not ) { 626 filter = filter.slice(1); 627 } 628 629 if ( name.indexOf(filter) !== -1 ) { 630 return !not; 631 } 632 633 if ( not ) { 634 run = true; 635 } 636 } 637 638 return run; 639 } 640 641 function escapeHtml(s) { 642 s = s === null ? "" : s + ""; 643 return s.replace(/[\&"<>\\]/g, function(s) { 644 switch(s) { 645 case "&": return "&"; 646 case "\\": return "\\\\"; 647 case '"': return '\"'; 648 case "<": return "<"; 649 case ">": return ">"; 650 default: return s; 896 897 if ( config.testNumber.length > 0 ) { 898 if ( inArray( test.testNumber, config.testNumber ) < 0 ) { 899 return false; 900 } 901 } 902 903 if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 904 return false; 905 } 906 907 if ( !filter ) { 908 return true; 909 } 910 911 include = filter.charAt( 0 ) !== "!"; 912 if ( !include ) { 913 filter = filter.slice( 1 ); 914 } 915 916 // If the filter matches, we need to honour include 917 if ( fullName.indexOf( filter ) !== -1 ) { 918 return include; 919 } 920 921 // Otherwise, do the opposite 922 return !include; 923 } 924 925 // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 926 // Later Safari and IE10 are supposed to support error.stack as well 927 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 928 function extractStacktrace( e, offset ) { 929 offset = offset === undefined ? 3 : offset; 930 931 var stack, include, i; 932 933 if ( e.stacktrace ) { 934 // Opera 935 return e.stacktrace.split( "\n" )[ offset + 3 ]; 936 } else if ( e.stack ) { 937 // Firefox, Chrome 938 stack = e.stack.split( "\n" ); 939 if (/^error$/i.test( stack[0] ) ) { 940 stack.shift(); 941 } 942 if ( fileName ) { 943 include = []; 944 for ( i = offset; i < stack.length; i++ ) { 945 if ( stack[ i ].indexOf( fileName ) !== -1 ) { 946 break; 947 } 948 include.push( stack[ i ] ); 949 } 950 if ( include.length ) { 951 return include.join( "\n" ); 952 } 953 } 954 return stack[ offset ]; 955 } else if ( e.sourceURL ) { 956 // Safari, PhantomJS 957 // hopefully one day Safari provides actual stacktraces 958 // exclude useless self-reference for generated Error objects 959 if ( /qunit.js$/.test( e.sourceURL ) ) { 960 return; 961 } 962 // for actual exceptions, this is useful 963 return e.sourceURL + ":" + e.line; 964 } 965 } 966 function sourceFromStacktrace( offset ) { 967 try { 968 throw new Error(); 969 } catch ( e ) { 970 return extractStacktrace( e, offset ); 971 } 972 } 973 974 /** 975 * Escape text for attribute or text content. 976 */ 977 function escapeText( s ) { 978 if ( !s ) { 979 return ""; 980 } 981 s = s + ""; 982 // Both single quotes and double quotes (for attributes) 983 return s.replace( /['"<>&]/g, function( s ) { 984 switch( s ) { 985 case "'": 986 return "'"; 987 case "\"": 988 return """; 989 case "<": 990 return "<"; 991 case ">": 992 return ">"; 993 case "&": 994 return "&"; 651 995 } 652 996 }); 653 997 } 654 998 655 function push(result, actual, expected, message) { 656 message = escapeHtml(message) || (result ? "okay" : "failed"); 657 message = '<span class="test-message">' + message + "</span>"; 658 expected = escapeHtml(QUnit.jsDump.parse(expected)); 659 actual = escapeHtml(QUnit.jsDump.parse(actual)); 660 var output = message + ', expected: <span class="test-expected">' + expected + '</span>'; 661 if (actual != expected) { 662 output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual); 663 } 664 665 // can't use ok, as that would double-escape messages 666 QUnit.log(result, output); 667 config.assertions.push({ 668 result: !!result, 669 message: output 670 }); 671 } 672 673 function synchronize( callback ) { 999 function synchronize( callback, last ) { 674 1000 config.queue.push( callback ); 675 1001 676 1002 if ( config.autorun && !config.blocking ) { 677 process(); 678 } 679 } 680 681 function process() { 682 var start = (new Date()).getTime(); 1003 process( last ); 1004 } 1005 } 1006 1007 function process( last ) { 1008 function next() { 1009 process( last ); 1010 } 1011 var start = new Date().getTime(); 1012 config.depth = config.depth ? config.depth + 1 : 1; 683 1013 684 1014 while ( config.queue.length && !config.blocking ) { 685 if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {1015 if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 686 1016 config.queue.shift()(); 687 688 1017 } else { 689 setTimeout( process, 13 );1018 setTimeout( next, 13 ); 690 1019 break; 691 1020 } 1021 } 1022 config.depth--; 1023 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1024 done(); 692 1025 } 693 1026 } … … 695 1028 function saveGlobal() { 696 1029 config.pollution = []; 697 1030 698 1031 if ( config.noglobals ) { 699 1032 for ( var key in window ) { 700 config.pollution.push( key ); 701 } 702 } 703 } 704 705 function checkPollution( name ) { 706 var old = config.pollution; 1033 if ( hasOwn.call( window, key ) ) { 1034 // in Opera sometimes DOM element ids show up here, ignore them 1035 if ( /^qunit-test-output/.test( key ) ) { 1036 continue; 1037 } 1038 config.pollution.push( key ); 1039 } 1040 } 1041 } 1042 } 1043 1044 function checkPollution() { 1045 var newGlobals, 1046 deletedGlobals, 1047 old = config.pollution; 1048 707 1049 saveGlobal(); 708 709 var newGlobals = diff( old, config.pollution);1050 1051 newGlobals = diff( config.pollution, old ); 710 1052 if ( newGlobals.length > 0 ) { 711 ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 712 config.expected++; 713 } 714 715 var deletedGlobals = diff( config.pollution, old ); 1053 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1054 } 1055 1056 deletedGlobals = diff( old, config.pollution ); 716 1057 if ( deletedGlobals.length > 0 ) { 717 ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 718 config.expected++; 1058 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 719 1059 } 720 1060 } … … 722 1062 // returns a new Array with the elements that are in a but not in b 723 1063 function diff( a, b ) { 724 var result = a.slice(); 725 for ( var i = 0; i < result.length; i++ ) { 726 for ( var j = 0; j < b.length; j++ ) { 1064 var i, j, 1065 result = a.slice(); 1066 1067 for ( i = 0; i < result.length; i++ ) { 1068 for ( j = 0; j < b.length; j++ ) { 727 1069 if ( result[i] === b[j] ) { 728 result.splice( i, 1);1070 result.splice( i, 1 ); 729 1071 i--; 730 1072 break; … … 735 1077 } 736 1078 737 function fail(message, exception, callback) { 738 if ( typeof console !== "undefined" && console.error && console.warn ) { 739 console.error(message); 740 console.error(exception); 741 console.warn(callback.toString()); 742 743 } else if ( window.opera && opera.postError ) { 744 opera.postError(message, exception, callback.toString); 745 } 746 } 747 748 function extend(a, b) { 1079 function extend( a, b ) { 749 1080 for ( var prop in b ) { 750 a[prop] = b[prop]; 1081 if ( hasOwn.call( b, prop ) ) { 1082 // Avoid "Member not found" error in IE8 caused by messing with window.constructor 1083 if ( !( prop === "constructor" && a === window ) ) { 1084 if ( b[ prop ] === undefined ) { 1085 delete a[ prop ]; 1086 } else { 1087 a[ prop ] = b[ prop ]; 1088 } 1089 } 1090 } 751 1091 } 752 1092 … … 754 1094 } 755 1095 756 function addEvent(elem, type, fn) { 1096 /** 1097 * @param {HTMLElement} elem 1098 * @param {string} type 1099 * @param {Function} fn 1100 */ 1101 function addEvent( elem, type, fn ) { 757 1102 if ( elem.addEventListener ) { 1103 1104 // Standards-based browsers 758 1105 elem.addEventListener( type, fn, false ); 759 1106 } else if ( elem.attachEvent ) { 1107 1108 // support: IE <9 760 1109 elem.attachEvent( "on" + type, fn ); 761 1110 } else { 762 fn(); 763 } 764 } 765 766 function id(name) { 767 return !!(typeof document !== "undefined" && document && document.getElementById) && 768 document.getElementById( name ); 769 } 1111 1112 // Caller must ensure support for event listeners is present 1113 throw new Error( "addEvent() was called in a context without event listener support" ); 1114 } 1115 } 1116 1117 /** 1118 * @param {Array|NodeList} elems 1119 * @param {string} type 1120 * @param {Function} fn 1121 */ 1122 function addEvents( elems, type, fn ) { 1123 var i = elems.length; 1124 while ( i-- ) { 1125 addEvent( elems[i], type, fn ); 1126 } 1127 } 1128 1129 function hasClass( elem, name ) { 1130 return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; 1131 } 1132 1133 function addClass( elem, name ) { 1134 if ( !hasClass( elem, name ) ) { 1135 elem.className += (elem.className ? " " : "") + name; 1136 } 1137 } 1138 1139 function removeClass( elem, name ) { 1140 var set = " " + elem.className + " "; 1141 // Class name may appear multiple times 1142 while ( set.indexOf(" " + name + " ") > -1 ) { 1143 set = set.replace(" " + name + " " , " "); 1144 } 1145 // If possible, trim it for prettiness, but not necessarily 1146 elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); 1147 } 1148 1149 function id( name ) { 1150 return defined.document && document.getElementById && document.getElementById( name ); 1151 } 1152 1153 function registerLoggingCallback( key ) { 1154 return function( callback ) { 1155 config[key].push( callback ); 1156 }; 1157 } 1158 1159 // Supports deprecated method of completely overwriting logging callbacks 1160 function runLoggingCallbacks( key, scope, args ) { 1161 var i, callbacks; 1162 if ( QUnit.hasOwnProperty( key ) ) { 1163 QUnit[ key ].call(scope, args ); 1164 } else { 1165 callbacks = config[ key ]; 1166 for ( i = 0; i < callbacks.length; i++ ) { 1167 callbacks[ i ].call( scope, args ); 1168 } 1169 } 1170 } 1171 1172 // from jquery.js 1173 function inArray( elem, array ) { 1174 if ( array.indexOf ) { 1175 return array.indexOf( elem ); 1176 } 1177 1178 for ( var i = 0, length = array.length; i < length; i++ ) { 1179 if ( array[ i ] === elem ) { 1180 return i; 1181 } 1182 } 1183 1184 return -1; 1185 } 1186 1187 function Test( settings ) { 1188 extend( this, settings ); 1189 this.assertions = []; 1190 this.testNumber = ++Test.count; 1191 } 1192 1193 Test.count = 0; 1194 1195 Test.prototype = { 1196 init: function() { 1197 var a, b, li, 1198 tests = id( "qunit-tests" ); 1199 1200 if ( tests ) { 1201 b = document.createElement( "strong" ); 1202 b.innerHTML = this.nameHtml; 1203 1204 // `a` initialized at top of scope 1205 a = document.createElement( "a" ); 1206 a.innerHTML = "Rerun"; 1207 a.href = QUnit.url({ testNumber: this.testNumber }); 1208 1209 li = document.createElement( "li" ); 1210 li.appendChild( b ); 1211 li.appendChild( a ); 1212 li.className = "running"; 1213 li.id = this.id = "qunit-test-output" + testId++; 1214 1215 tests.appendChild( li ); 1216 } 1217 }, 1218 setup: function() { 1219 if ( 1220 // Emit moduleStart when we're switching from one module to another 1221 this.module !== config.previousModule || 1222 // They could be equal (both undefined) but if the previousModule property doesn't 1223 // yet exist it means this is the first test in a suite that isn't wrapped in a 1224 // module, in which case we'll just emit a moduleStart event for 'undefined'. 1225 // Without this, reporters can get testStart before moduleStart which is a problem. 1226 !hasOwn.call( config, "previousModule" ) 1227 ) { 1228 if ( hasOwn.call( config, "previousModule" ) ) { 1229 runLoggingCallbacks( "moduleDone", QUnit, { 1230 name: config.previousModule, 1231 failed: config.moduleStats.bad, 1232 passed: config.moduleStats.all - config.moduleStats.bad, 1233 total: config.moduleStats.all 1234 }); 1235 } 1236 config.previousModule = this.module; 1237 config.moduleStats = { all: 0, bad: 0 }; 1238 runLoggingCallbacks( "moduleStart", QUnit, { 1239 name: this.module 1240 }); 1241 } 1242 1243 config.current = this; 1244 1245 this.testEnvironment = extend({ 1246 setup: function() {}, 1247 teardown: function() {} 1248 }, this.moduleTestEnvironment ); 1249 1250 this.started = +new Date(); 1251 runLoggingCallbacks( "testStart", QUnit, { 1252 name: this.testName, 1253 module: this.module 1254 }); 1255 1256 /*jshint camelcase:false */ 1257 1258 1259 /** 1260 * Expose the current test environment. 1261 * 1262 * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. 1263 */ 1264 QUnit.current_testEnvironment = this.testEnvironment; 1265 1266 /*jshint camelcase:true */ 1267 1268 if ( !config.pollution ) { 1269 saveGlobal(); 1270 } 1271 if ( config.notrycatch ) { 1272 this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 1273 return; 1274 } 1275 try { 1276 this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); 1277 } catch( e ) { 1278 QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 1279 } 1280 }, 1281 run: function() { 1282 config.current = this; 1283 1284 var running = id( "qunit-testresult" ); 1285 1286 if ( running ) { 1287 running.innerHTML = "Running: <br/>" + this.nameHtml; 1288 } 1289 1290 if ( this.async ) { 1291 QUnit.stop(); 1292 } 1293 1294 this.callbackStarted = +new Date(); 1295 1296 if ( config.notrycatch ) { 1297 this.callback.call( this.testEnvironment, QUnit.assert ); 1298 this.callbackRuntime = +new Date() - this.callbackStarted; 1299 return; 1300 } 1301 1302 try { 1303 this.callback.call( this.testEnvironment, QUnit.assert ); 1304 this.callbackRuntime = +new Date() - this.callbackStarted; 1305 } catch( e ) { 1306 this.callbackRuntime = +new Date() - this.callbackStarted; 1307 1308 QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 1309 // else next test will carry the responsibility 1310 saveGlobal(); 1311 1312 // Restart the tests if they're blocking 1313 if ( config.blocking ) { 1314 QUnit.start(); 1315 } 1316 } 1317 }, 1318 teardown: function() { 1319 config.current = this; 1320 if ( config.notrycatch ) { 1321 if ( typeof this.callbackRuntime === "undefined" ) { 1322 this.callbackRuntime = +new Date() - this.callbackStarted; 1323 } 1324 this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 1325 return; 1326 } else { 1327 try { 1328 this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); 1329 } catch( e ) { 1330 QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 1331 } 1332 } 1333 checkPollution(); 1334 }, 1335 finish: function() { 1336 config.current = this; 1337 if ( config.requireExpects && this.expected === null ) { 1338 QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 1339 } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 1340 QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 1341 } else if ( this.expected === null && !this.assertions.length ) { 1342 QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 1343 } 1344 1345 var i, assertion, a, b, time, li, ol, 1346 test = this, 1347 good = 0, 1348 bad = 0, 1349 tests = id( "qunit-tests" ); 1350 1351 this.runtime = +new Date() - this.started; 1352 config.stats.all += this.assertions.length; 1353 config.moduleStats.all += this.assertions.length; 1354 1355 if ( tests ) { 1356 ol = document.createElement( "ol" ); 1357 ol.className = "qunit-assert-list"; 1358 1359 for ( i = 0; i < this.assertions.length; i++ ) { 1360 assertion = this.assertions[i]; 1361 1362 li = document.createElement( "li" ); 1363 li.className = assertion.result ? "pass" : "fail"; 1364 li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 1365 ol.appendChild( li ); 1366 1367 if ( assertion.result ) { 1368 good++; 1369 } else { 1370 bad++; 1371 config.stats.bad++; 1372 config.moduleStats.bad++; 1373 } 1374 } 1375 1376 // store result when possible 1377 if ( QUnit.config.reorder && defined.sessionStorage ) { 1378 if ( bad ) { 1379 sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 1380 } else { 1381 sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 1382 } 1383 } 1384 1385 if ( bad === 0 ) { 1386 addClass( ol, "qunit-collapsed" ); 1387 } 1388 1389 // `b` initialized at top of scope 1390 b = document.createElement( "strong" ); 1391 b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; 1392 1393 addEvent(b, "click", function() { 1394 var next = b.parentNode.lastChild, 1395 collapsed = hasClass( next, "qunit-collapsed" ); 1396 ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); 1397 }); 1398 1399 addEvent(b, "dblclick", function( e ) { 1400 var target = e && e.target ? e.target : window.event.srcElement; 1401 if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { 1402 target = target.parentNode; 1403 } 1404 if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 1405 window.location = QUnit.url({ testNumber: test.testNumber }); 1406 } 1407 }); 1408 1409 // `time` initialized at top of scope 1410 time = document.createElement( "span" ); 1411 time.className = "runtime"; 1412 time.innerHTML = this.runtime + " ms"; 1413 1414 // `li` initialized at top of scope 1415 li = id( this.id ); 1416 li.className = bad ? "fail" : "pass"; 1417 li.removeChild( li.firstChild ); 1418 a = li.firstChild; 1419 li.appendChild( b ); 1420 li.appendChild( a ); 1421 li.appendChild( time ); 1422 li.appendChild( ol ); 1423 1424 } else { 1425 for ( i = 0; i < this.assertions.length; i++ ) { 1426 if ( !this.assertions[i].result ) { 1427 bad++; 1428 config.stats.bad++; 1429 config.moduleStats.bad++; 1430 } 1431 } 1432 } 1433 1434 runLoggingCallbacks( "testDone", QUnit, { 1435 name: this.testName, 1436 module: this.module, 1437 failed: bad, 1438 passed: this.assertions.length - bad, 1439 total: this.assertions.length, 1440 runtime: this.runtime, 1441 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead 1442 duration: this.runtime 1443 }); 1444 1445 QUnit.reset(); 1446 1447 config.current = undefined; 1448 }, 1449 1450 queue: function() { 1451 var bad, 1452 test = this; 1453 1454 synchronize(function() { 1455 test.init(); 1456 }); 1457 function run() { 1458 // each of these can by async 1459 synchronize(function() { 1460 test.setup(); 1461 }); 1462 synchronize(function() { 1463 test.run(); 1464 }); 1465 synchronize(function() { 1466 test.teardown(); 1467 }); 1468 synchronize(function() { 1469 test.finish(); 1470 }); 1471 } 1472 1473 // `bad` initialized at top of scope 1474 // defer when previous test run passed, if storage is available 1475 bad = QUnit.config.reorder && defined.sessionStorage && 1476 +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 1477 1478 if ( bad ) { 1479 run(); 1480 } else { 1481 synchronize( run, true ); 1482 } 1483 } 1484 }; 1485 1486 // `assert` initialized at top of scope 1487 // Assert helpers 1488 // All of these must either call QUnit.push() or manually do: 1489 // - runLoggingCallbacks( "log", .. ); 1490 // - config.current.assertions.push({ .. }); 1491 assert = QUnit.assert = { 1492 /** 1493 * Asserts rough true-ish result. 1494 * @name ok 1495 * @function 1496 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 1497 */ 1498 ok: function( result, msg ) { 1499 if ( !config.current ) { 1500 throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 1501 } 1502 result = !!result; 1503 msg = msg || ( result ? "okay" : "failed" ); 1504 1505 var source, 1506 details = { 1507 module: config.current.module, 1508 name: config.current.testName, 1509 result: result, 1510 message: msg 1511 }; 1512 1513 msg = "<span class='test-message'>" + escapeText( msg ) + "</span>"; 1514 1515 if ( !result ) { 1516 source = sourceFromStacktrace( 2 ); 1517 if ( source ) { 1518 details.source = source; 1519 msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + 1520 escapeText( source ) + 1521 "</pre></td></tr></table>"; 1522 } 1523 } 1524 runLoggingCallbacks( "log", QUnit, details ); 1525 config.current.assertions.push({ 1526 result: result, 1527 message: msg 1528 }); 1529 }, 1530 1531 /** 1532 * Assert that the first two arguments are equal, with an optional message. 1533 * Prints out both actual and expected values. 1534 * @name equal 1535 * @function 1536 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 1537 */ 1538 equal: function( actual, expected, message ) { 1539 /*jshint eqeqeq:false */ 1540 QUnit.push( expected == actual, actual, expected, message ); 1541 }, 1542 1543 /** 1544 * @name notEqual 1545 * @function 1546 */ 1547 notEqual: function( actual, expected, message ) { 1548 /*jshint eqeqeq:false */ 1549 QUnit.push( expected != actual, actual, expected, message ); 1550 }, 1551 1552 /** 1553 * @name propEqual 1554 * @function 1555 */ 1556 propEqual: function( actual, expected, message ) { 1557 actual = objectValues(actual); 1558 expected = objectValues(expected); 1559 QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 1560 }, 1561 1562 /** 1563 * @name notPropEqual 1564 * @function 1565 */ 1566 notPropEqual: function( actual, expected, message ) { 1567 actual = objectValues(actual); 1568 expected = objectValues(expected); 1569 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 1570 }, 1571 1572 /** 1573 * @name deepEqual 1574 * @function 1575 */ 1576 deepEqual: function( actual, expected, message ) { 1577 QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 1578 }, 1579 1580 /** 1581 * @name notDeepEqual 1582 * @function 1583 */ 1584 notDeepEqual: function( actual, expected, message ) { 1585 QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 1586 }, 1587 1588 /** 1589 * @name strictEqual 1590 * @function 1591 */ 1592 strictEqual: function( actual, expected, message ) { 1593 QUnit.push( expected === actual, actual, expected, message ); 1594 }, 1595 1596 /** 1597 * @name notStrictEqual 1598 * @function 1599 */ 1600 notStrictEqual: function( actual, expected, message ) { 1601 QUnit.push( expected !== actual, actual, expected, message ); 1602 }, 1603 1604 "throws": function( block, expected, message ) { 1605 var actual, 1606 expectedOutput = expected, 1607 ok = false; 1608 1609 // 'expected' is optional 1610 if ( !message && typeof expected === "string" ) { 1611 message = expected; 1612 expected = null; 1613 } 1614 1615 config.current.ignoreGlobalErrors = true; 1616 try { 1617 block.call( config.current.testEnvironment ); 1618 } catch (e) { 1619 actual = e; 1620 } 1621 config.current.ignoreGlobalErrors = false; 1622 1623 if ( actual ) { 1624 1625 // we don't want to validate thrown error 1626 if ( !expected ) { 1627 ok = true; 1628 expectedOutput = null; 1629 1630 // expected is an Error object 1631 } else if ( expected instanceof Error ) { 1632 ok = actual instanceof Error && 1633 actual.name === expected.name && 1634 actual.message === expected.message; 1635 1636 // expected is a regexp 1637 } else if ( QUnit.objectType( expected ) === "regexp" ) { 1638 ok = expected.test( errorString( actual ) ); 1639 1640 // expected is a string 1641 } else if ( QUnit.objectType( expected ) === "string" ) { 1642 ok = expected === errorString( actual ); 1643 1644 // expected is a constructor 1645 } else if ( actual instanceof expected ) { 1646 ok = true; 1647 1648 // expected is a validation function which returns true is validation passed 1649 } else if ( expected.call( {}, actual ) === true ) { 1650 expectedOutput = null; 1651 ok = true; 1652 } 1653 1654 QUnit.push( ok, actual, expectedOutput, message ); 1655 } else { 1656 QUnit.pushFailure( message, null, "No exception was thrown." ); 1657 } 1658 } 1659 }; 1660 1661 /** 1662 * @deprecated since 1.8.0 1663 * Kept assertion helpers in root for backwards compatibility. 1664 */ 1665 extend( QUnit.constructor.prototype, assert ); 1666 1667 /** 1668 * @deprecated since 1.9.0 1669 * Kept to avoid TypeErrors for undefined methods. 1670 */ 1671 QUnit.constructor.prototype.raises = function() { 1672 QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); 1673 }; 1674 1675 /** 1676 * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 1677 * Kept to avoid TypeErrors for undefined methods. 1678 */ 1679 QUnit.constructor.prototype.equals = function() { 1680 QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 1681 }; 1682 QUnit.constructor.prototype.same = function() { 1683 QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 1684 }; 770 1685 771 1686 // Test for equality any JavaScript type. 772 // Discussions and reference: http://philrathe.com/articles/equiv773 // Test suites: http://philrathe.com/tests/equiv774 1687 // Author: Philippe Rathé <prathe@gmail.com> 775 QUnit.equiv = function () { 776 777 var innerEquiv; // the real equiv function 778 var callers = []; // stack to decide between skip/abort functions 779 var parents = []; // stack to avoiding loops from circular referencing 780 781 // Call the o related callback with the given arguments. 782 function bindCallbacks(o, callbacks, args) { 783 var prop = QUnit.objectType(o); 784 if (prop) { 785 if (QUnit.objectType(callbacks[prop]) === "function") { 786 return callbacks[prop].apply(callbacks, args); 787 } else { 788 return callbacks[prop]; // or undefined 789 } 790 } 791 } 792 793 var callbacks = function () { 794 795 // for string, boolean, number and null 796 function useStrictEquality(b, a) { 797 if (b instanceof a.constructor || a instanceof b.constructor) { 798 // to catch short annotaion VS 'new' annotation of a declaration 799 // e.g. var i = 1; 800 // var j = new Number(1); 801 return a == b; 802 } else { 803 return a === b; 804 } 805 } 806 807 return { 808 "string": useStrictEquality, 809 "boolean": useStrictEquality, 810 "number": useStrictEquality, 811 "null": useStrictEquality, 812 "undefined": useStrictEquality, 813 814 "nan": function (b) { 815 return isNaN(b); 816 }, 817 818 "date": function (b, a) { 819 return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 820 }, 821 822 "regexp": function (b, a) { 823 return QUnit.objectType(b) === "regexp" && 824 a.source === b.source && // the regex itself 825 a.global === b.global && // and its modifers (gmi) ... 826 a.ignoreCase === b.ignoreCase && 827 a.multiline === b.multiline; 828 }, 829 830 // - skip when the property is a method of an instance (OOP) 831 // - abort otherwise, 832 // initial === would have catch identical references anyway 833 "function": function () { 834 var caller = callers[callers.length - 1]; 835 return caller !== Object && 836 typeof caller !== "undefined"; 837 }, 838 839 "array": function (b, a) { 840 var i, j, loop; 841 var len; 842 843 // b could be an object literal here 844 if ( ! (QUnit.objectType(b) === "array")) { 845 return false; 846 } 847 848 len = a.length; 849 if (len !== b.length) { // safe and faster 850 return false; 851 } 852 853 //track reference to avoid circular references 854 parents.push(a); 855 for (i = 0; i < len; i++) { 856 loop = false; 857 for(j=0;j<parents.length;j++){ 858 if(parents[j] === a[i]){ 859 loop = true;//dont rewalk array 860 } 861 } 862 if (!loop && ! innerEquiv(a[i], b[i])) { 863 parents.pop(); 864 return false; 865 } 866 } 867 parents.pop(); 868 return true; 869 }, 870 871 "object": function (b, a) { 872 var i, j, loop; 873 var eq = true; // unless we can proove it 874 var aProperties = [], bProperties = []; // collection of strings 875 876 // comparing constructors is more strict than using instanceof 877 if ( a.constructor !== b.constructor) { 878 return false; 879 } 880 881 // stack constructor before traversing properties 882 callers.push(a.constructor); 883 //track reference to avoid circular references 884 parents.push(a); 885 886 for (i in a) { // be strict: don't ensures hasOwnProperty and go deep 887 loop = false; 888 for(j=0;j<parents.length;j++){ 889 if(parents[j] === a[i]) 890 loop = true; //don't go down the same path twice 891 } 892 aProperties.push(i); // collect a's properties 893 894 if (!loop && ! innerEquiv(a[i], b[i])) { 895 eq = false; 896 break; 897 } 898 } 899 900 callers.pop(); // unstack, we are done 901 parents.pop(); 902 903 for (i in b) { 904 bProperties.push(i); // collect b's properties 905 } 906 907 // Ensures identical properties name 908 return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 909 } 910 }; 911 }(); 912 913 innerEquiv = function () { // can take multiple arguments 914 var args = Array.prototype.slice.apply(arguments); 915 if (args.length < 2) { 916 return true; // end transition 917 } 918 919 return (function (a, b) { 920 if (a === b) { 921 return true; // catch the most you can 922 } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) { 923 return false; // don't lose time with error prone cases 924 } else { 925 return bindCallbacks(a, callbacks, [b, a]); 926 } 927 928 // apply transition with (1..n) arguments 929 })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); 930 }; 931 932 return innerEquiv; 933 934 }(); 1688 QUnit.equiv = (function() { 1689 1690 // Call the o related callback with the given arguments. 1691 function bindCallbacks( o, callbacks, args ) { 1692 var prop = QUnit.objectType( o ); 1693 if ( prop ) { 1694 if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1695 return callbacks[ prop ].apply( callbacks, args ); 1696 } else { 1697 return callbacks[ prop ]; // or undefined 1698 } 1699 } 1700 } 1701 1702 // the real equiv function 1703 var innerEquiv, 1704 // stack to decide between skip/abort functions 1705 callers = [], 1706 // stack to avoiding loops from circular referencing 1707 parents = [], 1708 parentsB = [], 1709 1710 getProto = Object.getPrototypeOf || function ( obj ) { 1711 /*jshint camelcase:false */ 1712 return obj.__proto__; 1713 }, 1714 callbacks = (function () { 1715 1716 // for string, boolean, number and null 1717 function useStrictEquality( b, a ) { 1718 /*jshint eqeqeq:false */ 1719 if ( b instanceof a.constructor || a instanceof b.constructor ) { 1720 // to catch short annotation VS 'new' annotation of a 1721 // declaration 1722 // e.g. var i = 1; 1723 // var j = new Number(1); 1724 return a == b; 1725 } else { 1726 return a === b; 1727 } 1728 } 1729 1730 return { 1731 "string": useStrictEquality, 1732 "boolean": useStrictEquality, 1733 "number": useStrictEquality, 1734 "null": useStrictEquality, 1735 "undefined": useStrictEquality, 1736 1737 "nan": function( b ) { 1738 return isNaN( b ); 1739 }, 1740 1741 "date": function( b, a ) { 1742 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1743 }, 1744 1745 "regexp": function( b, a ) { 1746 return QUnit.objectType( b ) === "regexp" && 1747 // the regex itself 1748 a.source === b.source && 1749 // and its modifiers 1750 a.global === b.global && 1751 // (gmi) ... 1752 a.ignoreCase === b.ignoreCase && 1753 a.multiline === b.multiline && 1754 a.sticky === b.sticky; 1755 }, 1756 1757 // - skip when the property is a method of an instance (OOP) 1758 // - abort otherwise, 1759 // initial === would have catch identical references anyway 1760 "function": function() { 1761 var caller = callers[callers.length - 1]; 1762 return caller !== Object && typeof caller !== "undefined"; 1763 }, 1764 1765 "array": function( b, a ) { 1766 var i, j, len, loop, aCircular, bCircular; 1767 1768 // b could be an object literal here 1769 if ( QUnit.objectType( b ) !== "array" ) { 1770 return false; 1771 } 1772 1773 len = a.length; 1774 if ( len !== b.length ) { 1775 // safe and faster 1776 return false; 1777 } 1778 1779 // track reference to avoid circular references 1780 parents.push( a ); 1781 parentsB.push( b ); 1782 for ( i = 0; i < len; i++ ) { 1783 loop = false; 1784 for ( j = 0; j < parents.length; j++ ) { 1785 aCircular = parents[j] === a[i]; 1786 bCircular = parentsB[j] === b[i]; 1787 if ( aCircular || bCircular ) { 1788 if ( a[i] === b[i] || aCircular && bCircular ) { 1789 loop = true; 1790 } else { 1791 parents.pop(); 1792 parentsB.pop(); 1793 return false; 1794 } 1795 } 1796 } 1797 if ( !loop && !innerEquiv(a[i], b[i]) ) { 1798 parents.pop(); 1799 parentsB.pop(); 1800 return false; 1801 } 1802 } 1803 parents.pop(); 1804 parentsB.pop(); 1805 return true; 1806 }, 1807 1808 "object": function( b, a ) { 1809 /*jshint forin:false */ 1810 var i, j, loop, aCircular, bCircular, 1811 // Default to true 1812 eq = true, 1813 aProperties = [], 1814 bProperties = []; 1815 1816 // comparing constructors is more strict than using 1817 // instanceof 1818 if ( a.constructor !== b.constructor ) { 1819 // Allow objects with no prototype to be equivalent to 1820 // objects with Object as their constructor. 1821 if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1822 ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1823 return false; 1824 } 1825 } 1826 1827 // stack constructor before traversing properties 1828 callers.push( a.constructor ); 1829 1830 // track reference to avoid circular references 1831 parents.push( a ); 1832 parentsB.push( b ); 1833 1834 // be strict: don't ensure hasOwnProperty and go deep 1835 for ( i in a ) { 1836 loop = false; 1837 for ( j = 0; j < parents.length; j++ ) { 1838 aCircular = parents[j] === a[i]; 1839 bCircular = parentsB[j] === b[i]; 1840 if ( aCircular || bCircular ) { 1841 if ( a[i] === b[i] || aCircular && bCircular ) { 1842 loop = true; 1843 } else { 1844 eq = false; 1845 break; 1846 } 1847 } 1848 } 1849 aProperties.push(i); 1850 if ( !loop && !innerEquiv(a[i], b[i]) ) { 1851 eq = false; 1852 break; 1853 } 1854 } 1855 1856 parents.pop(); 1857 parentsB.pop(); 1858 callers.pop(); // unstack, we are done 1859 1860 for ( i in b ) { 1861 bProperties.push( i ); // collect b's properties 1862 } 1863 1864 // Ensures identical properties name 1865 return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1866 } 1867 }; 1868 }()); 1869 1870 innerEquiv = function() { // can take multiple arguments 1871 var args = [].slice.apply( arguments ); 1872 if ( args.length < 2 ) { 1873 return true; // end transition 1874 } 1875 1876 return (function( a, b ) { 1877 if ( a === b ) { 1878 return true; // catch the most you can 1879 } else if ( a === null || b === null || typeof a === "undefined" || 1880 typeof b === "undefined" || 1881 QUnit.objectType(a) !== QUnit.objectType(b) ) { 1882 return false; // don't lose time with error prone cases 1883 } else { 1884 return bindCallbacks(a, callbacks, [ b, a ]); 1885 } 1886 1887 // apply transition with (1..n) arguments 1888 }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); 1889 }; 1890 1891 return innerEquiv; 1892 }()); 935 1893 936 1894 /** 937 * jsDump 938 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com939 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)940 * Date: 5/15/20081895 * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1896 * http://flesler.blogspot.com Licensed under BSD 1897 * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1898 * 941 1899 * @projectDescription Advanced and extensible data dumping for Javascript. 942 1900 * @version 1.0.0 … … 946 1904 QUnit.jsDump = (function() { 947 1905 function quote( str ) { 948 return '"' + str.toString().replace(/"/g, '\\"') + '"';949 } ;1906 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; 1907 } 950 1908 function literal( o ) { 951 return o + '';952 } ;1909 return o + ""; 1910 } 953 1911 function join( pre, arr, post ) { 954 1912 var s = jsDump.separator(), 955 1913 base = jsDump.indent(), 956 1914 inner = jsDump.indent(1); 957 if ( arr.join ) 958 arr = arr.join( ',' + s + inner ); 959 if ( !arr ) 1915 if ( arr.join ) { 1916 arr = arr.join( "," + s + inner ); 1917 } 1918 if ( !arr ) { 960 1919 return pre + post; 1920 } 961 1921 return [ pre, inner + arr, base + post ].join(s); 962 } ;963 function array( arr ) {964 var i = arr.length, ret = Array(i);1922 } 1923 function array( arr, stack ) { 1924 var i = arr.length, ret = new Array(i); 965 1925 this.up(); 966 while ( i-- ) 967 ret[i] = this.parse( arr[i] ); 1926 while ( i-- ) { 1927 ret[i] = this.parse( arr[i] , undefined , stack); 1928 } 968 1929 this.down(); 969 return join( '[', ret, ']' ); 970 }; 971 972 var reName = /^function (\w+)/; 973 974 var jsDump = { 975 parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance 976 var parser = this.parsers[ type || this.typeOf(obj) ]; 977 type = typeof parser; 978 979 return type == 'function' ? parser.call( this, obj ) : 980 type == 'string' ? parser : 981 this.parsers.error; 982 }, 983 typeOf:function( obj ) { 984 var type; 985 if ( obj === null ) { 986 type = "null"; 987 } else if (typeof obj === "undefined") { 988 type = "undefined"; 989 } else if (QUnit.is("RegExp", obj)) { 990 type = "regexp"; 991 } else if (QUnit.is("Date", obj)) { 992 type = "date"; 993 } else if (QUnit.is("Function", obj)) { 994 type = "function"; 995 } else if (obj.setInterval && obj.document && !obj.nodeType) { 996 type = "window"; 997 } else if (obj.nodeType === 9) { 998 type = "document"; 999 } else if (obj.nodeType) { 1000 type = "node"; 1001 } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { 1002 type = "array"; 1003 } else { 1004 type = typeof obj; 1005 } 1006 return type; 1007 }, 1008 separator:function() { 1009 return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; 1010 }, 1011 indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1012 if ( !this.multiline ) 1013 return ''; 1014 var chr = this.indentChar; 1015 if ( this.HTML ) 1016 chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1017 return Array( this._depth_ + (extra||0) ).join(chr); 1018 }, 1019 up:function( a ) { 1020 this._depth_ += a || 1; 1021 }, 1022 down:function( a ) { 1023 this._depth_ -= a || 1; 1024 }, 1025 setParser:function( name, parser ) { 1026 this.parsers[name] = parser; 1027 }, 1028 // The next 3 are exposed so you can use them 1029 quote:quote, 1030 literal:literal, 1031 join:join, 1032 // 1033 _depth_: 1, 1034 // This is the list of parsers, to modify them, use jsDump.setParser 1035 parsers:{ 1036 window: '[Window]', 1037 document: '[Document]', 1038 error:'[ERROR]', //when no parser is found, shouldn't happen 1039 unknown: '[Unknown]', 1040 'null':'null', 1041 undefined:'undefined', 1042 'function':function( fn ) { 1043 var ret = 'function', 1044 name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1045 if ( name ) 1046 ret += ' ' + name; 1047 ret += '('; 1048 1049 ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 1050 return join( ret, this.parse(fn,'functionCode'), '}' ); 1930 return join( "[", ret, "]" ); 1931 } 1932 1933 var reName = /^function (\w+)/, 1934 jsDump = { 1935 // type is used mostly internally, you can fix a (custom)type in advance 1936 parse: function( obj, type, stack ) { 1937 stack = stack || [ ]; 1938 var inStack, res, 1939 parser = this.parsers[ type || this.typeOf(obj) ]; 1940 1941 type = typeof parser; 1942 inStack = inArray( obj, stack ); 1943 1944 if ( inStack !== -1 ) { 1945 return "recursion(" + (inStack - stack.length) + ")"; 1946 } 1947 if ( type === "function" ) { 1948 stack.push( obj ); 1949 res = parser.call( this, obj, stack ); 1950 stack.pop(); 1951 return res; 1952 } 1953 return ( type === "string" ) ? parser : this.parsers.error; 1051 1954 }, 1052 array: array, 1053 nodelist: array, 1054 arguments: array, 1055 object:function( map ) { 1056 var ret = [ ]; 1057 this.up(); 1058 for ( var key in map ) 1059 ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 1060 this.down(); 1061 return join( '{', ret, '}' ); 1955 typeOf: function( obj ) { 1956 var type; 1957 if ( obj === null ) { 1958 type = "null"; 1959 } else if ( typeof obj === "undefined" ) { 1960 type = "undefined"; 1961 } else if ( QUnit.is( "regexp", obj) ) { 1962 type = "regexp"; 1963 } else if ( QUnit.is( "date", obj) ) { 1964 type = "date"; 1965 } else if ( QUnit.is( "function", obj) ) { 1966 type = "function"; 1967 } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1968 type = "window"; 1969 } else if ( obj.nodeType === 9 ) { 1970 type = "document"; 1971 } else if ( obj.nodeType ) { 1972 type = "node"; 1973 } else if ( 1974 // native arrays 1975 toString.call( obj ) === "[object Array]" || 1976 // NodeList objects 1977 ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1978 ) { 1979 type = "array"; 1980 } else if ( obj.constructor === Error.prototype.constructor ) { 1981 type = "error"; 1982 } else { 1983 type = typeof obj; 1984 } 1985 return type; 1062 1986 }, 1063 node:function( node ) { 1064 var open = this.HTML ? '<' : '<', 1065 close = this.HTML ? '>' : '>'; 1066 1067 var tag = node.nodeName.toLowerCase(), 1068 ret = open + tag; 1069 1070 for ( var a in this.DOMAttrs ) { 1071 var val = node[this.DOMAttrs[a]]; 1072 if ( val ) 1073 ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 1074 } 1075 return ret + close + open + '/' + tag + close; 1987 separator: function() { 1988 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; 1076 1989 }, 1077 functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1078 var l = fn.length; 1079 if ( !l ) return ''; 1080 1081 var args = Array(l); 1082 while ( l-- ) 1083 args[l] = String.fromCharCode(97+l);//97 is 'a' 1084 return ' ' + args.join(', ') + ' '; 1990 // extra can be a number, shortcut for increasing-calling-decreasing 1991 indent: function( extra ) { 1992 if ( !this.multiline ) { 1993 return ""; 1994 } 1995 var chr = this.indentChar; 1996 if ( this.HTML ) { 1997 chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1998 } 1999 return new Array( this.depth + ( extra || 0 ) ).join(chr); 1085 2000 }, 1086 key:quote, //object calls it internally, the key part of an item in a map 1087 functionCode:'[code]', //function calls it internally, it's the content of the function 1088 attribute:quote, //node calls it internally, it's an html attribute value 1089 string:quote, 1090 date:quote, 1091 regexp:literal, //regex 1092 number:literal, 1093 'boolean':literal 1094 }, 1095 DOMAttrs:{//attributes to dump from nodes, name=>realName 1096 id:'id', 1097 name:'name', 1098 'class':'className' 1099 }, 1100 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1101 indentChar:' ',//indentation unit 1102 multiline:false //if true, items in a collection, are separated by a \n, else just a space. 1103 }; 2001 up: function( a ) { 2002 this.depth += a || 1; 2003 }, 2004 down: function( a ) { 2005 this.depth -= a || 1; 2006 }, 2007 setParser: function( name, parser ) { 2008 this.parsers[name] = parser; 2009 }, 2010 // The next 3 are exposed so you can use them 2011 quote: quote, 2012 literal: literal, 2013 join: join, 2014 // 2015 depth: 1, 2016 // This is the list of parsers, to modify them, use jsDump.setParser 2017 parsers: { 2018 window: "[Window]", 2019 document: "[Document]", 2020 error: function(error) { 2021 return "Error(\"" + error.message + "\")"; 2022 }, 2023 unknown: "[Unknown]", 2024 "null": "null", 2025 "undefined": "undefined", 2026 "function": function( fn ) { 2027 var ret = "function", 2028 // functions never have name in IE 2029 name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; 2030 2031 if ( name ) { 2032 ret += " " + name; 2033 } 2034 ret += "( "; 2035 2036 ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 2037 return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 2038 }, 2039 array: array, 2040 nodelist: array, 2041 "arguments": array, 2042 object: function( map, stack ) { 2043 /*jshint forin:false */ 2044 var ret = [ ], keys, key, val, i; 2045 QUnit.jsDump.up(); 2046 keys = []; 2047 for ( key in map ) { 2048 keys.push( key ); 2049 } 2050 keys.sort(); 2051 for ( i = 0; i < keys.length; i++ ) { 2052 key = keys[ i ]; 2053 val = map[ key ]; 2054 ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 2055 } 2056 QUnit.jsDump.down(); 2057 return join( "{", ret, "}" ); 2058 }, 2059 node: function( node ) { 2060 var len, i, val, 2061 open = QUnit.jsDump.HTML ? "<" : "<", 2062 close = QUnit.jsDump.HTML ? ">" : ">", 2063 tag = node.nodeName.toLowerCase(), 2064 ret = open + tag, 2065 attrs = node.attributes; 2066 2067 if ( attrs ) { 2068 for ( i = 0, len = attrs.length; i < len; i++ ) { 2069 val = attrs[i].nodeValue; 2070 // IE6 includes all attributes in .attributes, even ones not explicitly set. 2071 // Those have values like undefined, null, 0, false, "" or "inherit". 2072 if ( val && val !== "inherit" ) { 2073 ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); 2074 } 2075 } 2076 } 2077 ret += close; 2078 2079 // Show content of TextNode or CDATASection 2080 if ( node.nodeType === 3 || node.nodeType === 4 ) { 2081 ret += node.nodeValue; 2082 } 2083 2084 return ret + open + "/" + tag + close; 2085 }, 2086 // function calls it internally, it's the arguments part of the function 2087 functionArgs: function( fn ) { 2088 var args, 2089 l = fn.length; 2090 2091 if ( !l ) { 2092 return ""; 2093 } 2094 2095 args = new Array(l); 2096 while ( l-- ) { 2097 // 97 is 'a' 2098 args[l] = String.fromCharCode(97+l); 2099 } 2100 return " " + args.join( ", " ) + " "; 2101 }, 2102 // object calls it internally, the key part of an item in a map 2103 key: quote, 2104 // function calls it internally, it's the content of the function 2105 functionCode: "[code]", 2106 // node calls it internally, it's an html attribute value 2107 attribute: quote, 2108 string: quote, 2109 date: quote, 2110 regexp: literal, 2111 number: literal, 2112 "boolean": literal 2113 }, 2114 // if true, entities are escaped ( <, >, \t, space and \n ) 2115 HTML: false, 2116 // indentation unit 2117 indentChar: " ", 2118 // if true, items in a collection, are separated by a \n, else just a space. 2119 multiline: true 2120 }; 1104 2121 1105 2122 return jsDump; 1106 })(); 1107 1108 // from Sizzle.js 1109 function getText( elems ) { 1110 var ret = "", elem; 1111 1112 for ( var i = 0; elems[i]; i++ ) { 1113 elem = elems[i]; 1114 1115 // Get the text from text nodes and CDATA nodes 1116 if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1117 ret += elem.nodeValue; 1118 1119 // Traverse everything else, except comment nodes 1120 } else if ( elem.nodeType !== 8 ) { 1121 ret += getText( elem.childNodes ); 1122 } 1123 } 1124 1125 return ret; 1126 }; 2123 }()); 1127 2124 1128 2125 /* … … 1135 2132 * More Info: 1136 2133 * http://ejohn.org/projects/javascript-diff-algorithm/ 1137 * 2134 * 1138 2135 * Usage: QUnit.diff(expected, actual) 1139 * 1140 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"2136 * 2137 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" 1141 2138 */ 1142 2139 QUnit.diff = (function() { 1143 function diff(o, n){ 1144 var ns = new Object(); 1145 var os = new Object(); 1146 1147 for (var i = 0; i < n.length; i++) { 1148 if (ns[n[i]] == null) 1149 ns[n[i]] = { 1150 rows: new Array(), 2140 /*jshint eqeqeq:false, eqnull:true */ 2141 function diff( o, n ) { 2142 var i, 2143 ns = {}, 2144 os = {}; 2145 2146 for ( i = 0; i < n.length; i++ ) { 2147 if ( !hasOwn.call( ns, n[i] ) ) { 2148 ns[ n[i] ] = { 2149 rows: [], 1151 2150 o: null 1152 2151 }; 1153 ns[n[i]].rows.push(i); 1154 } 1155 1156 for (var i = 0; i < o.length; i++) { 1157 if (os[o[i]] == null) 1158 os[o[i]] = { 1159 rows: new Array(), 2152 } 2153 ns[ n[i] ].rows.push( i ); 2154 } 2155 2156 for ( i = 0; i < o.length; i++ ) { 2157 if ( !hasOwn.call( os, o[i] ) ) { 2158 os[ o[i] ] = { 2159 rows: [], 1160 2160 n: null 1161 2161 }; 1162 os[o[i]].rows.push(i); 1163 } 1164 1165 for (var i in ns) { 1166 if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1167 n[ns[i].rows[0]] = { 1168 text: n[ns[i].rows[0]], 1169 row: os[i].rows[0] 1170 }; 1171 o[os[i].rows[0]] = { 1172 text: o[os[i].rows[0]], 1173 row: ns[i].rows[0] 1174 }; 1175 } 1176 } 1177 1178 for (var i = 0; i < n.length - 1; i++) { 1179 if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1180 n[i + 1] == o[n[i].row + 1]) { 1181 n[i + 1] = { 1182 text: n[i + 1], 2162 } 2163 os[ o[i] ].rows.push( i ); 2164 } 2165 2166 for ( i in ns ) { 2167 if ( hasOwn.call( ns, i ) ) { 2168 if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { 2169 n[ ns[i].rows[0] ] = { 2170 text: n[ ns[i].rows[0] ], 2171 row: os[i].rows[0] 2172 }; 2173 o[ os[i].rows[0] ] = { 2174 text: o[ os[i].rows[0] ], 2175 row: ns[i].rows[0] 2176 }; 2177 } 2178 } 2179 } 2180 2181 for ( i = 0; i < n.length - 1; i++ ) { 2182 if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 2183 n[ i + 1 ] == o[ n[i].row + 1 ] ) { 2184 2185 n[ i + 1 ] = { 2186 text: n[ i + 1 ], 1183 2187 row: n[i].row + 1 1184 2188 }; 1185 o[ n[i].row + 1] = {1186 text: o[ n[i].row + 1],2189 o[ n[i].row + 1 ] = { 2190 text: o[ n[i].row + 1 ], 1187 2191 row: i + 1 1188 2192 }; 1189 2193 } 1190 2194 } 1191 1192 for (var i = n.length - 1; i > 0; i--) { 1193 if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1194 n[i - 1] == o[n[i].row - 1]) { 1195 n[i - 1] = { 1196 text: n[i - 1], 2195 2196 for ( i = n.length - 1; i > 0; i-- ) { 2197 if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 2198 n[ i - 1 ] == o[ n[i].row - 1 ]) { 2199 2200 n[ i - 1 ] = { 2201 text: n[ i - 1 ], 1197 2202 row: n[i].row - 1 1198 2203 }; 1199 o[ n[i].row - 1] = {1200 text: o[ n[i].row - 1],2204 o[ n[i].row - 1 ] = { 2205 text: o[ n[i].row - 1 ], 1201 2206 row: i - 1 1202 2207 }; 1203 2208 } 1204 2209 } 1205 2210 1206 2211 return { 1207 2212 o: o, … … 1209 2214 }; 1210 2215 } 1211 1212 return function(o, n){ 1213 o = o.replace(/\s+$/, ''); 1214 n = n.replace(/\s+$/, ''); 1215 var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1216 1217 var str = ""; 1218 1219 var oSpace = o.match(/\s+/g); 1220 if (oSpace == null) { 1221 oSpace = [" "]; 2216 2217 return function( o, n ) { 2218 o = o.replace( /\s+$/, "" ); 2219 n = n.replace( /\s+$/, "" ); 2220 2221 var i, pre, 2222 str = "", 2223 out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 2224 oSpace = o.match(/\s+/g), 2225 nSpace = n.match(/\s+/g); 2226 2227 if ( oSpace == null ) { 2228 oSpace = [ " " ]; 1222 2229 } 1223 2230 else { 1224 oSpace.push( " ");1225 } 1226 var nSpace = n.match(/\s+/g); 1227 if ( nSpace == null) {1228 nSpace = [ " "];2231 oSpace.push( " " ); 2232 } 2233 2234 if ( nSpace == null ) { 2235 nSpace = [ " " ]; 1229 2236 } 1230 2237 else { 1231 nSpace.push( " ");1232 } 1233 1234 if ( out.n.length == 0) {1235 for ( var i = 0; i < out.o.length; i++) {1236 str += '<del>'+ out.o[i] + oSpace[i] + "</del>";2238 nSpace.push( " " ); 2239 } 2240 2241 if ( out.n.length === 0 ) { 2242 for ( i = 0; i < out.o.length; i++ ) { 2243 str += "<del>" + out.o[i] + oSpace[i] + "</del>"; 1237 2244 } 1238 2245 } 1239 2246 else { 1240 if ( out.n[0].text == null) {1241 for ( n = 0; n < out.o.length && out.o[n].text == null; n++) {1242 str += '<del>'+ out.o[n] + oSpace[n] + "</del>";1243 } 1244 } 1245 1246 for ( var i = 0; i < out.n.length; i++) {2247 if ( out.n[0].text == null ) { 2248 for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 2249 str += "<del>" + out.o[n] + oSpace[n] + "</del>"; 2250 } 2251 } 2252 2253 for ( i = 0; i < out.n.length; i++ ) { 1247 2254 if (out.n[i].text == null) { 1248 str += '<ins>'+ out.n[i] + nSpace[i] + "</ins>";2255 str += "<ins>" + out.n[i] + nSpace[i] + "</ins>"; 1249 2256 } 1250 2257 else { 1251 var pre = ""; 1252 1253 for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1254 pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; 2258 // `pre` initialized at top of scope 2259 pre = ""; 2260 2261 for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 2262 pre += "<del>" + out.o[n] + oSpace[n] + "</del>"; 1255 2263 } 1256 2264 str += " " + out.n[i].text + nSpace[i] + pre; … … 1258 2266 } 1259 2267 } 1260 2268 1261 2269 return str; 1262 } 1263 })(); 1264 1265 })(this); 2270 }; 2271 }()); 2272 2273 // For browser, export only select globals 2274 if ( typeof window !== "undefined" ) { 2275 extend( window, QUnit.constructor.prototype ); 2276 window.QUnit = QUnit; 2277 } 2278 2279 // For CommonJS environments, export everything 2280 if ( typeof module !== "undefined" && module.exports ) { 2281 module.exports = QUnit; 2282 } 2283 2284 2285 // Get a reference to the global object, like window in browsers 2286 }( (function() { 2287 return this; 2288 })() )); -
trunk/tests/qunit/editor/js/utils.js
r27155 r27679 1 function fontFace(face) { 2 if (tinymce.isOpera) { 3 return "'" + face + "'"; 4 } else { 5 return face; 6 } 7 } 8 9 function findContainer(selector) { 10 var container; 11 if (tinymce.is(selector, 'string')) { 12 container = editor.dom.select(selector)[0]; 13 } else { 14 container = selector; 15 } 16 if (container.firstChild) { 17 container = container.firstChild; 18 } 19 return container; 20 } 21 22 function setSelection(startSelector, startOffset, endSelector, endOffset) { 23 if (!endSelector) { 24 endSelector = startSelector; 25 endOffset = startOffset; 26 } 27 var startContainer = findContainer(startSelector); 28 var endContainer = findContainer(endSelector); 29 var rng = editor.dom.createRng(); 30 31 function setRange(container, offset, start) { 32 offset = offset || 0; 33 34 if (offset === 'after') { 1 (function() { 2 function fontFace(face) { 3 if (tinymce.isOpera) { 4 return "'" + face + "'"; 5 } else { 6 return face; 7 } 8 } 9 10 function findContainer(selector) { 11 var container; 12 if (tinymce.is(selector, 'string')) { 13 container = editor.dom.select(selector)[0]; 14 } else { 15 container = selector; 16 } 17 if (container.firstChild) { 18 container = container.firstChild; 19 } 20 return container; 21 } 22 23 function setSelection(startSelector, startOffset, endSelector, endOffset) { 24 if (!endSelector) { 25 endSelector = startSelector; 26 endOffset = startOffset; 27 } 28 var startContainer = findContainer(startSelector); 29 var endContainer = findContainer(endSelector); 30 var rng = editor.dom.createRng(); 31 32 function setRange(container, offset, start) { 33 offset = offset || 0; 34 35 if (offset === 'after') { 36 if (start) { 37 rng.setStartAfter(container); 38 } else { 39 rng.setEndAfter(container); 40 } 41 return; 42 } else if (offset === 'afterNextCharacter') { 43 container = container.nextSibling; 44 offset = 1; 45 } 35 46 if (start) { 36 rng.setStart After(container);47 rng.setStart(container, offset); 37 48 } else { 38 rng.setEndAfter(container); 39 } 49 rng.setEnd(container, offset); 50 } 51 } 52 53 setRange(startContainer, startOffset, true); 54 setRange(endContainer, endOffset, false); 55 editor.selection.setRng(rng); 56 } 57 58 function trimContent(content) { 59 return content.replace(/^<p> <\/p>\n?/, '').replace(/\n?<p> <\/p>$/, ''); 60 } 61 62 /** 63 * Fakes a key event. 64 * 65 * @param {Element/String} e DOM element object or element id to send fake event to. 66 * @param {String} na Event name to fake like "keydown". 67 * @param {Object} o Optional object with data to send with the event like keyCode and charCode. 68 */ 69 function fakeKeyEvent(e, na, o) { 70 var ev; 71 72 o = tinymce.extend({ 73 keyCode : 13, 74 charCode : 0 75 }, o); 76 77 e = tinymce.DOM.get(e); 78 79 if (e.fireEvent) { 80 ev = document.createEventObject(); 81 tinymce.extend(ev, o); 82 e.fireEvent('on' + na, ev); 40 83 return; 41 } else if (offset === 'afterNextCharacter') { 42 container = container.nextSibling; 43 offset = 1; 44 } 45 if (start) { 46 rng.setStart(container, offset); 47 } else { 48 rng.setEnd(container, offset); 49 } 50 } 51 52 setRange(startContainer, startOffset, true); 53 setRange(endContainer, endOffset, false); 54 editor.selection.setRng(rng); 55 } 56 57 function initWhenTinyAndRobotAreReady(initTinyFunction) { 58 function loaded() { 59 QUnit.start(); 60 } 61 62 tinymce.on('AddEditor', function(e) { 63 e.editor.on('Init', function() { 64 loaded(); 65 }); 66 }); 67 68 window.robot.onload(initTinyFunction); 69 } 70 71 function trimContent(content) { 72 return content.replace(/^<p> <\/p>\n?/, '').replace(/\n?<p> <\/p>$/, ''); 73 } 74 75 /** 76 * Fakes a key event. 77 * 78 * @param {Element/String} e DOM element object or element id to send fake event to. 79 * @param {String} na Event name to fake like "keydown". 80 * @param {Object} o Optional object with data to send with the event like keyCode and charCode. 81 */ 82 function fakeKeyEvent(e, na, o) { 83 var ev; 84 85 o = tinymce.extend({ 86 keyCode : 13, 87 charCode : 0 88 }, o); 89 90 e = tinymce.DOM.get(e); 91 92 if (e.fireEvent) { 93 ev = document.createEventObject(); 94 tinymce.extend(ev, o); 95 e.fireEvent('on' + na, ev); 96 return; 97 } 98 99 if (document.createEvent) { 100 try { 101 // Fails in Safari 102 ev = document.createEvent('KeyEvents'); 103 ev.initKeyEvent(na, true, true, window, false, false, false, false, o.keyCode, o.charCode); 104 } catch (ex) { 105 ev = document.createEvent('Events'); 106 ev.initEvent(na, true, true); 84 } 85 86 if (document.createEvent) { 87 try { 88 // Fails in Safari 89 ev = document.createEvent('KeyEvents'); 90 ev.initKeyEvent(na, true, true, window, false, false, false, false, o.keyCode, o.charCode); 91 } catch (ex) { 92 ev = document.createEvent('Events'); 93 ev.initEvent(na, true, true); 94 95 ev.keyCode = o.keyCode; 96 ev.charCode = o.charCode; 97 } 98 } else { 99 ev = document.createEvent('UIEvents'); 100 101 if (ev.initUIEvent) 102 ev.initUIEvent(na, true, true, window, 1); 107 103 108 104 ev.keyCode = o.keyCode; 109 105 ev.charCode = o.charCode; 110 106 } 111 } else { 112 ev = document.createEvent('UIEvents'); 113 114 if (ev.initUIEvent) 115 ev.initUIEvent(na, true, true, window, 1); 116 117 ev.keyCode = o.keyCode; 118 ev.charCode = o.charCode; 119 } 120 121 e.dispatchEvent(ev); 122 } 123 124 function normalizeRng(rng) { 125 if (rng.startContainer.nodeType == 3) { 126 if (rng.startOffset == 0) 127 rng.setStartBefore(rng.startContainer); 128 else if (rng.startOffset >= rng.startContainer.nodeValue.length - 1) 129 rng.setStartAfter(rng.startContainer); 130 } 131 132 if (rng.endContainer.nodeType == 3) { 133 if (rng.endOffset == 0) 134 rng.setEndBefore(rng.endContainer); 135 else if (rng.endOffset >= rng.endContainer.nodeValue.length - 1) 136 rng.setEndAfter(rng.endContainer); 137 } 138 139 return rng; 140 } 141 142 // TODO: Replace this with the new event logic in 3.5 143 function type(chr) { 144 var editor = tinymce.activeEditor, keyCode, charCode, event = tinymce.dom.Event, evt, startElm, rng; 145 146 function fakeEvent(target, type, evt) { 147 editor.dom.fire(target, type, evt); 148 } 149 150 // Numeric keyCode 151 if (typeof(chr) == "number") { 152 charCode = keyCode = chr; 153 } else if (typeof(chr) == "string") { 154 // String value 155 if (chr == '\b') { 156 keyCode = 8; 157 charCode = chr.charCodeAt(0); 158 } else if (chr == '\n') { 159 keyCode = 13; 160 charCode = chr.charCodeAt(0); 161 } else { 162 charCode = chr.charCodeAt(0); 163 keyCode = charCode; 164 } 165 } else { 166 evt = chr; 167 } 168 169 evt = evt || {keyCode: keyCode, charCode: charCode}; 170 171 startElm = editor.selection.getStart(); 172 fakeEvent(startElm, 'keydown', evt); 173 fakeEvent(startElm, 'keypress', evt); 174 175 if (!evt.isDefaultPrevented()) { 176 if (keyCode == 8) { 177 if (editor.getDoc().selection) { 178 rng = editor.getDoc().selection.createRange(); 179 180 if (rng.text.length === 0) { 181 rng.moveStart('character', -1); 182 rng.select(); 107 108 e.dispatchEvent(ev); 109 } 110 111 function normalizeRng(rng) { 112 if (rng.startContainer.nodeType == 3) { 113 if (rng.startOffset == 0) 114 rng.setStartBefore(rng.startContainer); 115 else if (rng.startOffset >= rng.startContainer.nodeValue.length - 1) 116 rng.setStartAfter(rng.startContainer); 117 } 118 119 if (rng.endContainer.nodeType == 3) { 120 if (rng.endOffset == 0) 121 rng.setEndBefore(rng.endContainer); 122 else if (rng.endOffset >= rng.endContainer.nodeValue.length - 1) 123 rng.setEndAfter(rng.endContainer); 124 } 125 126 return rng; 127 } 128 129 // TODO: Replace this with the new event logic in 3.5 130 function type(chr) { 131 var editor = tinymce.activeEditor, keyCode, charCode, event = tinymce.dom.Event, evt, startElm, rng; 132 133 function fakeEvent(target, type, evt) { 134 editor.dom.fire(target, type, evt); 135 } 136 137 // Numeric keyCode 138 if (typeof(chr) == "number") { 139 charCode = keyCode = chr; 140 } else if (typeof(chr) == "string") { 141 // String value 142 if (chr == '\b') { 143 keyCode = 8; 144 charCode = chr.charCodeAt(0); 145 } else if (chr == '\n') { 146 keyCode = 13; 147 charCode = chr.charCodeAt(0); 148 } else { 149 charCode = chr.charCodeAt(0); 150 keyCode = charCode; 151 } 152 } else { 153 evt = chr; 154 } 155 156 evt = evt || {keyCode: keyCode, charCode: charCode}; 157 158 startElm = editor.selection.getStart(); 159 fakeEvent(startElm, 'keydown', evt); 160 fakeEvent(startElm, 'keypress', evt); 161 162 if (!evt.isDefaultPrevented()) { 163 if (keyCode == 8) { 164 if (editor.getDoc().selection) { 165 rng = editor.getDoc().selection.createRange(); 166 167 if (rng.text.length === 0) { 168 rng.moveStart('character', -1); 169 rng.select(); 170 } 171 172 rng.execCommand('Delete', false, null); 173 } else { 174 rng = editor.selection.getRng(); 175 176 if (rng.startContainer.nodeType == 1 && rng.collapsed) { 177 var nodes = rng.startContainer.childNodes, lastNode = nodes[nodes.length - 1]; 178 179 // If caret is at <p>abc|</p> and after the abc text node then move it to the end of the text node 180 // Expand the range to include the last char <p>ab[c]</p> since IE 11 doesn't delete otherwise 181 if (rng.startOffset >= nodes.length - 1 && lastNode && lastNode.nodeType == 3 && lastNode.data.length > 0) { 182 rng.setStart(lastNode, lastNode.data.length - 1); 183 rng.setEnd(lastNode, lastNode.data.length); 184 editor.selection.setRng(rng); 185 } 186 } 187 188 editor.getDoc().execCommand('Delete', false, null); 183 189 } 184 185 rng.execCommand('Delete', false, null); 186 } else { 187 rng = editor.selection.getRng(); 188 189 if (rng.startContainer.nodeType == 1 && rng.collapsed) { 190 var nodes = rng.startContainer.childNodes, lastNode = nodes[nodes.length - 1]; 191 192 // If caret is at <p>abc|</p> and after the abc text node then move it to the end of the text node 193 // Expand the range to include the last char <p>ab[c]</p> since IE 11 doesn't delete otherwise 194 if (rng.startOffset >= nodes.length - 1 && lastNode && lastNode.nodeType == 3 && lastNode.data.length > 0) { 195 rng.setStart(lastNode, lastNode.data.length - 1); 196 rng.setEnd(lastNode, lastNode.data.length); 197 editor.selection.setRng(rng); 190 } else if (typeof(chr) == 'string') { 191 rng = editor.selection.getRng(true); 192 193 if (rng.startContainer.nodeType == 3 && rng.collapsed) { 194 rng.startContainer.insertData(rng.startOffset, chr); 195 rng.setStart(rng.startContainer, rng.startOffset + 1); 196 rng.collapse(true); 197 editor.selection.setRng(rng); 198 } else { 199 rng.insertNode(editor.getDoc().createTextNode(chr)); 200 } 201 } 202 } 203 204 fakeEvent(startElm, 'keyup', evt); 205 } 206 207 function cleanHtml(html) { 208 html = html.toLowerCase().replace(/[\r\n]+/gi, ''); 209 html = html.replace(/ (sizcache[0-9]+|sizcache|nodeindex|sizset[0-9]+|sizset|data\-mce\-expando|data\-mce\-selected)="[^"]*"/gi, ''); 210 html = html.replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>|<div[^>]+data-mce-bogus[^>]+><\/div>/gi, ''); 211 html = html.replace(/ style="([^"]+)"/gi, function(val1, val2) { 212 val2 = val2.replace(/;$/, ''); 213 return ' style="' + val2.replace(/\:([^ ])/g, ': $1') + ';"'; 214 }); 215 216 return html; 217 } 218 219 function normalizeHtml(html) { 220 var writer = new tinymce.html.Writer(); 221 222 new tinymce.html.SaxParser({ 223 validate: false, 224 comment: writer.comment, 225 cdata: writer.cdata, 226 text: writer.text, 227 end: writer.end, 228 pi: writer.pi, 229 doctype: writer.doctype, 230 231 start: function(name, attrs, empty) { 232 attrs.sort(function(a, b) { 233 if (a.name === b.name) { 234 return 0; 198 235 } 199 } 200 201 editor.getDoc().execCommand('Delete', false, null); 202 } 203 } else if (typeof(chr) == 'string') { 204 rng = editor.selection.getRng(true); 205 206 if (rng.startContainer.nodeType == 3 && rng.collapsed) { 207 rng.startContainer.insertData(rng.startOffset, chr); 208 rng.setStart(rng.startContainer, rng.startOffset + 1); 209 rng.collapse(true); 210 editor.selection.setRng(rng); 211 } else { 212 rng.insertNode(editor.getDoc().createTextNode(chr)); 213 } 214 } 215 } 216 217 fakeEvent(startElm, 'keyup', evt); 218 } 219 220 function cleanHtml(html) { 221 html = html.toLowerCase().replace(/[\r\n]+/gi, ''); 222 html = html.replace(/ (sizcache[0-9]+|sizcache|nodeindex|sizset[0-9]+|sizset|data\-mce\-expando|data\-mce\-selected)="[^"]*"/gi, ''); 223 html = html.replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>|<div[^>]+data-mce-bogus[^>]+><\/div>/gi, ''); 224 225 return html; 226 } 227 228 function normalizeHtml(html) { 229 var writer = new tinymce.html.Writer(); 230 231 new tinymce.html.SaxParser({ 232 validate: false, 233 comment: writer.comment, 234 cdata: writer.cdata, 235 text: writer.text, 236 end: writer.end, 237 pi: writer.pi, 238 doctype: writer.doctype, 239 240 start: function(name, attrs, empty) { 241 attrs.sort(function(a, b) { 242 if (a.name === b.name) { 243 return 0; 244 } 245 246 return a.name > b.name ? 1 : -1; 247 }); 248 249 writer.start(name, attrs, empty); 250 } 251 }).parse(html); 252 253 return writer.getContent(); 254 } 255 256 /** 257 * Measures the x, y, w, h of the specified element/control relative to the view element. 258 */ 259 function rect(ctrl) { 260 var outerRect, innerRect; 261 262 if (ctrl.nodeType) { 263 innerRect = ctrl.getBoundingClientRect(); 264 } else { 265 innerRect = ctrl.getEl().getBoundingClientRect(); 266 } 267 268 outerRect = document.getElementById('view').getBoundingClientRect(); 269 270 return [ 271 Math.round(innerRect.left - outerRect.left), 272 Math.round(innerRect.top - outerRect.top), 273 Math.round(innerRect.right - innerRect.left), 274 Math.round(innerRect.bottom - innerRect.top) 275 ]; 276 } 277 278 function size(ctrl) { 279 return rect(ctrl).slice(2); 280 } 281 282 function resetScroll(elm) { 283 elm.scrollTop = 0; 284 elm.scrollLeft = 0; 285 } 286 287 // Needed since fonts render differently on different platforms 288 function nearlyEqualRects(rect1, rect2, diff) { 289 diff = diff || 1; 290 291 for (var i = 0; i < 4; i++) { 292 if (Math.abs(rect1[i] - rect2[i]) > diff) { 293 deepEqual(rect1, rect2); 294 return; 295 } 296 } 297 298 ok(true); 299 } 236 237 return a.name > b.name ? 1 : -1; 238 }); 239 240 writer.start(name, attrs, empty); 241 } 242 }).parse(html); 243 244 return writer.getContent(); 245 } 246 247 /** 248 * Measures the x, y, w, h of the specified element/control relative to the view element. 249 */ 250 function rect(ctrl) { 251 var outerRect, innerRect; 252 253 if (ctrl.nodeType) { 254 innerRect = ctrl.getBoundingClientRect(); 255 } else { 256 innerRect = ctrl.getEl().getBoundingClientRect(); 257 } 258 259 outerRect = document.getElementById('view').getBoundingClientRect(); 260 261 return [ 262 Math.round(innerRect.left - outerRect.left), 263 Math.round(innerRect.top - outerRect.top), 264 Math.round(innerRect.right - innerRect.left), 265 Math.round(innerRect.bottom - innerRect.top) 266 ]; 267 } 268 269 function size(ctrl) { 270 return rect(ctrl).slice(2); 271 } 272 273 function resetScroll(elm) { 274 elm.scrollTop = 0; 275 elm.scrollLeft = 0; 276 } 277 278 // Needed since fonts render differently on different platforms 279 function nearlyEqualRects(rect1, rect2, diff) { 280 diff = diff || 1; 281 282 for (var i = 0; i < 4; i++) { 283 if (Math.abs(rect1[i] - rect2[i]) > diff) { 284 deepEqual(rect1, rect2); 285 return; 286 } 287 } 288 289 ok(true); 290 } 291 292 function getFontmostWindow() { 293 return editor.windowManager.windows[editor.windowManager.windows.length - 1]; 294 } 295 296 function pressArrowKey(evt) { 297 var dom = editor.dom, target = editor.selection.getNode(); 298 299 evt = tinymce.extend({keyCode: 37}, evt); 300 301 dom.fire(target, 'keydown', evt); 302 dom.fire(target, 'keypress', evt); 303 dom.fire(target, 'keyup', evt); 304 } 305 306 function pressEnter(evt) { 307 var dom = editor.dom, target = editor.selection.getNode(); 308 309 evt = tinymce.extend({keyCode: 13}, evt); 310 311 dom.fire(target, 'keydown', evt); 312 dom.fire(target, 'keypress', evt); 313 dom.fire(target, 'keyup', evt); 314 } 315 316 function trimBrsOnIE(html) { 317 return html.replace(/<br[^>]*>/gi, ''); 318 } 319 320 window.Utils = { 321 fontFace: fontFace, 322 findContainer: findContainer, 323 setSelection: setSelection, 324 trimContent: trimContent, 325 fakeKeyEvent: fakeKeyEvent, 326 normalizeRng: normalizeRng, 327 type: type, 328 cleanHtml: cleanHtml, 329 normalizeHtml: normalizeHtml, 330 rect: rect, 331 size: size, 332 resetScroll: resetScroll, 333 nearlyEqualRects: nearlyEqualRects, 334 getFontmostWindow: getFontmostWindow, 335 pressArrowKey: pressArrowKey, 336 pressEnter: pressEnter, 337 trimBrsOnIE: trimBrsOnIE 338 }; 339 })(); -
trunk/tests/qunit/editor/tinymce/dom/DOMUtils.js
r27155 r27679 1 1 (function() { 2 module("tinymce.dom.DOMUtils"); 3 2 4 var DOM = new tinymce.dom.DOMUtils(document, {keep_values : true, schema : new tinymce.html.Schema()}); 3 5 … … 7 9 DOM.add(document.body, 'div', {id : 'test'}); 8 10 9 dom = new tinymce.dom.DOMUtils(document, {hex_colors : true, keep_values : true, url_converter : function(u , n, e) {11 dom = new tinymce.dom.DOMUtils(document, {hex_colors : true, keep_values : true, url_converter : function(u) { 10 12 return 'X' + u + 'Y'; 11 13 }}); … … 219 221 ok(DOM.select('div', 'test').reverse); 220 222 221 DOM.setHTML('test', '<div class="test1 test2 test3">test 1</div><div class="test2">test 2 <div>test 3</div></div><div>test 4</div>') 223 DOM.setHTML('test', '<div class="test1 test2 test3">test 1</div><div class="test2">test 2 <div>test 3</div></div><div>test 4</div>'); 222 224 equal(DOM.select('div.test2', 'test').length, 2); 223 225 224 DOM.setHTML('test', '<div class="test1 test2 test3">test 1</div><div class="test2">test 2 <div>test 3</div></div><div>test 4</div>') 226 DOM.setHTML('test', '<div class="test1 test2 test3">test 1</div><div class="test2">test 2 <div>test 3</div></div><div>test 4</div>'); 225 227 equal(DOM.select('div div', 'test').length, 1, null, tinymce.isWebKit); // Issue: http://bugs.webkit.org/show_bug.cgi?id=17461 226 228 //alert(DOM.select('div div', 'test').length +","+DOM.get('test').querySelectorAll('div div').length); … … 265 267 equal(DOM.getAttrib('test', 'title'), 'abc'); 266 268 267 dom = new tinymce.dom.DOMUtils(document, {keep_values : true, url_converter : function(u, n , e) {269 dom = new tinymce.dom.DOMUtils(document, {keep_values : true, url_converter : function(u, n) { 268 270 return '&<>"' + u + '&<>"' + n; 269 271 }}); … … 287 289 288 290 test('getAttribs', 2, function() { 289 var dom;290 291 291 function check(obj, val) { 292 292 var count = 0; … … 295 295 296 296 tinymce.each(obj, function(o) { 297 if (tinymce.inArray(val, o.nodeName.toLowerCase()) != -1 && o.specified) 297 if (tinymce.inArray(val, o.nodeName.toLowerCase()) != -1 && o.specified) { 298 298 count++; 299 } 299 300 }); 300 301 301 302 return count == obj.length; 302 } ;303 } 303 304 304 305 DOM.add(document.body, 'div', {id : 'test'}); … … 357 358 equal(DOM.getParent('test2', function(n) {return n.nodeName == 'BODY';}).nodeName, 'BODY'); 358 359 equal(DOM.getParent('test2', function(n) {return n.nodeName == 'BODY';}, document.body), null); 359 equal(DOM.getParent('test2', function( n) {return false;}), null);360 equal(DOM.getParent('test2', function() {return false;}), null); 360 361 equal(DOM.getParent('test2', 'SPAN').nodeName, 'SPAN'); 361 362 equal(DOM.getParent('test2', 'body', DOM.get('test')), null); … … 436 437 437 438 test('getNext', 5, function() { 438 var r;439 440 439 DOM.add(document.body, 'div', {id : 'test'}); 441 440 … … 445 444 equal(DOM.getNext(DOM.get('test').firstChild, 'div'), null); 446 445 equal(DOM.getNext(null, 'div'), null); 447 equal(DOM.getNext(DOM.get('test').firstChild, function(n) {return n.nodeName == 'EM' }).nodeName, 'EM');446 equal(DOM.getNext(DOM.get('test').firstChild, function(n) {return n.nodeName == 'EM';}).nodeName, 'EM'); 448 447 449 448 DOM.remove('test'); … … 451 450 452 451 test('getPrev', 5, function() { 453 var r;454 455 452 DOM.add(document.body, 'div', {id : 'test'}); 456 453 … … 460 457 equal(DOM.getPrev(DOM.get('test').lastChild, 'div'), null); 461 458 equal(DOM.getPrev(null, 'div'), null); 462 equal(DOM.getPrev(DOM.get('test').lastChild, function(n) {return n.nodeName == 'STRONG' }).nodeName, 'STRONG');459 equal(DOM.getPrev(DOM.get('test').lastChild, function(n) {return n.nodeName == 'STRONG';}).nodeName, 'STRONG'); 463 460 464 461 DOM.remove('test'); … … 468 465 var c = 0; 469 466 470 DOM.loadCSS(' css/test.css?a=1,css/test.css?a=2,css/test.css?a=3');467 DOM.loadCSS('tinymce/dom/test.css?a=1,tinymce/dom/test.css?a=2,tinymce/dom/test.css?a=3'); 471 468 472 469 tinymce.each(document.getElementsByTagName('link'), function(n) { 473 if (n.href.indexOf('test.css?a=') != -1) 470 if (n.href.indexOf('test.css?a=') != -1) { 474 471 c++; 472 } 475 473 }); 476 474 … … 577 575 point = DOM.select('ul li:nth-child(2)', DOM.get('test'))[0]; 578 576 DOM.split(parent, point); 579 equal( cleanHtml(DOM.get('test').innerHTML), '<ul><li>first line<br><ul><li><span>second</span> <span>line</span></li></ul></li><li>third line<br></li></ul>');577 equal(Utils.cleanHtml(DOM.get('test').innerHTML), '<ul><li>first line<br><ul><li><span>second</span> <span>line</span></li></ul></li><li>third line<br></li></ul>'); 580 578 581 579 DOM.remove('test'); … … 631 629 ok(!DOM.isEmpty(DOM.get('test')), 'Non empty complex HTML with achor name'); 632 630 633 DOM.setHTML('test', '<img src=" x">');631 DOM.setHTML('test', '<img src="tinymce/ui/img/raster.gif">'); 634 632 ok(!DOM.isEmpty(DOM.get('test')), 'Non empty html with img element'); 635 633
Note: See TracChangeset
for help on using the changeset viewer.