├── .gitignore ├── addons └── canvas │ ├── qunit-canvas.js │ ├── qunit-canvas.html │ └── qunit-canvas-test.js ├── test ├── logs.html ├── index.html ├── headless.html ├── logs.js ├── test.js └── same.js ├── package.json ├── README.md └── qunit ├── qunit.css └── qunit.js /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | *~ 3 | *.diff 4 | *.patch 5 | .DS_Store 6 | .settings 7 | 8 | -------------------------------------------------------------------------------- /addons/canvas/qunit-canvas.js: -------------------------------------------------------------------------------- 1 | QUnit.extend( QUnit, { 2 | pixelEqual: function(canvas, x, y, r, g, b, a, message) { 3 | var actual = Array.prototype.slice.apply(canvas.getContext('2d').getImageData(x, y, 1, 1).data), expected = [r, g, b, a]; 4 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/logs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 10 |

QUnit Test Suite

11 |

12 |
13 |

14 |
    15 |
    test markup
    16 | 17 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qunit", 3 | "author": "The jQuery Project", 4 | "contributors": [ 5 | { 6 | "name": "John Resig", 7 | "email": "jeresig@gmail.com", 8 | "url": "http://ejohn.org/" 9 | }, 10 | { 11 | "name": "Jörn Zaefferer", 12 | "email": "joern.zaefferer@googlemail.com", 13 | "url": "http://bassistance.de/" 14 | }, 15 | "url": "http://docs.jquery.com/QUnit", 16 | "license": { 17 | "name": "MIT", 18 | "url": "http://www.opensource.org/licenses/mit-license.php" 19 | }, 20 | "description": "An easy-to-use JavaScript Unit Testing framework.", 21 | "keywords": [ "testing", "unit", "jquery" ], 22 | "lib": "qunit" 23 | } 24 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 |

    QUnit Test Suite

    13 |

    14 |
    15 |

    16 |
      17 |
      test markup
      18 | 19 | 20 | -------------------------------------------------------------------------------- /addons/canvas/qunit-canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite - Canvas Addon 6 | 7 | 8 | 9 | 10 | 11 | 12 |

      QUnit Test Suite - Canvas Addon

      13 |

      14 |
      15 |

      16 |
        17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/headless.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Test Suite 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 |
        test markup
        23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework. 2 | ================================ 3 | 4 | QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery 5 | project to test its code and plugins but is capable of testing any generic 6 | JavaScript code (and even capable of testing JavaScript code on the server-side). 7 | 8 | QUnit is especially useful for regression testing: Whenever a bug is reported, 9 | write a test that asserts the existence of that particular bug. Then fix it and 10 | commit both. Every time you work on the code again, run the tests. If the bug 11 | comes up again - a regression - you'll spot it immediately and know how to fix 12 | it, because you know what code you just changed. 13 | 14 | Having good unit test coverage makes safe refactoring easy and cheap. You can 15 | run the tests after each small refactoring step and always know what change 16 | broke something. 17 | 18 | QUnit is similar to other unit testing frameworks like JUnit, but makes use of 19 | the features JavaScript provides and helps with testing code in the browser, eg. 20 | with it's stop/start facilities for testing asynchronous code. 21 | 22 | If you are interested in helping developing QUnit, you are in the right place. 23 | For related discussions, visit the 24 | [QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing). 25 | 26 | Planning for a qunitjs.com site and other testing tools related work now happens 27 | on the [jQuery Testing Team planning wiki](http://jquerytesting.pbworks.com/w/page/41556026/FrontPage). 28 | -------------------------------------------------------------------------------- /addons/canvas/qunit-canvas-test.js: -------------------------------------------------------------------------------- 1 | test("Canvas pixels", function () { 2 | var canvas = document.getElementById('qunit-canvas'), context; 3 | try { 4 | context = canvas.getContext('2d'); 5 | } catch(e) { 6 | // propably no canvas support, just exit 7 | return; 8 | } 9 | context.fillStyle = 'rgba(0, 0, 0, 0)'; 10 | context.fillRect(0, 0, 5, 5); 11 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 12 | context.clearRect(0,0,5,5); 13 | context.fillStyle = 'rgba(255, 0, 0, 0)'; 14 | context.fillRect(0, 0, 5, 5); 15 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 16 | context.clearRect(0,0,5,5); 17 | context.fillStyle = 'rgba(0, 255, 0, 0)'; 18 | context.fillRect(0, 0, 5, 5); 19 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 20 | context.clearRect(0,0,5,5); 21 | context.fillStyle = 'rgba(0, 0, 255, 0)'; 22 | context.fillRect(0, 0, 5, 5); 23 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 0); 24 | context.clearRect(0,0,5,5); 25 | 26 | context.fillStyle = 'rgba(0, 0, 0, 0.5)'; 27 | context.fillRect(0, 0, 5, 5); 28 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 0, 127); 29 | context.clearRect(0,0,5,5); 30 | context.fillStyle = 'rgba(255, 0, 0, 0.5)'; 31 | context.fillRect(0, 0, 5, 5); 32 | QUnit.pixelEqual(canvas, 0, 0, 255, 0, 0, 127); 33 | context.clearRect(0,0,5,5); 34 | context.fillStyle = 'rgba(0, 255, 0, 0.5)'; 35 | context.fillRect(0, 0, 5, 5); 36 | QUnit.pixelEqual(canvas, 0, 0, 0, 255, 0, 127); 37 | context.clearRect(0,0,5,5); 38 | context.fillStyle = 'rgba(0, 0, 255, 0.5)'; 39 | context.fillRect(0, 0, 5, 5); 40 | QUnit.pixelEqual(canvas, 0, 0, 0, 0, 255, 127); 41 | context.clearRect(0,0,5,5); 42 | 43 | context.fillStyle = 'rgba(0, 0, 0, 0.5)'; 44 | context.fillRect(0, 0, 5, 5); 45 | QUnit.pixelEqual(canvas, 2, 2, 0, 0, 0, 127); 46 | context.clearRect(0,0,5,5); 47 | context.fillStyle = 'rgba(255, 0, 0, 0.5)'; 48 | context.fillRect(0, 0, 5, 5); 49 | QUnit.pixelEqual(canvas, 2, 2, 255, 0, 0, 127); 50 | context.clearRect(0,0,5,5); 51 | context.fillStyle = 'rgba(0, 255, 0, 0.5)'; 52 | context.fillRect(0, 0, 5, 5); 53 | QUnit.pixelEqual(canvas, 2, 2, 0, 255, 0, 127); 54 | context.clearRect(0,0,5,5); 55 | context.fillStyle = 'rgba(0, 0, 255, 0.5)'; 56 | context.fillRect(0, 0, 5, 5); 57 | QUnit.pixelEqual(canvas, 2, 2, 0, 0, 255, 127); 58 | context.clearRect(0,0,5,5); 59 | 60 | context.fillStyle = 'rgba(0, 0, 0, 1)'; 61 | context.fillRect(0, 0, 5, 5); 62 | QUnit.pixelEqual(canvas, 4, 4, 0, 0, 0, 255); 63 | context.clearRect(0,0,5,5); 64 | context.fillStyle = 'rgba(255, 0, 0, 1)'; 65 | context.fillRect(0, 0, 5, 5); 66 | QUnit.pixelEqual(canvas, 4, 4, 255, 0, 0, 255); 67 | context.clearRect(0,0,5,5); 68 | context.fillStyle = 'rgba(0, 255, 0, 1)'; 69 | context.fillRect(0, 0, 5, 5); 70 | QUnit.pixelEqual(canvas, 4, 4, 0, 255, 0, 255); 71 | context.clearRect(0,0,5,5); 72 | context.fillStyle = 'rgba(0, 0, 255, 1)'; 73 | context.fillRect(0, 0, 5, 5); 74 | QUnit.pixelEqual(canvas, 4, 4, 0, 0, 255, 255); 75 | context.clearRect(0,0,5,5); 76 | }); 77 | -------------------------------------------------------------------------------- /test/logs.js: -------------------------------------------------------------------------------- 1 | // TODO disable reordering for this suite! 2 | 3 | 4 | var begin = 0, 5 | moduleStart = 0, 6 | moduleDone = 0, 7 | testStart = 0, 8 | testDone = 0, 9 | log = 0, 10 | moduleContext, 11 | moduleDoneContext, 12 | testContext, 13 | testDoneContext, 14 | logContext; 15 | 16 | QUnit.begin = function() { 17 | begin++; 18 | }; 19 | QUnit.done = function() { 20 | }; 21 | QUnit.moduleStart = function(context) { 22 | moduleStart++; 23 | moduleContext = context; 24 | }; 25 | QUnit.moduleDone = function(context) { 26 | moduleDone++; 27 | moduleDoneContext = context; 28 | }; 29 | QUnit.testStart = function(context) { 30 | testStart++; 31 | testContext = context; 32 | }; 33 | QUnit.testDone = function(context) { 34 | testDone++; 35 | testDoneContext = context; 36 | }; 37 | QUnit.log = function(context) { 38 | log++; 39 | logContext = context; 40 | }; 41 | 42 | var logs = ["begin", "testStart", "testDone", "log", "moduleStart", "moduleDone", "done"]; 43 | for (var i = 0; i < logs.length; i++) { 44 | (function() { 45 | var log = logs[i], 46 | logger = QUnit[log]; 47 | QUnit[log] = function() { 48 | console.log(log, arguments); 49 | logger.apply(this, arguments); 50 | }; 51 | })(); 52 | } 53 | 54 | module("logs1"); 55 | 56 | test("test1", 13, function() { 57 | equal(begin, 1); 58 | equal(moduleStart, 1); 59 | equal(testStart, 1); 60 | equal(testDone, 0); 61 | equal(moduleDone, 0); 62 | 63 | deepEqual(logContext, { 64 | result: true, 65 | message: undefined, 66 | actual: 0, 67 | expected: 0 68 | }); 69 | equal("foo", "foo", "msg"); 70 | deepEqual(logContext, { 71 | result: true, 72 | message: "msg", 73 | actual: "foo", 74 | expected: "foo" 75 | }); 76 | strictEqual(testDoneContext, undefined); 77 | deepEqual(testContext, { 78 | name: "test1" 79 | }); 80 | strictEqual(moduleDoneContext, undefined); 81 | deepEqual(moduleContext, { 82 | name: "logs1" 83 | }); 84 | 85 | equal(log, 12); 86 | }); 87 | test("test2", 10, function() { 88 | equal(begin, 1); 89 | equal(moduleStart, 1); 90 | equal(testStart, 2); 91 | equal(testDone, 1); 92 | equal(moduleDone, 0); 93 | 94 | deepEqual(testDoneContext, { 95 | name: "test1", 96 | failed: 0, 97 | passed: 13, 98 | total: 13 99 | }); 100 | deepEqual(testContext, { 101 | name: "test2" 102 | }); 103 | strictEqual(moduleDoneContext, undefined); 104 | deepEqual(moduleContext, { 105 | name: "logs1" 106 | }); 107 | 108 | equal(log, 22); 109 | }); 110 | 111 | module("logs2"); 112 | 113 | test("test1", 9, function() { 114 | equal(begin, 1); 115 | equal(moduleStart, 2); 116 | equal(testStart, 3); 117 | equal(testDone, 2); 118 | equal(moduleDone, 1); 119 | 120 | deepEqual(testContext, { 121 | name: "test1" 122 | }); 123 | deepEqual(moduleDoneContext, { 124 | name: "logs1", 125 | failed: 0, 126 | passed: 23, 127 | total: 23 128 | }); 129 | deepEqual(moduleContext, { 130 | name: "logs2" 131 | }); 132 | 133 | equal(log, 31); 134 | }); 135 | test("test2", 8, function() { 136 | equal(begin, 1); 137 | equal(moduleStart, 2); 138 | equal(testStart, 4); 139 | equal(testDone, 3); 140 | equal(moduleDone, 1); 141 | 142 | deepEqual(testContext, { 143 | name: "test2" 144 | }); 145 | deepEqual(moduleContext, { 146 | name: "logs2" 147 | }); 148 | 149 | equal(log, 39); 150 | }); 151 | -------------------------------------------------------------------------------- /qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-banner { 58 | height: 5px; 59 | } 60 | 61 | #qunit-testrunner-toolbar { 62 | padding: 0.5em 0 0.5em 2em; 63 | color: #5E740B; 64 | background-color: #eee; 65 | } 66 | 67 | #qunit-userAgent { 68 | padding: 0.5em 0 0.5em 2.5em; 69 | background-color: #2b81af; 70 | color: #fff; 71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 72 | } 73 | 74 | 75 | /** Tests: Pass/Fail */ 76 | 77 | #qunit-tests { 78 | list-style-position: inside; 79 | } 80 | 81 | #qunit-tests li { 82 | padding: 0.4em 0.5em 0.4em 2.5em; 83 | border-bottom: 1px solid #fff; 84 | list-style-position: inside; 85 | } 86 | 87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 88 | display: none; 89 | } 90 | 91 | #qunit-tests li strong { 92 | cursor: pointer; 93 | } 94 | 95 | #qunit-tests li a { 96 | padding: 0.5em; 97 | color: #c2ccd1; 98 | text-decoration: none; 99 | } 100 | #qunit-tests li a:hover, 101 | #qunit-tests li a:focus { 102 | color: #000; 103 | } 104 | 105 | #qunit-tests ol { 106 | margin-top: 0.5em; 107 | padding: 0.5em; 108 | 109 | background-color: #fff; 110 | 111 | border-radius: 15px; 112 | -moz-border-radius: 15px; 113 | -webkit-border-radius: 15px; 114 | 115 | box-shadow: inset 0px 2px 13px #999; 116 | -moz-box-shadow: inset 0px 2px 13px #999; 117 | -webkit-box-shadow: inset 0px 2px 13px #999; 118 | } 119 | 120 | #qunit-tests table { 121 | border-collapse: collapse; 122 | margin-top: .2em; 123 | } 124 | 125 | #qunit-tests th { 126 | text-align: right; 127 | vertical-align: top; 128 | padding: 0 .5em 0 0; 129 | } 130 | 131 | #qunit-tests td { 132 | vertical-align: top; 133 | } 134 | 135 | #qunit-tests pre { 136 | margin: 0; 137 | white-space: pre-wrap; 138 | word-wrap: break-word; 139 | } 140 | 141 | #qunit-tests del { 142 | background-color: #e0f2be; 143 | color: #374e0c; 144 | text-decoration: none; 145 | } 146 | 147 | #qunit-tests ins { 148 | background-color: #ffcaca; 149 | color: #500; 150 | text-decoration: none; 151 | } 152 | 153 | /*** Test Counts */ 154 | 155 | #qunit-tests b.counts { color: black; } 156 | #qunit-tests b.passed { color: #5E740B; } 157 | #qunit-tests b.failed { color: #710909; } 158 | 159 | #qunit-tests li li { 160 | margin: 0.5em; 161 | padding: 0.4em 0.5em 0.4em 0.5em; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #5E740B; 171 | background-color: #fff; 172 | border-left: 26px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 26px solid #EE5757; 189 | } 190 | 191 | #qunit-tests > li:last-child { 192 | border-radius: 0 0 15px 15px; 193 | -moz-border-radius: 0 0 15px 15px; 194 | -webkit-border-bottom-right-radius: 15px; 195 | -webkit-border-bottom-left-radius: 15px; 196 | } 197 | 198 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 199 | #qunit-tests .fail .test-name, 200 | #qunit-tests .fail .module-name { color: #000000; } 201 | 202 | #qunit-tests .fail .test-actual { color: #EE5757; } 203 | #qunit-tests .fail .test-expected { color: green; } 204 | 205 | #qunit-banner.qunit-fail { background-color: #EE5757; } 206 | 207 | 208 | /** Result */ 209 | 210 | #qunit-testresult { 211 | padding: 0.5em 0.5em 0.5em 2.5em; 212 | 213 | color: #2b81af; 214 | background-color: #D2E0E6; 215 | 216 | border-bottom: 1px solid white; 217 | } 218 | 219 | /** Fixture */ 220 | 221 | #qunit-fixture { 222 | position: absolute; 223 | top: -10000px; 224 | left: -10000px; 225 | } 226 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | test("module without setup/teardown (default)", function() { 2 | expect(1); 3 | ok(true); 4 | }); 5 | 6 | test("expect in test", 3, function() { 7 | ok(true); 8 | ok(true); 9 | ok(true); 10 | }); 11 | 12 | test("expect in test", 1, function() { 13 | ok(true); 14 | }); 15 | 16 | module("setup test", { 17 | setup: function() { 18 | ok(true); 19 | } 20 | }); 21 | 22 | test("module with setup", function() { 23 | expect(2); 24 | ok(true); 25 | }); 26 | 27 | test("module with setup, expect in test call", 2, function() { 28 | ok(true); 29 | }); 30 | 31 | var state; 32 | 33 | module("setup/teardown test", { 34 | setup: function() { 35 | state = true; 36 | ok(true); 37 | }, 38 | teardown: function() { 39 | ok(true); 40 | } 41 | }); 42 | 43 | test("module with setup/teardown", function() { 44 | expect(3); 45 | ok(true); 46 | }); 47 | 48 | module("setup/teardown test 2"); 49 | 50 | test("module without setup/teardown", function() { 51 | expect(1); 52 | ok(true); 53 | }); 54 | 55 | if (typeof setTimeout !== 'undefined') { 56 | state = 'fail'; 57 | 58 | module("teardown and stop", { 59 | teardown: function() { 60 | equal(state, "done", "Test teardown."); 61 | } 62 | }); 63 | 64 | test("teardown must be called after test ended", function() { 65 | expect(1); 66 | stop(); 67 | setTimeout(function() { 68 | state = "done"; 69 | start(); 70 | }, 13); 71 | }); 72 | 73 | module("async setup test", { 74 | setup: function() { 75 | stop(); 76 | setTimeout(function(){ 77 | ok(true); 78 | start(); 79 | }, 500); 80 | } 81 | }); 82 | 83 | asyncTest("module with async setup", function() { 84 | expect(2); 85 | ok(true); 86 | start(); 87 | }); 88 | 89 | module("async teardown test", { 90 | teardown: function() { 91 | stop(); 92 | setTimeout(function(){ 93 | ok(true); 94 | start(); 95 | }, 500); 96 | } 97 | }); 98 | 99 | asyncTest("module with async teardown", function() { 100 | expect(2); 101 | ok(true); 102 | start(); 103 | }); 104 | 105 | module("asyncTest"); 106 | 107 | asyncTest("asyncTest", function() { 108 | expect(2); 109 | ok(true); 110 | setTimeout(function() { 111 | state = "done"; 112 | ok(true); 113 | start(); 114 | }, 13); 115 | }); 116 | 117 | asyncTest("asyncTest", 2, function() { 118 | ok(true); 119 | setTimeout(function() { 120 | state = "done"; 121 | ok(true); 122 | start(); 123 | }, 13); 124 | }); 125 | 126 | test("sync", 2, function() { 127 | stop(); 128 | setTimeout(function() { 129 | ok(true); 130 | start(); 131 | }, 13); 132 | stop(); 133 | setTimeout(function() { 134 | ok(true); 135 | start(); 136 | }, 125); 137 | }); 138 | 139 | test("test synchronous calls to stop", 2, function() { 140 | stop(); 141 | setTimeout(function(){ 142 | ok(true, 'first'); 143 | start(); 144 | stop(); 145 | setTimeout(function(){ 146 | ok(true, 'second'); 147 | start(); 148 | }, 150); 149 | }, 150); 150 | }); 151 | } 152 | 153 | module("save scope", { 154 | setup: function() { 155 | this.foo = "bar"; 156 | }, 157 | teardown: function() { 158 | deepEqual(this.foo, "bar"); 159 | } 160 | }); 161 | test("scope check", function() { 162 | expect(2); 163 | deepEqual(this.foo, "bar"); 164 | }); 165 | 166 | module("simple testEnvironment setup", { 167 | foo: "bar", 168 | bugid: "#5311" // example of meta-data 169 | }); 170 | test("scope check", function() { 171 | deepEqual(this.foo, "bar"); 172 | }); 173 | test("modify testEnvironment",function() { 174 | this.foo="hamster"; 175 | }); 176 | test("testEnvironment reset for next test",function() { 177 | deepEqual(this.foo, "bar"); 178 | }); 179 | 180 | module("testEnvironment with object", { 181 | options:{ 182 | recipe:"soup", 183 | ingredients:["hamster","onions"] 184 | } 185 | }); 186 | test("scope check", function() { 187 | deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions"]}) ; 188 | }); 189 | test("modify testEnvironment",function() { 190 | // since we do a shallow copy, the testEnvironment can be modified 191 | this.options.ingredients.push("carrots"); 192 | }); 193 | test("testEnvironment reset for next test",function() { 194 | deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions","carrots"]}, "Is this a bug or a feature? Could do a deep copy") ; 195 | }); 196 | 197 | 198 | module("testEnvironment tests"); 199 | 200 | function makeurl() { 201 | var testEnv = QUnit.current_testEnvironment; 202 | var url = testEnv.url || 'http://example.com/search'; 203 | var q = testEnv.q || 'a search test'; 204 | return url + '?q='+encodeURIComponent(q); 205 | } 206 | 207 | test("makeurl working",function() { 208 | equal( QUnit.current_testEnvironment, this, 'The current testEnvironment is global'); 209 | equal( makeurl(), 'http://example.com/search?q=a%20search%20test', 'makeurl returns a default url if nothing specified in the testEnvironment'); 210 | }); 211 | 212 | module("testEnvironment with makeurl settings", { 213 | url: 'http://google.com/', 214 | q: 'another_search_test' 215 | }); 216 | test("makeurl working with settings from testEnvironment", function() { 217 | equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to form the url'); 218 | }); 219 | test("each test can extend the module testEnvironment", { 220 | q:'hamstersoup' 221 | }, function() { 222 | equal( makeurl(), 'http://google.com/?q=hamstersoup', 'url from module, q from test'); 223 | }); 224 | 225 | module("jsDump"); 226 | test("jsDump output", function() { 227 | equals( QUnit.jsDump.parse([1, 2]), "[\n 1,\n 2\n]" ); 228 | equals( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"top\": 5,\n \"left\": 0\n}" ); 229 | if (typeof document !== 'undefined' && document.getElementById("qunit-header")) { 230 | equals( QUnit.jsDump.parse(document.getElementById("qunit-header")), "

        " ); 231 | equals( QUnit.jsDump.parse(document.getElementsByTagName("h1")), "[\n

        \n]" ); 232 | } 233 | }); 234 | 235 | module("assertions"); 236 | test("raises",function() { 237 | function CustomError( message ) { 238 | this.message = message; 239 | } 240 | 241 | CustomError.prototype.toString = function() { 242 | return this.message; 243 | }; 244 | 245 | raises( 246 | function() { 247 | throw "error" 248 | } 249 | ); 250 | 251 | raises( 252 | function() { 253 | throw "error" 254 | }, 255 | 'raises with just a message, no expected' 256 | ); 257 | 258 | raises( 259 | function() { 260 | throw new CustomError(); 261 | }, 262 | CustomError, 263 | 'raised error is an instance of CustomError' 264 | ); 265 | 266 | raises( 267 | function() { 268 | throw new CustomError("some error description"); 269 | }, 270 | /description/, 271 | "raised error message contains 'description'" 272 | ); 273 | 274 | raises( 275 | function() { 276 | throw new CustomError("some error description"); 277 | }, 278 | function( err ) { 279 | if ( (err instanceof CustomError) && /description/.test(err) ) { 280 | return true; 281 | } 282 | }, 283 | "custom validation function" 284 | ); 285 | 286 | }); 287 | 288 | if (typeof document !== "undefined") { 289 | 290 | module("fixture"); 291 | test("setup", function() { 292 | document.getElementById("qunit-fixture").innerHTML = "foobar"; 293 | }); 294 | test("basics", function() { 295 | equal( document.getElementById("qunit-fixture").innerHTML, "test markup", "automatically reset" ); 296 | }); 297 | 298 | } 299 | 300 | module("custom assertions"); 301 | (function() { 302 | function mod2(value, expected, message) { 303 | var actual = value % 2; 304 | QUnit.push(actual == expected, actual, expected, message); 305 | } 306 | test("mod2", function() { 307 | mod2(2, 0, "2 % 2 == 0"); 308 | mod2(3, 1, "3 % 2 == 1"); 309 | }) 310 | })(); 311 | 312 | (function() { 313 | var reset = QUnit.reset; 314 | function afterTest() { 315 | ok( false, "reset should not modify test status" ); 316 | } 317 | module("reset"); 318 | test("reset runs assertions", function() { 319 | QUnit.reset = function() { 320 | afterTest(); 321 | reset.apply( this, arguments ); 322 | }; 323 | }); 324 | test("reset runs assertions2", function() { 325 | QUnit.reset = reset; 326 | }); 327 | })(); 328 | 329 | module("noglobals", { 330 | teardown: function() { 331 | delete window.badGlobalVariableIntroducedInTest; 332 | } 333 | }); 334 | test("let teardown clean up globals", function() { 335 | // this test will always pass if run without ?noglobals=true 336 | window.badGlobalVariableIntroducedInTest = true; 337 | }); 338 | -------------------------------------------------------------------------------- /qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var defined = { 14 | setTimeout: typeof window.setTimeout !== "undefined", 15 | sessionStorage: (function() { 16 | try { 17 | return !!sessionStorage.getItem; 18 | } catch(e) { 19 | return false; 20 | } 21 | })() 22 | }; 23 | 24 | var testId = 0; 25 | 26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { 27 | this.name = name; 28 | this.testName = testName; 29 | this.expected = expected; 30 | this.testEnvironmentArg = testEnvironmentArg; 31 | this.async = async; 32 | this.callback = callback; 33 | this.assertions = []; 34 | }; 35 | Test.prototype = { 36 | init: function() { 37 | var tests = id("qunit-tests"); 38 | if (tests) { 39 | var b = document.createElement("strong"); 40 | b.innerHTML = "Running " + this.name; 41 | var li = document.createElement("li"); 42 | li.appendChild( b ); 43 | li.className = "running"; 44 | li.id = this.id = "test-output" + testId++; 45 | tests.appendChild( li ); 46 | } 47 | }, 48 | setup: function() { 49 | if (this.module != config.previousModule) { 50 | if ( config.previousModule ) { 51 | QUnit.moduleDone( { 52 | name: config.previousModule, 53 | failed: config.moduleStats.bad, 54 | passed: config.moduleStats.all - config.moduleStats.bad, 55 | total: config.moduleStats.all 56 | } ); 57 | } 58 | config.previousModule = this.module; 59 | config.moduleStats = { all: 0, bad: 0 }; 60 | QUnit.moduleStart( { 61 | name: this.module 62 | } ); 63 | } 64 | 65 | config.current = this; 66 | this.testEnvironment = extend({ 67 | setup: function() {}, 68 | teardown: function() {} 69 | }, this.moduleTestEnvironment); 70 | if (this.testEnvironmentArg) { 71 | extend(this.testEnvironment, this.testEnvironmentArg); 72 | } 73 | 74 | QUnit.testStart( { 75 | name: this.testName 76 | } ); 77 | 78 | // allow utility functions to access the current test environment 79 | // TODO why?? 80 | QUnit.current_testEnvironment = this.testEnvironment; 81 | 82 | try { 83 | if ( !config.pollution ) { 84 | saveGlobal(); 85 | } 86 | 87 | this.testEnvironment.setup.call(this.testEnvironment); 88 | } catch(e) { 89 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); 90 | } 91 | }, 92 | run: function() { 93 | if ( this.async ) { 94 | QUnit.stop(); 95 | } 96 | 97 | if ( config.notrycatch ) { 98 | this.callback.call(this.testEnvironment); 99 | return; 100 | } 101 | try { 102 | this.callback.call(this.testEnvironment); 103 | } catch(e) { 104 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); 105 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); 106 | // else next test will carry the responsibility 107 | saveGlobal(); 108 | 109 | // Restart the tests if they're blocking 110 | if ( config.blocking ) { 111 | start(); 112 | } 113 | } 114 | }, 115 | teardown: function() { 116 | try { 117 | this.testEnvironment.teardown.call(this.testEnvironment); 118 | checkPollution(); 119 | } catch(e) { 120 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); 121 | } 122 | }, 123 | finish: function() { 124 | if ( this.expected && this.expected != this.assertions.length ) { 125 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 126 | } 127 | 128 | var good = 0, bad = 0, 129 | tests = id("qunit-tests"); 130 | 131 | config.stats.all += this.assertions.length; 132 | config.moduleStats.all += this.assertions.length; 133 | 134 | if ( tests ) { 135 | var ol = document.createElement("ol"); 136 | 137 | for ( var i = 0; i < this.assertions.length; i++ ) { 138 | var assertion = this.assertions[i]; 139 | 140 | var li = document.createElement("li"); 141 | li.className = assertion.result ? "pass" : "fail"; 142 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 143 | ol.appendChild( li ); 144 | 145 | if ( assertion.result ) { 146 | good++; 147 | } else { 148 | bad++; 149 | config.stats.bad++; 150 | config.moduleStats.bad++; 151 | } 152 | } 153 | 154 | // store result when possible 155 | if ( QUnit.config.reorder && defined.sessionStorage ) { 156 | if (bad) { 157 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); 158 | } else { 159 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); 160 | } 161 | } 162 | 163 | if (bad == 0) { 164 | ol.style.display = "none"; 165 | } 166 | 167 | var b = document.createElement("strong"); 168 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 169 | 170 | var a = document.createElement("a"); 171 | a.innerHTML = "Rerun"; 172 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 173 | 174 | addEvent(b, "click", function() { 175 | var next = b.nextSibling.nextSibling, 176 | display = next.style.display; 177 | next.style.display = display === "none" ? "block" : "none"; 178 | }); 179 | 180 | addEvent(b, "dblclick", function(e) { 181 | var target = e && e.target ? e.target : window.event.srcElement; 182 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 183 | target = target.parentNode; 184 | } 185 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 186 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 187 | } 188 | }); 189 | 190 | var li = id(this.id); 191 | li.className = bad ? "fail" : "pass"; 192 | li.removeChild( li.firstChild ); 193 | li.appendChild( b ); 194 | li.appendChild( a ); 195 | li.appendChild( ol ); 196 | 197 | } else { 198 | for ( var i = 0; i < this.assertions.length; i++ ) { 199 | if ( !this.assertions[i].result ) { 200 | bad++; 201 | config.stats.bad++; 202 | config.moduleStats.bad++; 203 | } 204 | } 205 | } 206 | 207 | try { 208 | QUnit.reset(); 209 | } catch(e) { 210 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); 211 | } 212 | 213 | QUnit.testDone( { 214 | name: this.testName, 215 | failed: bad, 216 | passed: this.assertions.length - bad, 217 | total: this.assertions.length 218 | } ); 219 | }, 220 | 221 | queue: function() { 222 | var test = this; 223 | synchronize(function() { 224 | test.init(); 225 | }); 226 | function run() { 227 | // each of these can by async 228 | synchronize(function() { 229 | test.setup(); 230 | }); 231 | synchronize(function() { 232 | test.run(); 233 | }); 234 | synchronize(function() { 235 | test.teardown(); 236 | }); 237 | synchronize(function() { 238 | test.finish(); 239 | }); 240 | } 241 | // defer when previous test run passed, if storage is available 242 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); 243 | if (bad) { 244 | run(); 245 | } else { 246 | synchronize(run); 247 | }; 248 | } 249 | 250 | }; 251 | 252 | var QUnit = { 253 | 254 | // call on start of module test to prepend name to all tests 255 | module: function(name, testEnvironment) { 256 | config.currentModule = name; 257 | config.currentModuleTestEnviroment = testEnvironment; 258 | }, 259 | 260 | asyncTest: function(testName, expected, callback) { 261 | if ( arguments.length === 2 ) { 262 | callback = expected; 263 | expected = 0; 264 | } 265 | 266 | QUnit.test(testName, expected, callback, true); 267 | }, 268 | 269 | test: function(testName, expected, callback, async) { 270 | var name = '' + testName + '', testEnvironmentArg; 271 | 272 | if ( arguments.length === 2 ) { 273 | callback = expected; 274 | expected = null; 275 | } 276 | // is 2nd argument a testEnvironment? 277 | if ( expected && typeof expected === 'object') { 278 | testEnvironmentArg = expected; 279 | expected = null; 280 | } 281 | 282 | if ( config.currentModule ) { 283 | name = '' + config.currentModule + ": " + name; 284 | } 285 | 286 | if ( !validTest(config.currentModule + ": " + testName) ) { 287 | return; 288 | } 289 | 290 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); 291 | test.module = config.currentModule; 292 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 293 | test.queue(); 294 | }, 295 | 296 | /** 297 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 298 | */ 299 | expect: function(asserts) { 300 | config.current.expected = asserts; 301 | }, 302 | 303 | /** 304 | * Asserts true. 305 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 306 | */ 307 | ok: function(a, msg) { 308 | a = !!a; 309 | var details = { 310 | result: a, 311 | message: msg 312 | }; 313 | msg = escapeHtml(msg); 314 | QUnit.log(details); 315 | config.current.assertions.push({ 316 | result: a, 317 | message: msg 318 | }); 319 | }, 320 | 321 | /** 322 | * Checks that the first two arguments are equal, with an optional message. 323 | * Prints out both actual and expected values. 324 | * 325 | * Prefered to ok( actual == expected, message ) 326 | * 327 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 328 | * 329 | * @param Object actual 330 | * @param Object expected 331 | * @param String message (optional) 332 | */ 333 | equal: function(actual, expected, message) { 334 | QUnit.push(expected == actual, actual, expected, message); 335 | }, 336 | 337 | notEqual: function(actual, expected, message) { 338 | QUnit.push(expected != actual, actual, expected, message); 339 | }, 340 | 341 | deepEqual: function(actual, expected, message) { 342 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 343 | }, 344 | 345 | notDeepEqual: function(actual, expected, message) { 346 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 347 | }, 348 | 349 | strictEqual: function(actual, expected, message) { 350 | QUnit.push(expected === actual, actual, expected, message); 351 | }, 352 | 353 | notStrictEqual: function(actual, expected, message) { 354 | QUnit.push(expected !== actual, actual, expected, message); 355 | }, 356 | 357 | raises: function(block, expected, message) { 358 | var actual, ok = false; 359 | 360 | if (typeof expected === 'string') { 361 | message = expected; 362 | expected = null; 363 | } 364 | 365 | try { 366 | block(); 367 | } catch (e) { 368 | actual = e; 369 | } 370 | 371 | if (actual) { 372 | // we don't want to validate thrown error 373 | if (!expected) { 374 | ok = true; 375 | // expected is a regexp 376 | } else if (QUnit.objectType(expected) === "regexp") { 377 | ok = expected.test(actual); 378 | // expected is a constructor 379 | } else if (actual instanceof expected) { 380 | ok = true; 381 | // expected is a validation function which returns true is validation passed 382 | } else if (expected.call({}, actual) === true) { 383 | ok = true; 384 | } 385 | } 386 | 387 | QUnit.ok(ok, message); 388 | }, 389 | 390 | start: function() { 391 | config.semaphore--; 392 | if (config.semaphore > 0) { 393 | // don't start until equal number of stop-calls 394 | return; 395 | } 396 | if (config.semaphore < 0) { 397 | // ignore if start is called more often then stop 398 | config.semaphore = 0; 399 | } 400 | // A slight delay, to avoid any current callbacks 401 | if ( defined.setTimeout ) { 402 | window.setTimeout(function() { 403 | if (config.semaphore > 0) { 404 | return; 405 | } 406 | if ( config.timeout ) { 407 | clearTimeout(config.timeout); 408 | } 409 | 410 | config.blocking = false; 411 | process(); 412 | }, 13); 413 | } else { 414 | config.blocking = false; 415 | process(); 416 | } 417 | }, 418 | 419 | stop: function(timeout) { 420 | config.semaphore++; 421 | config.blocking = true; 422 | 423 | if ( timeout && defined.setTimeout ) { 424 | clearTimeout(config.timeout); 425 | config.timeout = window.setTimeout(function() { 426 | QUnit.ok( false, "Test timed out" ); 427 | QUnit.start(); 428 | }, timeout); 429 | } 430 | } 431 | }; 432 | 433 | // Backwards compatibility, deprecated 434 | QUnit.equals = QUnit.equal; 435 | QUnit.same = QUnit.deepEqual; 436 | 437 | // Maintain internal state 438 | var config = { 439 | // The queue of tests to run 440 | queue: [], 441 | 442 | // block until document ready 443 | blocking: true, 444 | 445 | // when enabled, show only failing tests 446 | // gets persisted through sessionStorage and can be changed in UI via checkbox 447 | hidepassed: false, 448 | 449 | // by default, run previously failed tests first 450 | // very useful in combination with "Hide passed tests" checked 451 | reorder: true, 452 | 453 | // by default, modify document.title when suite is done 454 | altertitle: true, 455 | 456 | noglobals: false, 457 | notrycatch: false 458 | }; 459 | 460 | // Load paramaters 461 | (function() { 462 | var location = window.location || { search: "", protocol: "file:" }, 463 | params = location.search.slice( 1 ).split( "&" ), 464 | length = params.length, 465 | urlParams = {}, 466 | current; 467 | 468 | if ( params[ 0 ] ) { 469 | for ( var i = 0; i < length; i++ ) { 470 | current = params[ i ].split( "=" ); 471 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 472 | // allow just a key to turn on a flag, e.g., test.html?noglobals 473 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 474 | urlParams[ current[ 0 ] ] = current[ 1 ]; 475 | if ( current[ 0 ] in config ) { 476 | config[ current[ 0 ] ] = current[ 1 ]; 477 | } 478 | } 479 | } 480 | 481 | QUnit.urlParams = urlParams; 482 | config.filter = urlParams.filter; 483 | 484 | // Figure out if we're running the tests from a server or not 485 | QUnit.isLocal = !!(location.protocol === 'file:'); 486 | })(); 487 | 488 | // Expose the API as global variables, unless an 'exports' 489 | // object exists, in that case we assume we're in CommonJS 490 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 491 | extend(window, QUnit); 492 | window.QUnit = QUnit; 493 | } else { 494 | extend(exports, QUnit); 495 | exports.QUnit = QUnit; 496 | } 497 | 498 | // define these after exposing globals to keep them in these QUnit namespace only 499 | extend(QUnit, { 500 | config: config, 501 | 502 | // Initialize the configuration options 503 | init: function() { 504 | extend(config, { 505 | stats: { all: 0, bad: 0 }, 506 | moduleStats: { all: 0, bad: 0 }, 507 | started: +new Date, 508 | updateRate: 1000, 509 | blocking: false, 510 | autostart: true, 511 | autorun: false, 512 | filter: "", 513 | queue: [], 514 | semaphore: 0 515 | }); 516 | 517 | var tests = id( "qunit-tests" ), 518 | banner = id( "qunit-banner" ), 519 | result = id( "qunit-testresult" ); 520 | 521 | if ( tests ) { 522 | tests.innerHTML = ""; 523 | } 524 | 525 | if ( banner ) { 526 | banner.className = ""; 527 | } 528 | 529 | if ( result ) { 530 | result.parentNode.removeChild( result ); 531 | } 532 | 533 | if ( tests ) { 534 | result = document.createElement( "p" ); 535 | result.id = "qunit-testresult"; 536 | result.className = "result"; 537 | tests.parentNode.insertBefore( result, tests ); 538 | result.innerHTML = 'Running...
         '; 539 | } 540 | }, 541 | 542 | /** 543 | * Resets the test setup. Useful for tests that modify the DOM. 544 | * 545 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 546 | */ 547 | reset: function() { 548 | if ( window.jQuery ) { 549 | jQuery( "#qunit-fixture" ).html( config.fixture ); 550 | } else { 551 | var main = id( 'qunit-fixture' ); 552 | if ( main ) { 553 | main.innerHTML = config.fixture; 554 | } 555 | } 556 | }, 557 | 558 | /** 559 | * Trigger an event on an element. 560 | * 561 | * @example triggerEvent( document.body, "click" ); 562 | * 563 | * @param DOMElement elem 564 | * @param String type 565 | */ 566 | triggerEvent: function( elem, type, event ) { 567 | if ( document.createEvent ) { 568 | event = document.createEvent("MouseEvents"); 569 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 570 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 571 | elem.dispatchEvent( event ); 572 | 573 | } else if ( elem.fireEvent ) { 574 | elem.fireEvent("on"+type); 575 | } 576 | }, 577 | 578 | // Safe object type checking 579 | is: function( type, obj ) { 580 | return QUnit.objectType( obj ) == type; 581 | }, 582 | 583 | objectType: function( obj ) { 584 | if (typeof obj === "undefined") { 585 | return "undefined"; 586 | 587 | // consider: typeof null === object 588 | } 589 | if (obj === null) { 590 | return "null"; 591 | } 592 | 593 | var type = Object.prototype.toString.call( obj ) 594 | .match(/^\[object\s(.*)\]$/)[1] || ''; 595 | 596 | switch (type) { 597 | case 'Number': 598 | if (isNaN(obj)) { 599 | return "nan"; 600 | } else { 601 | return "number"; 602 | } 603 | case 'String': 604 | case 'Boolean': 605 | case 'Array': 606 | case 'Date': 607 | case 'RegExp': 608 | case 'Function': 609 | return type.toLowerCase(); 610 | } 611 | if (typeof obj === "object") { 612 | return "object"; 613 | } 614 | return undefined; 615 | }, 616 | 617 | push: function(result, actual, expected, message) { 618 | var details = { 619 | result: result, 620 | message: message, 621 | actual: actual, 622 | expected: expected 623 | }; 624 | 625 | message = escapeHtml(message) || (result ? "okay" : "failed"); 626 | message = '' + message + ""; 627 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 628 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 629 | var output = message + ''; 630 | if (actual != expected) { 631 | output += ''; 632 | output += ''; 633 | } 634 | if (!result) { 635 | var source = sourceFromStacktrace(); 636 | if (source) { 637 | details.source = source; 638 | output += ''; 639 | } 640 | } 641 | output += "
        Expected:
        ' + expected + '
        Result:
        ' + actual + '
        Diff:
        ' + QUnit.diff(expected, actual) +'
        Source:
        ' + escapeHtml(source) + '
        "; 642 | 643 | QUnit.log(details); 644 | 645 | config.current.assertions.push({ 646 | result: !!result, 647 | message: output 648 | }); 649 | }, 650 | 651 | url: function( params ) { 652 | params = extend( extend( {}, QUnit.urlParams ), params ); 653 | var querystring = "?", 654 | key; 655 | for ( key in params ) { 656 | querystring += encodeURIComponent( key ) + "=" + 657 | encodeURIComponent( params[ key ] ) + "&"; 658 | } 659 | return window.location.pathname + querystring.slice( 0, -1 ); 660 | }, 661 | 662 | extend: extend, 663 | id: id, 664 | addEvent: addEvent, 665 | 666 | // Logging callbacks; all receive a single argument with the listed properties 667 | // run test/logs.html for any related changes 668 | begin: function() {}, 669 | // done: { failed, passed, total, runtime } 670 | done: function() {}, 671 | // log: { result, actual, expected, message } 672 | log: function() {}, 673 | // testStart: { name } 674 | testStart: function() {}, 675 | // testDone: { name, failed, passed, total } 676 | testDone: function() {}, 677 | // moduleStart: { name } 678 | moduleStart: function() {}, 679 | // moduleDone: { name, failed, passed, total } 680 | moduleDone: function() {} 681 | }); 682 | 683 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 684 | config.autorun = true; 685 | } 686 | 687 | QUnit.load = function() { 688 | QUnit.begin({}); 689 | 690 | // Initialize the config, saving the execution queue 691 | var oldconfig = extend({}, config); 692 | QUnit.init(); 693 | extend(config, oldconfig); 694 | 695 | config.blocking = false; 696 | 697 | var userAgent = id("qunit-userAgent"); 698 | if ( userAgent ) { 699 | userAgent.innerHTML = navigator.userAgent; 700 | } 701 | var banner = id("qunit-header"); 702 | if ( banner ) { 703 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + 704 | '' + 705 | ''; 706 | addEvent( banner, "change", function( event ) { 707 | var params = {}; 708 | params[ event.target.name ] = event.target.checked ? true : undefined; 709 | window.location = QUnit.url( params ); 710 | }); 711 | } 712 | 713 | var toolbar = id("qunit-testrunner-toolbar"); 714 | if ( toolbar ) { 715 | var filter = document.createElement("input"); 716 | filter.type = "checkbox"; 717 | filter.id = "qunit-filter-pass"; 718 | addEvent( filter, "click", function() { 719 | var ol = document.getElementById("qunit-tests"); 720 | if ( filter.checked ) { 721 | ol.className = ol.className + " hidepass"; 722 | } else { 723 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 724 | ol.className = tmp.replace(/ hidepass /, " "); 725 | } 726 | if ( defined.sessionStorage ) { 727 | if (filter.checked) { 728 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 729 | } else { 730 | sessionStorage.removeItem("qunit-filter-passed-tests"); 731 | } 732 | } 733 | }); 734 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 735 | filter.checked = true; 736 | var ol = document.getElementById("qunit-tests"); 737 | ol.className = ol.className + " hidepass"; 738 | } 739 | toolbar.appendChild( filter ); 740 | 741 | var label = document.createElement("label"); 742 | label.setAttribute("for", "qunit-filter-pass"); 743 | label.innerHTML = "Hide passed tests"; 744 | toolbar.appendChild( label ); 745 | } 746 | 747 | var main = id('qunit-fixture'); 748 | if ( main ) { 749 | config.fixture = main.innerHTML; 750 | } 751 | 752 | if (config.autostart) { 753 | QUnit.start(); 754 | } 755 | }; 756 | 757 | addEvent(window, "load", QUnit.load); 758 | 759 | function done() { 760 | config.autorun = true; 761 | 762 | // Log the last module results 763 | if ( config.currentModule ) { 764 | QUnit.moduleDone( { 765 | name: config.currentModule, 766 | failed: config.moduleStats.bad, 767 | passed: config.moduleStats.all - config.moduleStats.bad, 768 | total: config.moduleStats.all 769 | } ); 770 | } 771 | 772 | var banner = id("qunit-banner"), 773 | tests = id("qunit-tests"), 774 | runtime = +new Date - config.started, 775 | passed = config.stats.all - config.stats.bad, 776 | html = [ 777 | 'Tests completed in ', 778 | runtime, 779 | ' milliseconds.
        ', 780 | '', 781 | passed, 782 | ' tests of ', 783 | config.stats.all, 784 | ' passed, ', 785 | config.stats.bad, 786 | ' failed.' 787 | ].join(''); 788 | 789 | if ( banner ) { 790 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 791 | } 792 | 793 | if ( tests ) { 794 | id( "qunit-testresult" ).innerHTML = html; 795 | } 796 | 797 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 798 | // show ✖ for good, ✔ for bad suite result in title 799 | // use escape sequences in case file gets loaded with non-utf-8-charset 800 | document.title = [ 801 | (config.stats.bad ? "\u2716" : "\u2714"), 802 | document.title.replace(/^[\u2714\u2716] /i, "") 803 | ].join(" "); 804 | } 805 | 806 | QUnit.done( { 807 | failed: config.stats.bad, 808 | passed: passed, 809 | total: config.stats.all, 810 | runtime: runtime 811 | } ); 812 | } 813 | 814 | function validTest( name ) { 815 | var filter = config.filter, 816 | run = false; 817 | 818 | if ( !filter ) { 819 | return true; 820 | } 821 | 822 | var not = filter.charAt( 0 ) === "!"; 823 | if ( not ) { 824 | filter = filter.slice( 1 ); 825 | } 826 | 827 | if ( name.indexOf( filter ) !== -1 ) { 828 | return !not; 829 | } 830 | 831 | if ( not ) { 832 | run = true; 833 | } 834 | 835 | return run; 836 | } 837 | 838 | // so far supports only Firefox, Chrome and Opera (buggy) 839 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 840 | function sourceFromStacktrace() { 841 | try { 842 | throw new Error(); 843 | } catch ( e ) { 844 | if (e.stacktrace) { 845 | // Opera 846 | return e.stacktrace.split("\n")[6]; 847 | } else if (e.stack) { 848 | // Firefox, Chrome 849 | return e.stack.split("\n")[4]; 850 | } 851 | } 852 | } 853 | 854 | function escapeHtml(s) { 855 | if (!s) { 856 | return ""; 857 | } 858 | s = s + ""; 859 | return s.replace(/[\&"<>\\]/g, function(s) { 860 | switch(s) { 861 | case "&": return "&"; 862 | case "\\": return "\\\\"; 863 | case '"': return '\"'; 864 | case "<": return "<"; 865 | case ">": return ">"; 866 | default: return s; 867 | } 868 | }); 869 | } 870 | 871 | function synchronize( callback ) { 872 | config.queue.push( callback ); 873 | 874 | if ( config.autorun && !config.blocking ) { 875 | process(); 876 | } 877 | } 878 | 879 | function process() { 880 | var start = (new Date()).getTime(); 881 | 882 | while ( config.queue.length && !config.blocking ) { 883 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 884 | config.queue.shift()(); 885 | } else { 886 | window.setTimeout( process, 13 ); 887 | break; 888 | } 889 | } 890 | if (!config.blocking && !config.queue.length) { 891 | done(); 892 | } 893 | } 894 | 895 | function saveGlobal() { 896 | config.pollution = []; 897 | 898 | if ( config.noglobals ) { 899 | for ( var key in window ) { 900 | config.pollution.push( key ); 901 | } 902 | } 903 | } 904 | 905 | function checkPollution( name ) { 906 | var old = config.pollution; 907 | saveGlobal(); 908 | 909 | var newGlobals = diff( config.pollution, old ); 910 | if ( newGlobals.length > 0 ) { 911 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 912 | } 913 | 914 | var deletedGlobals = diff( old, config.pollution ); 915 | if ( deletedGlobals.length > 0 ) { 916 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 917 | } 918 | } 919 | 920 | // returns a new Array with the elements that are in a but not in b 921 | function diff( a, b ) { 922 | var result = a.slice(); 923 | for ( var i = 0; i < result.length; i++ ) { 924 | for ( var j = 0; j < b.length; j++ ) { 925 | if ( result[i] === b[j] ) { 926 | result.splice(i, 1); 927 | i--; 928 | break; 929 | } 930 | } 931 | } 932 | return result; 933 | } 934 | 935 | function fail(message, exception, callback) { 936 | if ( typeof console !== "undefined" && console.error && console.warn ) { 937 | console.error(message); 938 | console.error(exception); 939 | console.warn(callback.toString()); 940 | 941 | } else if ( window.opera && opera.postError ) { 942 | opera.postError(message, exception, callback.toString); 943 | } 944 | } 945 | 946 | function extend(a, b) { 947 | for ( var prop in b ) { 948 | if ( b[prop] === undefined ) { 949 | delete a[prop]; 950 | } else { 951 | a[prop] = b[prop]; 952 | } 953 | } 954 | 955 | return a; 956 | } 957 | 958 | function addEvent(elem, type, fn) { 959 | if ( elem.addEventListener ) { 960 | elem.addEventListener( type, fn, false ); 961 | } else if ( elem.attachEvent ) { 962 | elem.attachEvent( "on" + type, fn ); 963 | } else { 964 | fn(); 965 | } 966 | } 967 | 968 | function id(name) { 969 | return !!(typeof document !== "undefined" && document && document.getElementById) && 970 | document.getElementById( name ); 971 | } 972 | 973 | // Test for equality any JavaScript type. 974 | // Discussions and reference: http://philrathe.com/articles/equiv 975 | // Test suites: http://philrathe.com/tests/equiv 976 | // Author: Philippe Rathé 977 | QUnit.equiv = function () { 978 | 979 | var innerEquiv; // the real equiv function 980 | var callers = []; // stack to decide between skip/abort functions 981 | var parents = []; // stack to avoiding loops from circular referencing 982 | 983 | // Call the o related callback with the given arguments. 984 | function bindCallbacks(o, callbacks, args) { 985 | var prop = QUnit.objectType(o); 986 | if (prop) { 987 | if (QUnit.objectType(callbacks[prop]) === "function") { 988 | return callbacks[prop].apply(callbacks, args); 989 | } else { 990 | return callbacks[prop]; // or undefined 991 | } 992 | } 993 | } 994 | 995 | var callbacks = function () { 996 | 997 | // for string, boolean, number and null 998 | function useStrictEquality(b, a) { 999 | if (b instanceof a.constructor || a instanceof b.constructor) { 1000 | // to catch short annotaion VS 'new' annotation of a 1001 | // declaration 1002 | // e.g. var i = 1; 1003 | // var j = new Number(1); 1004 | return a == b; 1005 | } else { 1006 | return a === b; 1007 | } 1008 | } 1009 | 1010 | return { 1011 | "string" : useStrictEquality, 1012 | "boolean" : useStrictEquality, 1013 | "number" : useStrictEquality, 1014 | "null" : useStrictEquality, 1015 | "undefined" : useStrictEquality, 1016 | 1017 | "nan" : function(b) { 1018 | return isNaN(b); 1019 | }, 1020 | 1021 | "date" : function(b, a) { 1022 | return QUnit.objectType(b) === "date" 1023 | && a.valueOf() === b.valueOf(); 1024 | }, 1025 | 1026 | "regexp" : function(b, a) { 1027 | return QUnit.objectType(b) === "regexp" 1028 | && a.source === b.source && // the regex itself 1029 | a.global === b.global && // and its modifers 1030 | // (gmi) ... 1031 | a.ignoreCase === b.ignoreCase 1032 | && a.multiline === b.multiline; 1033 | }, 1034 | 1035 | // - skip when the property is a method of an instance (OOP) 1036 | // - abort otherwise, 1037 | // initial === would have catch identical references anyway 1038 | "function" : function() { 1039 | var caller = callers[callers.length - 1]; 1040 | return caller !== Object && typeof caller !== "undefined"; 1041 | }, 1042 | 1043 | "array" : function(b, a) { 1044 | var i, j, loop; 1045 | var len; 1046 | 1047 | // b could be an object literal here 1048 | if (!(QUnit.objectType(b) === "array")) { 1049 | return false; 1050 | } 1051 | 1052 | len = a.length; 1053 | if (len !== b.length) { // safe and faster 1054 | return false; 1055 | } 1056 | 1057 | // track reference to avoid circular references 1058 | parents.push(a); 1059 | for (i = 0; i < len; i++) { 1060 | loop = false; 1061 | for (j = 0; j < parents.length; j++) { 1062 | if (parents[j] === a[i]) { 1063 | loop = true;// dont rewalk array 1064 | } 1065 | } 1066 | if (!loop && !innerEquiv(a[i], b[i])) { 1067 | parents.pop(); 1068 | return false; 1069 | } 1070 | } 1071 | parents.pop(); 1072 | return true; 1073 | }, 1074 | 1075 | "object" : function(b, a) { 1076 | var i, j, loop; 1077 | var eq = true; // unless we can proove it 1078 | var aProperties = [], bProperties = []; // collection of 1079 | // strings 1080 | 1081 | // comparing constructors is more strict than using 1082 | // instanceof 1083 | if (a.constructor !== b.constructor) { 1084 | return false; 1085 | } 1086 | 1087 | // stack constructor before traversing properties 1088 | callers.push(a.constructor); 1089 | // track reference to avoid circular references 1090 | parents.push(a); 1091 | 1092 | for (i in a) { // be strict: don't ensures hasOwnProperty 1093 | // and go deep 1094 | loop = false; 1095 | for (j = 0; j < parents.length; j++) { 1096 | if (parents[j] === a[i]) 1097 | loop = true; // don't go down the same path 1098 | // twice 1099 | } 1100 | aProperties.push(i); // collect a's properties 1101 | 1102 | if (!loop && !innerEquiv(a[i], b[i])) { 1103 | eq = false; 1104 | break; 1105 | } 1106 | } 1107 | 1108 | callers.pop(); // unstack, we are done 1109 | parents.pop(); 1110 | 1111 | for (i in b) { 1112 | bProperties.push(i); // collect b's properties 1113 | } 1114 | 1115 | // Ensures identical properties name 1116 | return eq 1117 | && innerEquiv(aProperties.sort(), bProperties 1118 | .sort()); 1119 | } 1120 | }; 1121 | }(); 1122 | 1123 | innerEquiv = function() { // can take multiple arguments 1124 | var args = Array.prototype.slice.apply(arguments); 1125 | if (args.length < 2) { 1126 | return true; // end transition 1127 | } 1128 | 1129 | return (function(a, b) { 1130 | if (a === b) { 1131 | return true; // catch the most you can 1132 | } else if (a === null || b === null || typeof a === "undefined" 1133 | || typeof b === "undefined" 1134 | || QUnit.objectType(a) !== QUnit.objectType(b)) { 1135 | return false; // don't lose time with error prone cases 1136 | } else { 1137 | return bindCallbacks(a, callbacks, [ b, a ]); 1138 | } 1139 | 1140 | // apply transition with (1..n) arguments 1141 | })(args[0], args[1]) 1142 | && arguments.callee.apply(this, args.splice(1, 1143 | args.length - 1)); 1144 | }; 1145 | 1146 | return innerEquiv; 1147 | 1148 | }(); 1149 | 1150 | /** 1151 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1152 | * http://flesler.blogspot.com Licensed under BSD 1153 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1154 | * 1155 | * @projectDescription Advanced and extensible data dumping for Javascript. 1156 | * @version 1.0.0 1157 | * @author Ariel Flesler 1158 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1159 | */ 1160 | QUnit.jsDump = (function() { 1161 | function quote( str ) { 1162 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 1163 | }; 1164 | function literal( o ) { 1165 | return o + ''; 1166 | }; 1167 | function join( pre, arr, post ) { 1168 | var s = jsDump.separator(), 1169 | base = jsDump.indent(), 1170 | inner = jsDump.indent(1); 1171 | if ( arr.join ) 1172 | arr = arr.join( ',' + s + inner ); 1173 | if ( !arr ) 1174 | return pre + post; 1175 | return [ pre, inner + arr, base + post ].join(s); 1176 | }; 1177 | function array( arr ) { 1178 | var i = arr.length, ret = Array(i); 1179 | this.up(); 1180 | while ( i-- ) 1181 | ret[i] = this.parse( arr[i] ); 1182 | this.down(); 1183 | return join( '[', ret, ']' ); 1184 | }; 1185 | 1186 | var reName = /^function (\w+)/; 1187 | 1188 | var jsDump = { 1189 | parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance 1190 | var parser = this.parsers[ type || this.typeOf(obj) ]; 1191 | type = typeof parser; 1192 | 1193 | return type == 'function' ? parser.call( this, obj ) : 1194 | type == 'string' ? parser : 1195 | this.parsers.error; 1196 | }, 1197 | typeOf:function( obj ) { 1198 | var type; 1199 | if ( obj === null ) { 1200 | type = "null"; 1201 | } else if (typeof obj === "undefined") { 1202 | type = "undefined"; 1203 | } else if (QUnit.is("RegExp", obj)) { 1204 | type = "regexp"; 1205 | } else if (QUnit.is("Date", obj)) { 1206 | type = "date"; 1207 | } else if (QUnit.is("Function", obj)) { 1208 | type = "function"; 1209 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { 1210 | type = "window"; 1211 | } else if (obj.nodeType === 9) { 1212 | type = "document"; 1213 | } else if (obj.nodeType) { 1214 | type = "node"; 1215 | } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { 1216 | type = "array"; 1217 | } else { 1218 | type = typeof obj; 1219 | } 1220 | return type; 1221 | }, 1222 | separator:function() { 1223 | return this.multiline ? this.HTML ? '
        ' : '\n' : this.HTML ? ' ' : ' '; 1224 | }, 1225 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1226 | if ( !this.multiline ) 1227 | return ''; 1228 | var chr = this.indentChar; 1229 | if ( this.HTML ) 1230 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1231 | return Array( this._depth_ + (extra||0) ).join(chr); 1232 | }, 1233 | up:function( a ) { 1234 | this._depth_ += a || 1; 1235 | }, 1236 | down:function( a ) { 1237 | this._depth_ -= a || 1; 1238 | }, 1239 | setParser:function( name, parser ) { 1240 | this.parsers[name] = parser; 1241 | }, 1242 | // The next 3 are exposed so you can use them 1243 | quote:quote, 1244 | literal:literal, 1245 | join:join, 1246 | // 1247 | _depth_: 1, 1248 | // This is the list of parsers, to modify them, use jsDump.setParser 1249 | parsers:{ 1250 | window: '[Window]', 1251 | document: '[Document]', 1252 | error:'[ERROR]', //when no parser is found, shouldn't happen 1253 | unknown: '[Unknown]', 1254 | 'null':'null', 1255 | 'undefined':'undefined', 1256 | 'function':function( fn ) { 1257 | var ret = 'function', 1258 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1259 | if ( name ) 1260 | ret += ' ' + name; 1261 | ret += '('; 1262 | 1263 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1264 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1265 | }, 1266 | array: array, 1267 | nodelist: array, 1268 | arguments: array, 1269 | object:function( map ) { 1270 | var ret = [ ]; 1271 | QUnit.jsDump.up(); 1272 | for ( var key in map ) 1273 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); 1274 | QUnit.jsDump.down(); 1275 | return join( '{', ret, '}' ); 1276 | }, 1277 | node:function( node ) { 1278 | var open = QUnit.jsDump.HTML ? '<' : '<', 1279 | close = QUnit.jsDump.HTML ? '>' : '>'; 1280 | 1281 | var tag = node.nodeName.toLowerCase(), 1282 | ret = open + tag; 1283 | 1284 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1285 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1286 | if ( val ) 1287 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1288 | } 1289 | return ret + close + open + '/' + tag + close; 1290 | }, 1291 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1292 | var l = fn.length; 1293 | if ( !l ) return ''; 1294 | 1295 | var args = Array(l); 1296 | while ( l-- ) 1297 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1298 | return ' ' + args.join(', ') + ' '; 1299 | }, 1300 | key:quote, //object calls it internally, the key part of an item in a map 1301 | functionCode:'[code]', //function calls it internally, it's the content of the function 1302 | attribute:quote, //node calls it internally, it's an html attribute value 1303 | string:quote, 1304 | date:quote, 1305 | regexp:literal, //regex 1306 | number:literal, 1307 | 'boolean':literal 1308 | }, 1309 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1310 | id:'id', 1311 | name:'name', 1312 | 'class':'className' 1313 | }, 1314 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1315 | indentChar:' ',//indentation unit 1316 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1317 | }; 1318 | 1319 | return jsDump; 1320 | })(); 1321 | 1322 | // from Sizzle.js 1323 | function getText( elems ) { 1324 | var ret = "", elem; 1325 | 1326 | for ( var i = 0; elems[i]; i++ ) { 1327 | elem = elems[i]; 1328 | 1329 | // Get the text from text nodes and CDATA nodes 1330 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1331 | ret += elem.nodeValue; 1332 | 1333 | // Traverse everything else, except comment nodes 1334 | } else if ( elem.nodeType !== 8 ) { 1335 | ret += getText( elem.childNodes ); 1336 | } 1337 | } 1338 | 1339 | return ret; 1340 | }; 1341 | 1342 | /* 1343 | * Javascript Diff Algorithm 1344 | * By John Resig (http://ejohn.org/) 1345 | * Modified by Chu Alan "sprite" 1346 | * 1347 | * Released under the MIT license. 1348 | * 1349 | * More Info: 1350 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1351 | * 1352 | * Usage: QUnit.diff(expected, actual) 1353 | * 1354 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1355 | */ 1356 | QUnit.diff = (function() { 1357 | function diff(o, n) { 1358 | var ns = new Object(); 1359 | var os = new Object(); 1360 | 1361 | for (var i = 0; i < n.length; i++) { 1362 | if (ns[n[i]] == null) 1363 | ns[n[i]] = { 1364 | rows: new Array(), 1365 | o: null 1366 | }; 1367 | ns[n[i]].rows.push(i); 1368 | } 1369 | 1370 | for (var i = 0; i < o.length; i++) { 1371 | if (os[o[i]] == null) 1372 | os[o[i]] = { 1373 | rows: new Array(), 1374 | n: null 1375 | }; 1376 | os[o[i]].rows.push(i); 1377 | } 1378 | 1379 | for (var i in ns) { 1380 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1381 | n[ns[i].rows[0]] = { 1382 | text: n[ns[i].rows[0]], 1383 | row: os[i].rows[0] 1384 | }; 1385 | o[os[i].rows[0]] = { 1386 | text: o[os[i].rows[0]], 1387 | row: ns[i].rows[0] 1388 | }; 1389 | } 1390 | } 1391 | 1392 | for (var i = 0; i < n.length - 1; i++) { 1393 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1394 | n[i + 1] == o[n[i].row + 1]) { 1395 | n[i + 1] = { 1396 | text: n[i + 1], 1397 | row: n[i].row + 1 1398 | }; 1399 | o[n[i].row + 1] = { 1400 | text: o[n[i].row + 1], 1401 | row: i + 1 1402 | }; 1403 | } 1404 | } 1405 | 1406 | for (var i = n.length - 1; i > 0; i--) { 1407 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1408 | n[i - 1] == o[n[i].row - 1]) { 1409 | n[i - 1] = { 1410 | text: n[i - 1], 1411 | row: n[i].row - 1 1412 | }; 1413 | o[n[i].row - 1] = { 1414 | text: o[n[i].row - 1], 1415 | row: i - 1 1416 | }; 1417 | } 1418 | } 1419 | 1420 | return { 1421 | o: o, 1422 | n: n 1423 | }; 1424 | } 1425 | 1426 | return function(o, n) { 1427 | o = o.replace(/\s+$/, ''); 1428 | n = n.replace(/\s+$/, ''); 1429 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1430 | 1431 | var str = ""; 1432 | 1433 | var oSpace = o.match(/\s+/g); 1434 | if (oSpace == null) { 1435 | oSpace = [" "]; 1436 | } 1437 | else { 1438 | oSpace.push(" "); 1439 | } 1440 | var nSpace = n.match(/\s+/g); 1441 | if (nSpace == null) { 1442 | nSpace = [" "]; 1443 | } 1444 | else { 1445 | nSpace.push(" "); 1446 | } 1447 | 1448 | if (out.n.length == 0) { 1449 | for (var i = 0; i < out.o.length; i++) { 1450 | str += '' + out.o[i] + oSpace[i] + ""; 1451 | } 1452 | } 1453 | else { 1454 | if (out.n[0].text == null) { 1455 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1456 | str += '' + out.o[n] + oSpace[n] + ""; 1457 | } 1458 | } 1459 | 1460 | for (var i = 0; i < out.n.length; i++) { 1461 | if (out.n[i].text == null) { 1462 | str += '' + out.n[i] + nSpace[i] + ""; 1463 | } 1464 | else { 1465 | var pre = ""; 1466 | 1467 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1468 | pre += '' + out.o[n] + oSpace[n] + ""; 1469 | } 1470 | str += " " + out.n[i].text + nSpace[i] + pre; 1471 | } 1472 | } 1473 | } 1474 | 1475 | return str; 1476 | }; 1477 | })(); 1478 | 1479 | })(this); 1480 | -------------------------------------------------------------------------------- /test/same.js: -------------------------------------------------------------------------------- 1 | module("equiv"); 2 | 3 | 4 | test("Primitive types and constants", function () { 5 | equals(QUnit.equiv(null, null), true, "null"); 6 | equals(QUnit.equiv(null, {}), false, "null"); 7 | equals(QUnit.equiv(null, undefined), false, "null"); 8 | equals(QUnit.equiv(null, 0), false, "null"); 9 | equals(QUnit.equiv(null, false), false, "null"); 10 | equals(QUnit.equiv(null, ''), false, "null"); 11 | equals(QUnit.equiv(null, []), false, "null"); 12 | 13 | equals(QUnit.equiv(undefined, undefined), true, "undefined"); 14 | equals(QUnit.equiv(undefined, null), false, "undefined"); 15 | equals(QUnit.equiv(undefined, 0), false, "undefined"); 16 | equals(QUnit.equiv(undefined, false), false, "undefined"); 17 | equals(QUnit.equiv(undefined, {}), false, "undefined"); 18 | equals(QUnit.equiv(undefined, []), false, "undefined"); 19 | equals(QUnit.equiv(undefined, ""), false, "undefined"); 20 | 21 | // Nan usually doest not equal to Nan using the '==' operator. 22 | // Only isNaN() is able to do it. 23 | equals(QUnit.equiv(0/0, 0/0), true, "NaN"); // NaN VS NaN 24 | equals(QUnit.equiv(1/0, 2/0), true, "Infinity"); // Infinity VS Infinity 25 | equals(QUnit.equiv(-1/0, 2/0), false, "-Infinity, Infinity"); // -Infinity VS Infinity 26 | equals(QUnit.equiv(-1/0, -2/0), true, "-Infinity, -Infinity"); // -Infinity VS -Infinity 27 | equals(QUnit.equiv(0/0, 1/0), false, "NaN, Infinity"); // Nan VS Infinity 28 | equals(QUnit.equiv(1/0, 0/0), false, "NaN, Infinity"); // Nan VS Infinity 29 | equals(QUnit.equiv(0/0, null), false, "NaN"); 30 | equals(QUnit.equiv(0/0, undefined), false, "NaN"); 31 | equals(QUnit.equiv(0/0, 0), false, "NaN"); 32 | equals(QUnit.equiv(0/0, false), false, "NaN"); 33 | equals(QUnit.equiv(0/0, function () {}), false, "NaN"); 34 | equals(QUnit.equiv(1/0, null), false, "NaN, Infinity"); 35 | equals(QUnit.equiv(1/0, undefined), false, "NaN, Infinity"); 36 | equals(QUnit.equiv(1/0, 0), false, "NaN, Infinity"); 37 | equals(QUnit.equiv(1/0, 1), false, "NaN, Infinity"); 38 | equals(QUnit.equiv(1/0, false), false, "NaN, Infinity"); 39 | equals(QUnit.equiv(1/0, true), false, "NaN, Infinity"); 40 | equals(QUnit.equiv(1/0, function () {}), false, "NaN, Infinity"); 41 | 42 | equals(QUnit.equiv(0, 0), true, "number"); 43 | equals(QUnit.equiv(0, 1), false, "number"); 44 | equals(QUnit.equiv(1, 0), false, "number"); 45 | equals(QUnit.equiv(1, 1), true, "number"); 46 | equals(QUnit.equiv(1.1, 1.1), true, "number"); 47 | equals(QUnit.equiv(0.0000005, 0.0000005), true, "number"); 48 | equals(QUnit.equiv(0, ''), false, "number"); 49 | equals(QUnit.equiv(0, '0'), false, "number"); 50 | equals(QUnit.equiv(1, '1'), false, "number"); 51 | equals(QUnit.equiv(0, false), false, "number"); 52 | equals(QUnit.equiv(1, true), false, "number"); 53 | 54 | equals(QUnit.equiv(true, true), true, "boolean"); 55 | equals(QUnit.equiv(true, false), false, "boolean"); 56 | equals(QUnit.equiv(false, true), false, "boolean"); 57 | equals(QUnit.equiv(false, 0), false, "boolean"); 58 | equals(QUnit.equiv(false, null), false, "boolean"); 59 | equals(QUnit.equiv(false, undefined), false, "boolean"); 60 | equals(QUnit.equiv(true, 1), false, "boolean"); 61 | equals(QUnit.equiv(true, null), false, "boolean"); 62 | equals(QUnit.equiv(true, undefined), false, "boolean"); 63 | 64 | equals(QUnit.equiv('', ''), true, "string"); 65 | equals(QUnit.equiv('a', 'a'), true, "string"); 66 | equals(QUnit.equiv("foobar", "foobar"), true, "string"); 67 | equals(QUnit.equiv("foobar", "foo"), false, "string"); 68 | equals(QUnit.equiv('', 0), false, "string"); 69 | equals(QUnit.equiv('', false), false, "string"); 70 | equals(QUnit.equiv('', null), false, "string"); 71 | equals(QUnit.equiv('', undefined), false, "string"); 72 | 73 | // Short annotation VS new annotation 74 | equals(QUnit.equiv(0, new Number()), true, "short annotation VS new annotation"); 75 | equals(QUnit.equiv(new Number(), 0), true, "short annotation VS new annotation"); 76 | equals(QUnit.equiv(1, new Number(1)), true, "short annotation VS new annotation"); 77 | equals(QUnit.equiv(new Number(1), 1), true, "short annotation VS new annotation"); 78 | equals(QUnit.equiv(new Number(0), 1), false, "short annotation VS new annotation"); 79 | equals(QUnit.equiv(0, new Number(1)), false, "short annotation VS new annotation"); 80 | 81 | equals(QUnit.equiv(new String(), ""), true, "short annotation VS new annotation"); 82 | equals(QUnit.equiv("", new String()), true, "short annotation VS new annotation"); 83 | equals(QUnit.equiv(new String("My String"), "My String"), true, "short annotation VS new annotation"); 84 | equals(QUnit.equiv("My String", new String("My String")), true, "short annotation VS new annotation"); 85 | equals(QUnit.equiv("Bad String", new String("My String")), false, "short annotation VS new annotation"); 86 | equals(QUnit.equiv(new String("Bad String"), "My String"), false, "short annotation VS new annotation"); 87 | 88 | equals(QUnit.equiv(false, new Boolean()), true, "short annotation VS new annotation"); 89 | equals(QUnit.equiv(new Boolean(), false), true, "short annotation VS new annotation"); 90 | equals(QUnit.equiv(true, new Boolean(true)), true, "short annotation VS new annotation"); 91 | equals(QUnit.equiv(new Boolean(true), true), true, "short annotation VS new annotation"); 92 | equals(QUnit.equiv(true, new Boolean(1)), true, "short annotation VS new annotation"); 93 | equals(QUnit.equiv(false, new Boolean(false)), true, "short annotation VS new annotation"); 94 | equals(QUnit.equiv(new Boolean(false), false), true, "short annotation VS new annotation"); 95 | equals(QUnit.equiv(false, new Boolean(0)), true, "short annotation VS new annotation"); 96 | equals(QUnit.equiv(true, new Boolean(false)), false, "short annotation VS new annotation"); 97 | equals(QUnit.equiv(new Boolean(false), true), false, "short annotation VS new annotation"); 98 | 99 | equals(QUnit.equiv(new Object(), {}), true, "short annotation VS new annotation"); 100 | equals(QUnit.equiv({}, new Object()), true, "short annotation VS new annotation"); 101 | equals(QUnit.equiv(new Object(), {a:1}), false, "short annotation VS new annotation"); 102 | equals(QUnit.equiv({a:1}, new Object()), false, "short annotation VS new annotation"); 103 | equals(QUnit.equiv({a:undefined}, new Object()), false, "short annotation VS new annotation"); 104 | equals(QUnit.equiv(new Object(), {a:undefined}), false, "short annotation VS new annotation"); 105 | }); 106 | 107 | test("Objects Basics.", function() { 108 | equals(QUnit.equiv({}, {}), true); 109 | equals(QUnit.equiv({}, null), false); 110 | equals(QUnit.equiv({}, undefined), false); 111 | equals(QUnit.equiv({}, 0), false); 112 | equals(QUnit.equiv({}, false), false); 113 | 114 | // This test is a hard one, it is very important 115 | // REASONS: 116 | // 1) They are of the same type "object" 117 | // 2) [] instanceof Object is true 118 | // 3) Their properties are the same (doesn't exists) 119 | equals(QUnit.equiv({}, []), false); 120 | 121 | equals(QUnit.equiv({a:1}, {a:1}), true); 122 | equals(QUnit.equiv({a:1}, {a:"1"}), false); 123 | equals(QUnit.equiv({a:[]}, {a:[]}), true); 124 | equals(QUnit.equiv({a:{}}, {a:null}), false); 125 | equals(QUnit.equiv({a:1}, {}), false); 126 | equals(QUnit.equiv({}, {a:1}), false); 127 | 128 | // Hard ones 129 | equals(QUnit.equiv({a:undefined}, {}), false); 130 | equals(QUnit.equiv({}, {a:undefined}), false); 131 | equals(QUnit.equiv( 132 | { 133 | a: [{ bar: undefined }] 134 | }, 135 | { 136 | a: [{ bat: undefined }] 137 | } 138 | ), false); 139 | }); 140 | 141 | 142 | test("Arrays Basics.", function() { 143 | 144 | equals(QUnit.equiv([], []), true); 145 | 146 | // May be a hard one, can invoke a crash at execution. 147 | // because their types are both "object" but null isn't 148 | // like a true object, it doesn't have any property at all. 149 | equals(QUnit.equiv([], null), false); 150 | 151 | equals(QUnit.equiv([], undefined), false); 152 | equals(QUnit.equiv([], false), false); 153 | equals(QUnit.equiv([], 0), false); 154 | equals(QUnit.equiv([], ""), false); 155 | 156 | // May be a hard one, but less hard 157 | // than {} with [] (note the order) 158 | equals(QUnit.equiv([], {}), false); 159 | 160 | equals(QUnit.equiv([null],[]), false); 161 | equals(QUnit.equiv([undefined],[]), false); 162 | equals(QUnit.equiv([],[null]), false); 163 | equals(QUnit.equiv([],[undefined]), false); 164 | equals(QUnit.equiv([null],[undefined]), false); 165 | equals(QUnit.equiv([[]],[[]]), true); 166 | equals(QUnit.equiv([[],[],[]],[[],[],[]]), true); 167 | equals(QUnit.equiv( 168 | [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], 169 | [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), 170 | true); 171 | equals(QUnit.equiv( 172 | [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], 173 | [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // shorter 174 | false); 175 | equals(QUnit.equiv( 176 | [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], 177 | [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // deepest element not an array 178 | false); 179 | 180 | // same multidimensional 181 | equals(QUnit.equiv( 182 | [1,2,3,4,5,6,7,8,9, [ 183 | 1,2,3,4,5,6,7,8,9, [ 184 | 1,2,3,4,5,[ 185 | [6,7,8,9, [ 186 | [ 187 | 1,2,3,4,[ 188 | 2,3,4,[ 189 | 1,2,[ 190 | 1,2,3,4,[ 191 | 1,2,3,4,5,6,7,8,9,[ 192 | 0 193 | ],1,2,3,4,5,6,7,8,9 194 | ],5,6,7,8,9 195 | ],4,5,6,7,8,9 196 | ],5,6,7,8,9 197 | ],5,6,7 198 | ] 199 | ] 200 | ] 201 | ] 202 | ]]], 203 | [1,2,3,4,5,6,7,8,9, [ 204 | 1,2,3,4,5,6,7,8,9, [ 205 | 1,2,3,4,5,[ 206 | [6,7,8,9, [ 207 | [ 208 | 1,2,3,4,[ 209 | 2,3,4,[ 210 | 1,2,[ 211 | 1,2,3,4,[ 212 | 1,2,3,4,5,6,7,8,9,[ 213 | 0 214 | ],1,2,3,4,5,6,7,8,9 215 | ],5,6,7,8,9 216 | ],4,5,6,7,8,9 217 | ],5,6,7,8,9 218 | ],5,6,7 219 | ] 220 | ] 221 | ] 222 | ] 223 | ]]]), 224 | true, "Multidimensional"); 225 | 226 | // different multidimensional 227 | equals(QUnit.equiv( 228 | [1,2,3,4,5,6,7,8,9, [ 229 | 1,2,3,4,5,6,7,8,9, [ 230 | 1,2,3,4,5,[ 231 | [6,7,8,9, [ 232 | [ 233 | 1,2,3,4,[ 234 | 2,3,4,[ 235 | 1,2,[ 236 | 1,2,3,4,[ 237 | 1,2,3,4,5,6,7,8,9,[ 238 | 0 239 | ],1,2,3,4,5,6,7,8,9 240 | ],5,6,7,8,9 241 | ],4,5,6,7,8,9 242 | ],5,6,7,8,9 243 | ],5,6,7 244 | ] 245 | ] 246 | ] 247 | ] 248 | ]]], 249 | [1,2,3,4,5,6,7,8,9, [ 250 | 1,2,3,4,5,6,7,8,9, [ 251 | 1,2,3,4,5,[ 252 | [6,7,8,9, [ 253 | [ 254 | 1,2,3,4,[ 255 | 2,3,4,[ 256 | 1,2,[ 257 | '1',2,3,4,[ // string instead of number 258 | 1,2,3,4,5,6,7,8,9,[ 259 | 0 260 | ],1,2,3,4,5,6,7,8,9 261 | ],5,6,7,8,9 262 | ],4,5,6,7,8,9 263 | ],5,6,7,8,9 264 | ],5,6,7 265 | ] 266 | ] 267 | ] 268 | ] 269 | ]]]), 270 | false, "Multidimensional"); 271 | 272 | // different multidimensional 273 | equals(QUnit.equiv( 274 | [1,2,3,4,5,6,7,8,9, [ 275 | 1,2,3,4,5,6,7,8,9, [ 276 | 1,2,3,4,5,[ 277 | [6,7,8,9, [ 278 | [ 279 | 1,2,3,4,[ 280 | 2,3,4,[ 281 | 1,2,[ 282 | 1,2,3,4,[ 283 | 1,2,3,4,5,6,7,8,9,[ 284 | 0 285 | ],1,2,3,4,5,6,7,8,9 286 | ],5,6,7,8,9 287 | ],4,5,6,7,8,9 288 | ],5,6,7,8,9 289 | ],5,6,7 290 | ] 291 | ] 292 | ] 293 | ] 294 | ]]], 295 | [1,2,3,4,5,6,7,8,9, [ 296 | 1,2,3,4,5,6,7,8,9, [ 297 | 1,2,3,4,5,[ 298 | [6,7,8,9, [ 299 | [ 300 | 1,2,3,4,[ 301 | 2,3,[ // missing an element (4) 302 | 1,2,[ 303 | 1,2,3,4,[ 304 | 1,2,3,4,5,6,7,8,9,[ 305 | 0 306 | ],1,2,3,4,5,6,7,8,9 307 | ],5,6,7,8,9 308 | ],4,5,6,7,8,9 309 | ],5,6,7,8,9 310 | ],5,6,7 311 | ] 312 | ] 313 | ] 314 | ] 315 | ]]]), 316 | false, "Multidimensional"); 317 | }); 318 | 319 | test("Functions.", function() { 320 | var f0 = function () {}; 321 | var f1 = function () {}; 322 | 323 | // f2 and f3 have the same code, formatted differently 324 | var f2 = function () {var i = 0;}; 325 | var f3 = function () { 326 | var i = 0 // this comment and no semicoma as difference 327 | }; 328 | 329 | equals(QUnit.equiv(function() {}, function() {}), false, "Anonymous functions"); // exact source code 330 | equals(QUnit.equiv(function() {}, function() {return true;}), false, "Anonymous functions"); 331 | 332 | equals(QUnit.equiv(f0, f0), true, "Function references"); // same references 333 | equals(QUnit.equiv(f0, f1), false, "Function references"); // exact source code, different references 334 | equals(QUnit.equiv(f2, f3), false, "Function references"); // equivalent source code, different references 335 | equals(QUnit.equiv(f1, f2), false, "Function references"); // different source code, different references 336 | equals(QUnit.equiv(function() {}, true), false); 337 | equals(QUnit.equiv(function() {}, undefined), false); 338 | equals(QUnit.equiv(function() {}, null), false); 339 | equals(QUnit.equiv(function() {}, {}), false); 340 | }); 341 | 342 | 343 | test("Date instances.", function() { 344 | // Date, we don't need to test Date.parse() because it returns a number. 345 | // Only test the Date instances by setting them a fix date. 346 | // The date use is midnight January 1, 1970 347 | 348 | var d1 = new Date(); 349 | d1.setTime(0); // fix the date 350 | 351 | var d2 = new Date(); 352 | d2.setTime(0); // fix the date 353 | 354 | var d3 = new Date(); // The very now 355 | 356 | // Anyway their types differs, just in case the code fails in the order in which it deals with date 357 | equals(QUnit.equiv(d1, 0), false); // d1.valueOf() returns 0, but d1 and 0 are different 358 | // test same values date and different instances equality 359 | equals(QUnit.equiv(d1, d2), true); 360 | // test different date and different instances difference 361 | equals(QUnit.equiv(d1, d3), false); 362 | }); 363 | 364 | 365 | test("RegExp.", function() { 366 | // Must test cases that imply those traps: 367 | // var a = /./; 368 | // a instanceof Object; // Oops 369 | // a instanceof RegExp; // Oops 370 | // typeof a === "function"; // Oops, false in IE and Opera, true in FF and Safari ("object") 371 | 372 | // Tests same regex with same modifiers in different order 373 | var r = /foo/; 374 | var r5 = /foo/gim; 375 | var r6 = /foo/gmi; 376 | var r7 = /foo/igm; 377 | var r8 = /foo/img; 378 | var r9 = /foo/mig; 379 | var r10 = /foo/mgi; 380 | var ri1 = /foo/i; 381 | var ri2 = /foo/i; 382 | var rm1 = /foo/m; 383 | var rm2 = /foo/m; 384 | var rg1 = /foo/g; 385 | var rg2 = /foo/g; 386 | 387 | equals(QUnit.equiv(r5, r6), true, "Modifier order"); 388 | equals(QUnit.equiv(r5, r7), true, "Modifier order"); 389 | equals(QUnit.equiv(r5, r8), true, "Modifier order"); 390 | equals(QUnit.equiv(r5, r9), true, "Modifier order"); 391 | equals(QUnit.equiv(r5, r10), true, "Modifier order"); 392 | equals(QUnit.equiv(r, r5), false, "Modifier"); 393 | 394 | equals(QUnit.equiv(ri1, ri2), true, "Modifier"); 395 | equals(QUnit.equiv(r, ri1), false, "Modifier"); 396 | equals(QUnit.equiv(ri1, rm1), false, "Modifier"); 397 | equals(QUnit.equiv(r, rm1), false, "Modifier"); 398 | equals(QUnit.equiv(rm1, ri1), false, "Modifier"); 399 | equals(QUnit.equiv(rm1, rm2), true, "Modifier"); 400 | equals(QUnit.equiv(rg1, rm1), false, "Modifier"); 401 | equals(QUnit.equiv(rm1, rg1), false, "Modifier"); 402 | equals(QUnit.equiv(rg1, rg2), true, "Modifier"); 403 | 404 | // Different regex, same modifiers 405 | var r11 = /[a-z]/gi; 406 | var r13 = /[0-9]/gi; // oops! different 407 | equals(QUnit.equiv(r11, r13), false, "Regex pattern"); 408 | 409 | var r14 = /0/ig; 410 | var r15 = /"0"/ig; // oops! different 411 | equals(QUnit.equiv(r14, r15), false, "Regex pattern"); 412 | 413 | var r1 = /[\n\r\u2028\u2029]/g; 414 | var r2 = /[\n\r\u2028\u2029]/g; 415 | var r3 = /[\n\r\u2028\u2028]/g; // differs from r1 416 | var r4 = /[\n\r\u2028\u2029]/; // differs from r1 417 | 418 | equals(QUnit.equiv(r1, r2), true, "Regex pattern"); 419 | equals(QUnit.equiv(r1, r3), false, "Regex pattern"); 420 | equals(QUnit.equiv(r1, r4), false, "Regex pattern"); 421 | 422 | // More complex regex 423 | var regex1 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; 424 | var regex2 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; 425 | // regex 3 is different: '.' not escaped 426 | var regex3 = "^[-_.a-z0-9]+@([-_a-z0-9]+.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; 427 | 428 | var r21 = new RegExp(regex1); 429 | var r22 = new RegExp(regex2); 430 | var r23 = new RegExp(regex3); // diff from r21, not same pattern 431 | var r23a = new RegExp(regex3, "gi"); // diff from r23, not same modifier 432 | var r24a = new RegExp(regex3, "ig"); // same as r23a 433 | 434 | equals(QUnit.equiv(r21, r22), true, "Complex Regex"); 435 | equals(QUnit.equiv(r21, r23), false, "Complex Regex"); 436 | equals(QUnit.equiv(r23, r23a), false, "Complex Regex"); 437 | equals(QUnit.equiv(r23a, r24a), true, "Complex Regex"); 438 | 439 | // typeof r1 is "function" in some browsers and "object" in others so we must cover this test 440 | var re = / /; 441 | equals(QUnit.equiv(re, function () {}), false, "Regex internal"); 442 | equals(QUnit.equiv(re, {}), false, "Regex internal"); 443 | }); 444 | 445 | 446 | test("Complex Objects.", function() { 447 | 448 | function fn1() { 449 | return "fn1"; 450 | } 451 | function fn2() { 452 | return "fn2"; 453 | } 454 | 455 | // Try to invert the order of some properties to make sure it is covered. 456 | // It can failed when properties are compared between unsorted arrays. 457 | equals(QUnit.equiv( 458 | { 459 | a: 1, 460 | b: null, 461 | c: [{}], 462 | d: { 463 | a: 3.14159, 464 | b: false, 465 | c: { 466 | e: fn1, 467 | f: [[[]]], 468 | g: { 469 | j: { 470 | k: { 471 | n: { 472 | r: "r", 473 | s: [1,2,3], 474 | t: undefined, 475 | u: 0, 476 | v: { 477 | w: { 478 | x: { 479 | y: "Yahoo!", 480 | z: null 481 | } 482 | } 483 | } 484 | }, 485 | q: [], 486 | p: 1/0, 487 | o: 99 488 | }, 489 | l: undefined, 490 | m: null 491 | } 492 | }, 493 | d: 0, 494 | i: true, 495 | h: "false" 496 | } 497 | }, 498 | e: undefined, 499 | g: "", 500 | h: "h", 501 | f: {}, 502 | i: [] 503 | }, 504 | { 505 | a: 1, 506 | b: null, 507 | c: [{}], 508 | d: { 509 | b: false, 510 | a: 3.14159, 511 | c: { 512 | d: 0, 513 | e: fn1, 514 | f: [[[]]], 515 | g: { 516 | j: { 517 | k: { 518 | n: { 519 | r: "r", 520 | t: undefined, 521 | u: 0, 522 | s: [1,2,3], 523 | v: { 524 | w: { 525 | x: { 526 | z: null, 527 | y: "Yahoo!" 528 | } 529 | } 530 | } 531 | }, 532 | o: 99, 533 | p: 1/0, 534 | q: [] 535 | }, 536 | l: undefined, 537 | m: null 538 | } 539 | }, 540 | i: true, 541 | h: "false" 542 | } 543 | }, 544 | e: undefined, 545 | g: "", 546 | f: {}, 547 | h: "h", 548 | i: [] 549 | } 550 | ), true); 551 | 552 | equals(QUnit.equiv( 553 | { 554 | a: 1, 555 | b: null, 556 | c: [{}], 557 | d: { 558 | a: 3.14159, 559 | b: false, 560 | c: { 561 | d: 0, 562 | e: fn1, 563 | f: [[[]]], 564 | g: { 565 | j: { 566 | k: { 567 | n: { 568 | //r: "r", // different: missing a property 569 | s: [1,2,3], 570 | t: undefined, 571 | u: 0, 572 | v: { 573 | w: { 574 | x: { 575 | y: "Yahoo!", 576 | z: null 577 | } 578 | } 579 | } 580 | }, 581 | o: 99, 582 | p: 1/0, 583 | q: [] 584 | }, 585 | l: undefined, 586 | m: null 587 | } 588 | }, 589 | h: "false", 590 | i: true 591 | } 592 | }, 593 | e: undefined, 594 | f: {}, 595 | g: "", 596 | h: "h", 597 | i: [] 598 | }, 599 | { 600 | a: 1, 601 | b: null, 602 | c: [{}], 603 | d: { 604 | a: 3.14159, 605 | b: false, 606 | c: { 607 | d: 0, 608 | e: fn1, 609 | f: [[[]]], 610 | g: { 611 | j: { 612 | k: { 613 | n: { 614 | r: "r", 615 | s: [1,2,3], 616 | t: undefined, 617 | u: 0, 618 | v: { 619 | w: { 620 | x: { 621 | y: "Yahoo!", 622 | z: null 623 | } 624 | } 625 | } 626 | }, 627 | o: 99, 628 | p: 1/0, 629 | q: [] 630 | }, 631 | l: undefined, 632 | m: null 633 | } 634 | }, 635 | h: "false", 636 | i: true 637 | } 638 | }, 639 | e: undefined, 640 | f: {}, 641 | g: "", 642 | h: "h", 643 | i: [] 644 | } 645 | ), false); 646 | 647 | equals(QUnit.equiv( 648 | { 649 | a: 1, 650 | b: null, 651 | c: [{}], 652 | d: { 653 | a: 3.14159, 654 | b: false, 655 | c: { 656 | d: 0, 657 | e: fn1, 658 | f: [[[]]], 659 | g: { 660 | j: { 661 | k: { 662 | n: { 663 | r: "r", 664 | s: [1,2,3], 665 | t: undefined, 666 | u: 0, 667 | v: { 668 | w: { 669 | x: { 670 | y: "Yahoo!", 671 | z: null 672 | } 673 | } 674 | } 675 | }, 676 | o: 99, 677 | p: 1/0, 678 | q: [] 679 | }, 680 | l: undefined, 681 | m: null 682 | } 683 | }, 684 | h: "false", 685 | i: true 686 | } 687 | }, 688 | e: undefined, 689 | f: {}, 690 | g: "", 691 | h: "h", 692 | i: [] 693 | }, 694 | { 695 | a: 1, 696 | b: null, 697 | c: [{}], 698 | d: { 699 | a: 3.14159, 700 | b: false, 701 | c: { 702 | d: 0, 703 | e: fn1, 704 | f: [[[]]], 705 | g: { 706 | j: { 707 | k: { 708 | n: { 709 | r: "r", 710 | s: [1,2,3], 711 | //t: undefined, // different: missing a property with an undefined value 712 | u: 0, 713 | v: { 714 | w: { 715 | x: { 716 | y: "Yahoo!", 717 | z: null 718 | } 719 | } 720 | } 721 | }, 722 | o: 99, 723 | p: 1/0, 724 | q: [] 725 | }, 726 | l: undefined, 727 | m: null 728 | } 729 | }, 730 | h: "false", 731 | i: true 732 | } 733 | }, 734 | e: undefined, 735 | f: {}, 736 | g: "", 737 | h: "h", 738 | i: [] 739 | } 740 | ), false); 741 | 742 | equals(QUnit.equiv( 743 | { 744 | a: 1, 745 | b: null, 746 | c: [{}], 747 | d: { 748 | a: 3.14159, 749 | b: false, 750 | c: { 751 | d: 0, 752 | e: fn1, 753 | f: [[[]]], 754 | g: { 755 | j: { 756 | k: { 757 | n: { 758 | r: "r", 759 | s: [1,2,3], 760 | t: undefined, 761 | u: 0, 762 | v: { 763 | w: { 764 | x: { 765 | y: "Yahoo!", 766 | z: null 767 | } 768 | } 769 | } 770 | }, 771 | o: 99, 772 | p: 1/0, 773 | q: [] 774 | }, 775 | l: undefined, 776 | m: null 777 | } 778 | }, 779 | h: "false", 780 | i: true 781 | } 782 | }, 783 | e: undefined, 784 | f: {}, 785 | g: "", 786 | h: "h", 787 | i: [] 788 | }, 789 | { 790 | a: 1, 791 | b: null, 792 | c: [{}], 793 | d: { 794 | a: 3.14159, 795 | b: false, 796 | c: { 797 | d: 0, 798 | e: fn1, 799 | f: [[[]]], 800 | g: { 801 | j: { 802 | k: { 803 | n: { 804 | r: "r", 805 | s: [1,2,3], 806 | t: undefined, 807 | u: 0, 808 | v: { 809 | w: { 810 | x: { 811 | y: "Yahoo!", 812 | z: null 813 | } 814 | } 815 | } 816 | }, 817 | o: 99, 818 | p: 1/0, 819 | q: {} // different was [] 820 | }, 821 | l: undefined, 822 | m: null 823 | } 824 | }, 825 | h: "false", 826 | i: true 827 | } 828 | }, 829 | e: undefined, 830 | f: {}, 831 | g: "", 832 | h: "h", 833 | i: [] 834 | } 835 | ), false); 836 | 837 | var same1 = { 838 | a: [ 839 | "string", null, 0, "1", 1, { 840 | prop: null, 841 | foo: [1,2,null,{}, [], [1,2,3]], 842 | bar: undefined 843 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας" 844 | ], 845 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 846 | b: "b", 847 | c: fn1 848 | }; 849 | 850 | var same2 = { 851 | a: [ 852 | "string", null, 0, "1", 1, { 853 | prop: null, 854 | foo: [1,2,null,{}, [], [1,2,3]], 855 | bar: undefined 856 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας" 857 | ], 858 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 859 | b: "b", 860 | c: fn1 861 | }; 862 | 863 | var diff1 = { 864 | a: [ 865 | "string", null, 0, "1", 1, { 866 | prop: null, 867 | foo: [1,2,null,{}, [], [1,2,3,4]], // different: 4 was add to the array 868 | bar: undefined 869 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας" 870 | ], 871 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 872 | b: "b", 873 | c: fn1 874 | }; 875 | 876 | var diff2 = { 877 | a: [ 878 | "string", null, 0, "1", 1, { 879 | prop: null, 880 | foo: [1,2,null,{}, [], [1,2,3]], 881 | newprop: undefined, // different: newprop was added 882 | bar: undefined 883 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας" 884 | ], 885 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 886 | b: "b", 887 | c: fn1 888 | }; 889 | 890 | var diff3 = { 891 | a: [ 892 | "string", null, 0, "1", 1, { 893 | prop: null, 894 | foo: [1,2,null,{}, [], [1,2,3]], 895 | bar: undefined 896 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ α" // different: missing last char 897 | ], 898 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 899 | b: "b", 900 | c: fn1 901 | }; 902 | 903 | var diff4 = { 904 | a: [ 905 | "string", null, 0, "1", 1, { 906 | prop: null, 907 | foo: [1,2,undefined,{}, [], [1,2,3]], // different: undefined instead of null 908 | bar: undefined 909 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας" 910 | ], 911 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 912 | b: "b", 913 | c: fn1 914 | }; 915 | 916 | var diff5 = { 917 | a: [ 918 | "string", null, 0, "1", 1, { 919 | prop: null, 920 | foo: [1,2,null,{}, [], [1,2,3]], 921 | bat: undefined // different: property name not "bar" 922 | }, 3, "Hey!", "Κάνε πάντα γνωρίζουμε ας των, μηχανής επιδιόρθωσης επιδιορθώσεις ώς μια. Κλπ ας" 923 | ], 924 | unicode: "老 汉语中存在 港澳和海外的华人圈中 贵州 我去了书店 现在尚有争", 925 | b: "b", 926 | c: fn1 927 | }; 928 | 929 | equals(QUnit.equiv(same1, same2), true); 930 | equals(QUnit.equiv(same2, same1), true); 931 | equals(QUnit.equiv(same2, diff1), false); 932 | equals(QUnit.equiv(diff1, same2), false); 933 | 934 | equals(QUnit.equiv(same1, diff1), false); 935 | equals(QUnit.equiv(same1, diff2), false); 936 | equals(QUnit.equiv(same1, diff3), false); 937 | equals(QUnit.equiv(same1, diff3), false); 938 | equals(QUnit.equiv(same1, diff4), false); 939 | equals(QUnit.equiv(same1, diff5), false); 940 | equals(QUnit.equiv(diff5, diff1), false); 941 | }); 942 | 943 | 944 | test("Complex Arrays.", function() { 945 | 946 | function fn() { 947 | } 948 | 949 | equals(QUnit.equiv( 950 | [1, 2, 3, true, {}, null, [ 951 | { 952 | a: ["", '1', 0] 953 | }, 954 | 5, 6, 7 955 | ], "foo"], 956 | [1, 2, 3, true, {}, null, [ 957 | { 958 | a: ["", '1', 0] 959 | }, 960 | 5, 6, 7 961 | ], "foo"]), 962 | true); 963 | 964 | equals(QUnit.equiv( 965 | [1, 2, 3, true, {}, null, [ 966 | { 967 | a: ["", '1', 0] 968 | }, 969 | 5, 6, 7 970 | ], "foo"], 971 | [1, 2, 3, true, {}, null, [ 972 | { 973 | b: ["", '1', 0] // not same property name 974 | }, 975 | 5, 6, 7 976 | ], "foo"]), 977 | false); 978 | 979 | var a = [{ 980 | b: fn, 981 | c: false, 982 | "do": "reserved word", 983 | "for": { 984 | ar: [3,5,9,"hey!", [], { 985 | ar: [1,[ 986 | 3,4,6,9, null, [], [] 987 | ]], 988 | e: fn, 989 | f: undefined 990 | }] 991 | }, 992 | e: 0.43445 993 | }, 5, "string", 0, fn, false, null, undefined, 0, [ 994 | 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 995 | ], [], [[[], "foo", null, { 996 | n: 1/0, 997 | z: { 998 | a: [3,4,5,6,"yep!", undefined, undefined], 999 | b: {} 1000 | } 1001 | }, {}]]]; 1002 | 1003 | equals(QUnit.equiv(a, 1004 | [{ 1005 | b: fn, 1006 | c: false, 1007 | "do": "reserved word", 1008 | "for": { 1009 | ar: [3,5,9,"hey!", [], { 1010 | ar: [1,[ 1011 | 3,4,6,9, null, [], [] 1012 | ]], 1013 | e: fn, 1014 | f: undefined 1015 | }] 1016 | }, 1017 | e: 0.43445 1018 | }, 5, "string", 0, fn, false, null, undefined, 0, [ 1019 | 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 1020 | ], [], [[[], "foo", null, { 1021 | n: 1/0, 1022 | z: { 1023 | a: [3,4,5,6,"yep!", undefined, undefined], 1024 | b: {} 1025 | } 1026 | }, {}]]]), true); 1027 | 1028 | equals(QUnit.equiv(a, 1029 | [{ 1030 | b: fn, 1031 | c: false, 1032 | "do": "reserved word", 1033 | "for": { 1034 | ar: [3,5,9,"hey!", [], { 1035 | ar: [1,[ 1036 | 3,4,6,9, null, [], [] 1037 | ]], 1038 | e: fn, 1039 | f: undefined 1040 | }] 1041 | }, 1042 | e: 0.43445 1043 | }, 5, "string", 0, fn, false, null, undefined, 0, [ 1044 | 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[2]]]], "3"], {}, 1/0 // different: [[[[[2]]]]] instead of [[[[[3]]]]] 1045 | ], [], [[[], "foo", null, { 1046 | n: 1/0, 1047 | z: { 1048 | a: [3,4,5,6,"yep!", undefined, undefined], 1049 | b: {} 1050 | } 1051 | }, {}]]]), false); 1052 | 1053 | equals(QUnit.equiv(a, 1054 | [{ 1055 | b: fn, 1056 | c: false, 1057 | "do": "reserved word", 1058 | "for": { 1059 | ar: [3,5,9,"hey!", [], { 1060 | ar: [1,[ 1061 | 3,4,6,9, null, [], [] 1062 | ]], 1063 | e: fn, 1064 | f: undefined 1065 | }] 1066 | }, 1067 | e: 0.43445 1068 | }, 5, "string", 0, fn, false, null, undefined, 0, [ 1069 | 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 1070 | ], [], [[[], "foo", null, { 1071 | n: -1/0, // different, -Infinity instead of Infinity 1072 | z: { 1073 | a: [3,4,5,6,"yep!", undefined, undefined], 1074 | b: {} 1075 | } 1076 | }, {}]]]), false); 1077 | 1078 | equals(QUnit.equiv(a, 1079 | [{ 1080 | b: fn, 1081 | c: false, 1082 | "do": "reserved word", 1083 | "for": { 1084 | ar: [3,5,9,"hey!", [], { 1085 | ar: [1,[ 1086 | 3,4,6,9, null, [], [] 1087 | ]], 1088 | e: fn, 1089 | f: undefined 1090 | }] 1091 | }, 1092 | e: 0.43445 1093 | }, 5, "string", 0, fn, false, null, undefined, 0, [ 1094 | 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 1095 | ], [], [[[], "foo", { // different: null is missing 1096 | n: 1/0, 1097 | z: { 1098 | a: [3,4,5,6,"yep!", undefined, undefined], 1099 | b: {} 1100 | } 1101 | }, {}]]]), false); 1102 | 1103 | equals(QUnit.equiv(a, 1104 | [{ 1105 | b: fn, 1106 | c: false, 1107 | "do": "reserved word", 1108 | "for": { 1109 | ar: [3,5,9,"hey!", [], { 1110 | ar: [1,[ 1111 | 3,4,6,9, null, [], [] 1112 | ]], 1113 | e: fn 1114 | // different: missing property f: undefined 1115 | }] 1116 | }, 1117 | e: 0.43445 1118 | }, 5, "string", 0, fn, false, null, undefined, 0, [ 1119 | 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 1120 | ], [], [[[], "foo", null, { 1121 | n: 1/0, 1122 | z: { 1123 | a: [3,4,5,6,"yep!", undefined, undefined], 1124 | b: {} 1125 | } 1126 | }, {}]]]), false); 1127 | }); 1128 | 1129 | 1130 | test("Prototypal inheritance", function() { 1131 | function Gizmo(id) { 1132 | this.id = id; 1133 | } 1134 | 1135 | function Hoozit(id) { 1136 | this.id = id; 1137 | } 1138 | Hoozit.prototype = new Gizmo(); 1139 | 1140 | var gizmo = new Gizmo("ok"); 1141 | var hoozit = new Hoozit("ok"); 1142 | 1143 | // Try this test many times after test on instances that hold function 1144 | // to make sure that our code does not mess with last object constructor memoization. 1145 | equals(QUnit.equiv(function () {}, function () {}), false); 1146 | 1147 | // Hoozit inherit from Gizmo 1148 | // hoozit instanceof Hoozit; // true 1149 | // hoozit instanceof Gizmo; // true 1150 | equals(QUnit.equiv(hoozit, gizmo), true); 1151 | 1152 | Gizmo.prototype.bar = true; // not a function just in case we skip them 1153 | 1154 | // Hoozit inherit from Gizmo 1155 | // They are equivalent 1156 | equals(QUnit.equiv(hoozit, gizmo), true); 1157 | 1158 | // Make sure this is still true !important 1159 | // The reason for this is that I forgot to reset the last 1160 | // caller to where it were called from. 1161 | equals(QUnit.equiv(function () {}, function () {}), false); 1162 | 1163 | // Make sure this is still true !important 1164 | equals(QUnit.equiv(hoozit, gizmo), true); 1165 | 1166 | Hoozit.prototype.foo = true; // not a function just in case we skip them 1167 | 1168 | // Gizmo does not inherit from Hoozit 1169 | // gizmo instanceof Gizmo; // true 1170 | // gizmo instanceof Hoozit; // false 1171 | // They are not equivalent 1172 | equals(QUnit.equiv(hoozit, gizmo), false); 1173 | 1174 | // Make sure this is still true !important 1175 | equals(QUnit.equiv(function () {}, function () {}), false); 1176 | }); 1177 | 1178 | 1179 | test("Instances", function() { 1180 | function A() {} 1181 | var a1 = new A(); 1182 | var a2 = new A(); 1183 | 1184 | function B() { 1185 | this.fn = function () {}; 1186 | } 1187 | var b1 = new B(); 1188 | var b2 = new B(); 1189 | 1190 | equals(QUnit.equiv(a1, a2), true, "Same property, same constructor"); 1191 | 1192 | // b1.fn and b2.fn are functions but they are different references 1193 | // But we decided to skip function for instances. 1194 | equals(QUnit.equiv(b1, b2), true, "Same property, same constructor"); 1195 | equals(QUnit.equiv(a1, b1), false, "Same properties but different constructor"); // failed 1196 | 1197 | function Car(year) { 1198 | var privateVar = 0; 1199 | this.year = year; 1200 | this.isOld = function() { 1201 | return year > 10; 1202 | }; 1203 | } 1204 | 1205 | function Human(year) { 1206 | var privateVar = 1; 1207 | this.year = year; 1208 | this.isOld = function() { 1209 | return year > 80; 1210 | }; 1211 | } 1212 | 1213 | var car = new Car(30); 1214 | var carSame = new Car(30); 1215 | var carDiff = new Car(10); 1216 | var human = new Human(30); 1217 | 1218 | var diff = { 1219 | year: 30 1220 | }; 1221 | 1222 | var same = { 1223 | year: 30, 1224 | isOld: function () {} 1225 | }; 1226 | 1227 | equals(QUnit.equiv(car, car), true); 1228 | equals(QUnit.equiv(car, carDiff), false); 1229 | equals(QUnit.equiv(car, carSame), true); 1230 | equals(QUnit.equiv(car, human), false); 1231 | }); 1232 | 1233 | 1234 | test("Complex Instances Nesting (with function value in literals and/or in nested instances)", function() { 1235 | function A(fn) { 1236 | this.a = {}; 1237 | this.fn = fn; 1238 | this.b = {a: []}; 1239 | this.o = {}; 1240 | this.fn1 = fn; 1241 | } 1242 | function B(fn) { 1243 | this.fn = fn; 1244 | this.fn1 = function () {}; 1245 | this.a = new A(function () {}); 1246 | } 1247 | 1248 | function fnOutside() { 1249 | } 1250 | 1251 | function C(fn) { 1252 | function fnInside() { 1253 | } 1254 | this.x = 10; 1255 | this.fn = fn; 1256 | this.fn1 = function () {}; 1257 | this.fn2 = fnInside; 1258 | this.fn3 = { 1259 | a: true, 1260 | b: fnOutside // ok make reference to a function in all instances scope 1261 | }; 1262 | this.o1 = {}; 1263 | 1264 | // This function will be ignored. 1265 | // Even if it is not visible for all instances (e.g. locked in a closures), 1266 | // it is from a property that makes part of an instance (e.g. from the C constructor) 1267 | this.b1 = new B(function () {}); 1268 | this.b2 = new B({ 1269 | x: { 1270 | b2: new B(function() {}) 1271 | } 1272 | }); 1273 | } 1274 | 1275 | function D(fn) { 1276 | function fnInside() { 1277 | } 1278 | this.x = 10; 1279 | this.fn = fn; 1280 | this.fn1 = function () {}; 1281 | this.fn2 = fnInside; 1282 | this.fn3 = { 1283 | a: true, 1284 | b: fnOutside, // ok make reference to a function in all instances scope 1285 | 1286 | // This function won't be ingored. 1287 | // It isn't visible for all C insances 1288 | // and it is not in a property of an instance. (in an Object instances e.g. the object literal) 1289 | c: fnInside 1290 | }; 1291 | this.o1 = {}; 1292 | 1293 | // This function will be ignored. 1294 | // Even if it is not visible for all instances (e.g. locked in a closures), 1295 | // it is from a property that makes part of an instance (e.g. from the C constructor) 1296 | this.b1 = new B(function () {}); 1297 | this.b2 = new B({ 1298 | x: { 1299 | b2: new B(function() {}) 1300 | } 1301 | }); 1302 | } 1303 | 1304 | function E(fn) { 1305 | function fnInside() { 1306 | } 1307 | this.x = 10; 1308 | this.fn = fn; 1309 | this.fn1 = function () {}; 1310 | this.fn2 = fnInside; 1311 | this.fn3 = { 1312 | a: true, 1313 | b: fnOutside // ok make reference to a function in all instances scope 1314 | }; 1315 | this.o1 = {}; 1316 | 1317 | // This function will be ignored. 1318 | // Even if it is not visible for all instances (e.g. locked in a closures), 1319 | // it is from a property that makes part of an instance (e.g. from the C constructor) 1320 | this.b1 = new B(function () {}); 1321 | this.b2 = new B({ 1322 | x: { 1323 | b1: new B({a: function() {}}), 1324 | b2: new B(function() {}) 1325 | } 1326 | }); 1327 | } 1328 | 1329 | 1330 | var a1 = new A(function () {}); 1331 | var a2 = new A(function () {}); 1332 | equals(QUnit.equiv(a1, a2), true); 1333 | 1334 | equals(QUnit.equiv(a1, a2), true); // different instances 1335 | 1336 | var b1 = new B(function () {}); 1337 | var b2 = new B(function () {}); 1338 | equals(QUnit.equiv(b1, b2), true); 1339 | 1340 | var c1 = new C(function () {}); 1341 | var c2 = new C(function () {}); 1342 | equals(QUnit.equiv(c1, c2), true); 1343 | 1344 | var d1 = new D(function () {}); 1345 | var d2 = new D(function () {}); 1346 | equals(QUnit.equiv(d1, d2), false); 1347 | 1348 | var e1 = new E(function () {}); 1349 | var e2 = new E(function () {}); 1350 | equals(QUnit.equiv(e1, e2), false); 1351 | 1352 | }); 1353 | 1354 | 1355 | test('object with references to self wont loop', function(){ 1356 | var circularA = { 1357 | abc:null 1358 | }, circularB = { 1359 | abc:null 1360 | }; 1361 | circularA.abc = circularA; 1362 | circularB.abc = circularB; 1363 | equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambigous test)"); 1364 | 1365 | circularA.def = 1; 1366 | circularB.def = 1; 1367 | equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambigous test)"); 1368 | 1369 | circularA.def = 1; 1370 | circularB.def = 0; 1371 | equals(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object (unambigous test)"); 1372 | }); 1373 | 1374 | test('array with references to self wont loop', function(){ 1375 | var circularA = [], 1376 | circularB = []; 1377 | circularA.push(circularA); 1378 | circularB.push(circularB); 1379 | equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambigous test)"); 1380 | 1381 | circularA.push( 'abc' ); 1382 | circularB.push( 'abc' ); 1383 | equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambigous test)"); 1384 | 1385 | circularA.push( 'hello' ); 1386 | circularB.push( 'goodbye' ); 1387 | equals(QUnit.equiv(circularA, circularB), false, "Should not repeat test on array (unambigous test)"); 1388 | }); 1389 | 1390 | test('mixed object/array with references to self wont loop', function(){ 1391 | var circularA = [{abc:null}], 1392 | circularB = [{abc:null}]; 1393 | circularA[0].abc = circularA; 1394 | circularB[0].abc = circularB; 1395 | 1396 | circularA.push(circularA); 1397 | circularB.push(circularB); 1398 | equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambigous test)"); 1399 | 1400 | circularA[0].def = 1; 1401 | circularB[0].def = 1; 1402 | equals(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambigous test)"); 1403 | 1404 | circularA[0].def = 1; 1405 | circularB[0].def = 0; 1406 | equals(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object/array (unambigous test)"); 1407 | }); 1408 | 1409 | test("Test that must be done at the end because they extend some primitive's prototype", function() { 1410 | // Try that a function looks like our regular expression. 1411 | // This tests if we check that a and b are really both instance of RegExp 1412 | Function.prototype.global = true; 1413 | Function.prototype.multiline = true; 1414 | Function.prototype.ignoreCase = false; 1415 | Function.prototype.source = "my regex"; 1416 | var re = /my regex/gm; 1417 | equals(QUnit.equiv(re, function () {}), false, "A function that looks that a regex isn't a regex"); 1418 | // This test will ensures it works in both ways, and ALSO especially that we can make differences 1419 | // between RegExp and Function constructor because typeof on a RegExpt instance is "function" 1420 | equals(QUnit.equiv(function () {}, re), false, "Same conversely, but ensures that function and regexp are distinct because their constructor are different"); 1421 | }); 1422 | --------------------------------------------------------------------------------