├── 3x2.html ├── Makefile ├── benchmark ├── boxes.png ├── index.html ├── optimized.html ├── original.html └── original.jquery.viewport.js ├── img ├── bmw_m1_hood.jpg ├── bmw_m1_side.jpg ├── bmw_m3_gt.jpg ├── corvette_pitstop.jpg ├── viper_1.jpg └── viper_corner.jpg ├── jquery.viewport.js └── test ├── index.html ├── jquery.viewport.test.js ├── threshold_test.html └── vendor ├── qunit.css └── qunit.js /3x2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Viewport Selectors Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 38 | 39 | 40 | 48 | 49 | 50 | 51 | 52 |
53 | 66 |
67 | 68 |

Selector test on 3x2 grid.

69 |
70 | 71 |

72 | Make your browser window bit smaller than usual. Then scroll around 73 | page. Small window will tell you which photos are inside the viewport. 74 | 75 |

76 | 77 | 82 | 87 |
78 | BMW M1 Hood 79 | BMW M1 Side 80 | Viper 1 81 |
83 | Viper Corner 84 | BMW M3 GT 85 | Corvette Pitstop 86 |
88 | 89 |
90 | 93 | 94 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | VERSION = 0.8.2 4 | SHELL = /bin/sh 5 | DOWNLOAD = /var/www/www.appelsiini.net/htdocs/download 6 | JSPACKER = /home/tuupola/bin/jspacker 7 | JSMIN = /home/tuupola/bin/jsmin 8 | 9 | all: viewport packed minified latest 10 | 11 | viewport: jquery.viewport.js 12 | cp jquery.viewport.js $(DOWNLOAD)/jquery.viewport-$(VERSION).js 13 | 14 | packed: jquery.viewport.js 15 | $(JSPACKER) < jquery.viewport.js > jquery.viewport.pack.js 16 | cp jquery.viewport.pack.js $(DOWNLOAD)/jquery.viewport-$(VERSION).pack.js 17 | 18 | minified: jquery.viewport.js 19 | $(JSMIN) < jquery.viewport.js > jquery.viewport.mini.js 20 | cp jquery.viewport.mini.js $(DOWNLOAD)/jquery.viewport-$(VERSION).mini.js 21 | 22 | latest: jquery.viewport.js jquery.viewport.pack.js jquery.viewport.mini.js 23 | cp jquery.viewport.js $(DOWNLOAD)/jquery.viewport.js 24 | cp jquery.viewport.pack.js $(DOWNLOAD)/jquery.viewport.pack.js 25 | cp jquery.viewport.mini.js $(DOWNLOAD)/jquery.viewport.mini.js 26 | 27 | -------------------------------------------------------------------------------- /benchmark/boxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/benchmark/boxes.png -------------------------------------------------------------------------------- /benchmark/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jquery.viewport benchmark 5 | 19 | 20 | 21 | 22 | Fork me on GitHub 23 | 24 | 25 |

Optimized version is ?× faster.

26 | 91 | -------------------------------------------------------------------------------- /benchmark/optimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Original 5 | 6 | 7 | 18 | 19 | 20 | 95 | -------------------------------------------------------------------------------- /benchmark/original.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Original 5 | 6 | 7 | 18 | 19 | 20 | 95 | -------------------------------------------------------------------------------- /benchmark/original.jquery.viewport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Viewport - jQuery selectors for finding elements in viewport 3 | * 4 | * Copyright (c) 2008-2009 Mika Tuupola 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * 9 | * Project home: 10 | * http://www.appelsiini.net/projects/viewport 11 | * 12 | */ 13 | (function($) { 14 | 15 | $.belowthefold = function(element, settings) { 16 | var fold = $(window).height() + $(window).scrollTop(); 17 | return fold <= $(element).offset().top - settings.threshold; 18 | }; 19 | 20 | $.abovethetop = function(element, settings) { 21 | var top = $(window).scrollTop(); 22 | return top >= $(element).offset().top + $(element).height() - settings.threshold; 23 | }; 24 | 25 | $.rightofscreen = function(element, settings) { 26 | var fold = $(window).width() + $(window).scrollLeft(); 27 | return fold <= $(element).offset().left - settings.threshold; 28 | }; 29 | 30 | $.leftofscreen = function(element, settings) { 31 | var left = $(window).scrollLeft(); 32 | return left >= $(element).offset().left + $(element).width() - settings.threshold; 33 | }; 34 | 35 | $.inviewport = function(element, settings) { 36 | return !$.rightofscreen(element, settings) && !$.leftofscreen(element, settings) && !$.belowthefold(element, settings) && !$.abovethetop(element, settings); 37 | }; 38 | 39 | $.extend($.expr[':'], { 40 | "below-the-fold": function(a, i, m) { 41 | return $.belowthefold(a, {threshold : 0}); 42 | }, 43 | "above-the-top": function(a, i, m) { 44 | return $.abovethetop(a, {threshold : 0}); 45 | }, 46 | "left-of-screen": function(a, i, m) { 47 | return $.leftofscreen(a, {threshold : 0}); 48 | }, 49 | "right-of-screen": function(a, i, m) { 50 | return $.rightofscreen(a, {threshold : 0}); 51 | }, 52 | "in-viewport": function(a, i, m) { 53 | return $.inviewport(a, {threshold : 0}); 54 | } 55 | }); 56 | 57 | 58 | })(jQuery); 59 | -------------------------------------------------------------------------------- /img/bmw_m1_hood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/img/bmw_m1_hood.jpg -------------------------------------------------------------------------------- /img/bmw_m1_side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/img/bmw_m1_side.jpg -------------------------------------------------------------------------------- /img/bmw_m3_gt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/img/bmw_m3_gt.jpg -------------------------------------------------------------------------------- /img/corvette_pitstop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/img/corvette_pitstop.jpg -------------------------------------------------------------------------------- /img/viper_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/img/viper_1.jpg -------------------------------------------------------------------------------- /img/viper_corner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NV/jquery_viewport/42db979dc4b779db778d97a7028fe5fa62ebf6be/img/viper_corner.jpg -------------------------------------------------------------------------------- /jquery.viewport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Viewport - jQuery selectors for finding elements in viewport 3 | * 4 | * Copyright (c) 2008-2009 Mika Tuupola 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * 9 | * Project home: 10 | * http://www.appelsiini.net/projects/viewport 11 | * 12 | */ 13 | (function($) { 14 | 15 | $.belowthefold = function(element, settings) { 16 | var fold = $(window).height() + $(window).scrollTop(); 17 | return fold <= $(element).offset().top - settings.threshold; 18 | }; 19 | 20 | $.abovethetop = function(element, settings) { 21 | var top = $(window).scrollTop(); 22 | return top >= $(element).offset().top + $(element).height() - settings.threshold; 23 | }; 24 | 25 | $.rightofscreen = function(element, settings) { 26 | var fold = $(window).width() + $(window).scrollLeft(); 27 | return fold <= $(element).offset().left - settings.threshold; 28 | }; 29 | 30 | $.leftofscreen = function(element, settings) { 31 | var left = $(window).scrollLeft(); 32 | return left >= $(element).offset().left + $(element).width() - settings.threshold; 33 | }; 34 | 35 | $.inviewport = function(element, settings) { 36 | var $element = $(element); 37 | var offset = $element.offset(); 38 | 39 | var $window = $(window); 40 | var windowTop = $window.scrollTop(); 41 | var threshold = settings.threshold; 42 | 43 | if (offset.top - threshold < windowTop) { 44 | if (offset.top + $element.height() + threshold >= windowTop) { 45 | // top edge below the window's top 46 | } else { 47 | return false; 48 | } 49 | } else { 50 | if (offset.top - threshold <= windowTop + $window.height()) { 51 | // bottom edge above the window's bottom 52 | } else { 53 | return false; 54 | } 55 | } 56 | 57 | var windowLeft = $window.scrollLeft(); 58 | 59 | if (offset.left - threshold < windowLeft) { 60 | if (offset.left + $element.width() + threshold >= windowLeft) { 61 | // left edge be on the left side of the window's left edge 62 | } else { 63 | return false; 64 | } 65 | } else { 66 | if (offset.left - threshold <= windowLeft + $window.width()) { 67 | // right edge be on the right side of the window's right edge 68 | } else { 69 | return false; 70 | } 71 | } 72 | 73 | return true; 74 | }; 75 | 76 | $.extend($.expr[':'], { 77 | "below-the-fold": function(a, i, m) { 78 | return $.belowthefold(a, {threshold : 0}); 79 | }, 80 | "above-the-top": function(a, i, m) { 81 | return $.abovethetop(a, {threshold : 0}); 82 | }, 83 | "left-of-screen": function(a, i, m) { 84 | return $.leftofscreen(a, {threshold : 0}); 85 | }, 86 | "right-of-screen": function(a, i, m) { 87 | return $.rightofscreen(a, {threshold : 0}); 88 | }, 89 | "in-viewport": function(a, i, m) { 90 | return $.inviewport(a, {threshold : 0}); 91 | } 92 | }); 93 | 94 | 95 | })(jQuery); 96 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jQuery.viewport tests 5 | 6 | 7 | 8 | 9 | 10 | 11 |

jQuery.viewport tests

12 |

13 |
14 |
    15 |

    16 | 17 | 18 | -------------------------------------------------------------------------------- /test/jquery.viewport.test.js: -------------------------------------------------------------------------------- 1 | test("threshold", function(){ 2 | var frame = frames[0]; 3 | var img = frame.$("img"); 4 | ok(!frame.$.inviewport(img, {threshold: 0}), "left 0"); 5 | ok(!frame.$.inviewport(img, {threshold: 50}), "left 50"); 6 | ok(frame.$.inviewport(img, {threshold: 150}), "left 150"); 7 | 8 | frame.scroll(1200, 0); 9 | 10 | ok(!frame.$.inviewport(img, {threshold: 0}), "right 0"); 11 | ok(frame.$.inviewport(img, {threshold: 400}), "right 400"); 12 | 13 | frame.scroll(600, 300); 14 | 15 | ok(frame.$.inviewport(img, {threshold: -100}), "middle -100"); 16 | }); 17 | -------------------------------------------------------------------------------- /test/threshold_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threshold test 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/vendor/qunit.css: -------------------------------------------------------------------------------- 1 | /** Font Family and Sizes */ 2 | 3 | #qunit-wrapper { 4 | font-family: "Helvetica Neue", Helvetica, sans-serif; 5 | } 6 | 7 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 8 | #qunit-tests { font-size: smaller; } 9 | 10 | 11 | /** Resets */ 12 | 13 | #qunit-wrapper, #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-testresult { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | 19 | /** Header */ 20 | 21 | #qunit-header { 22 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Helvetica, sans-serif; 23 | padding: 0.5em 0 0.5em 1em; 24 | font-weight: normal; 25 | background-color: #0d3349; 26 | } 27 | 28 | #qunit-header a { 29 | text-decoration: none; 30 | color: #bad2df; 31 | } 32 | 33 | #qunit-header a:hover, 34 | #qunit-header a:focus { 35 | color: white; 36 | text-shadow: 0 0 4px #5deeff; 37 | } 38 | 39 | #qunit-banner.qunit-pass { 40 | height: 3px; 41 | } 42 | #qunit-banner.qunit-fail { 43 | height: 5px; 44 | } 45 | 46 | #qunit-testrunner-toolbar { 47 | padding: 0 0 0.5em 2em; 48 | } 49 | 50 | #qunit-testrunner-toolbar label { 51 | margin-right: 1em; 52 | } 53 | 54 | #qunit-userAgent { 55 | padding: 0.5em 0 0.5em 2.5em; 56 | font-weight: normal; 57 | color: #666; 58 | } 59 | 60 | 61 | /** Tests: Pass/Fail */ 62 | 63 | #qunit-tests { 64 | list-style-type: none; 65 | background-color: #D2E0E6; 66 | } 67 | 68 | #qunit-tests li { 69 | padding: 0.4em 0.5em 0.4em 2.5em; 70 | } 71 | 72 | #qunit-tests li strong { 73 | font-weight: normal; 74 | cursor: pointer; 75 | } 76 | 77 | #qunit-tests ol { 78 | margin: 0.5em 0 1em; 79 | background-color: #fff; 80 | } 81 | 82 | #qunit-tests table { 83 | border-collapse: collapse; 84 | } 85 | 86 | #qunit-tests th { 87 | text-align: right; 88 | vertical-align: top; 89 | padding: 0 .5em 0 0; 90 | } 91 | 92 | #qunit-tests td { 93 | vertical-align: top; 94 | } 95 | 96 | #qunit-tests pre { 97 | margin: 0; 98 | white-space: pre-wrap; 99 | word-wrap: break-word; 100 | } 101 | 102 | #qunit-tests del { 103 | background-color: #e0f2be; 104 | color: #374e0c; 105 | text-decoration: none; 106 | } 107 | 108 | #qunit-tests ins { 109 | background-color: #ffcaca; 110 | color: #500; 111 | text-decoration: none; 112 | } 113 | 114 | #qunit-tests table { 115 | border-collapse: collapse; 116 | margin-top: .2em; 117 | } 118 | 119 | #qunit-tests th { 120 | text-align: right; 121 | vertical-align: top; 122 | padding: 0 .5em 0 0; 123 | } 124 | 125 | #qunit-tests td { 126 | vertical-align: top; 127 | } 128 | 129 | #qunit-tests pre { 130 | margin: 0; 131 | white-space: pre-wrap; 132 | word-wrap: break-word; 133 | } 134 | 135 | #qunit-tests del { 136 | background-color: #e0f2be; 137 | color: #374e0c; 138 | text-decoration: none; 139 | } 140 | 141 | #qunit-tests ins { 142 | background-color: #ffcaca; 143 | color: #500; 144 | text-decoration: none; 145 | } 146 | 147 | /*** Test Counts */ 148 | 149 | #qunit-tests b.passed { color: #5E740B; } 150 | #qunit-tests b.failed { 151 | color: #710909; 152 | } 153 | #qunit-tests li.fail .failed { 154 | color: #EE5757; 155 | } 156 | #qunit-tests li.fail .passed { 157 | color: #d3ff9a; 158 | } 159 | 160 | #qunit-tests li li { 161 | margin-left: 2.5em; 162 | padding: 0.7em 0.5em 0.7em 0; 163 | background-color: #fff; 164 | border-bottom: none; 165 | } 166 | 167 | #qunit-tests b.counts { 168 | font-weight: normal; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #5E740B; 175 | background-color: #fff; 176 | 177 | } 178 | 179 | #qunit-tests .pass { color: #2f3424; background-color: #d9dec3; } 180 | #qunit-tests .pass .module-name { color: #636b51; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | } 193 | 194 | #qunit-tests .fail { color: #fff; background-color: #962323; } 195 | #qunit-tests .fail .module-name { color: #c28e8e; } 196 | 197 | #qunit-tests .fail .test-actual { color: #EE5757; } 198 | #qunit-tests .fail .test-expected { color: green; } 199 | 200 | #qunit-banner.qunit-fail, 201 | #qunit-testrunner-toolbar { color: #dec1c1; background-color: #962323; } 202 | 203 | 204 | /** Footer */ 205 | 206 | #qunit-testresult { 207 | padding: 0.5em 0.5em 0.5em 2.5em; 208 | color: #333; 209 | } 210 | 211 | /** Fixture */ 212 | 213 | #qunit-fixture { 214 | position: absolute; 215 | top: -10000px; 216 | left: -10000px; 217 | } 218 | -------------------------------------------------------------------------------- /test/vendor/qunit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var QUnit = { 14 | 15 | // call on start of module test to prepend name to all tests 16 | module: function(name, testEnvironment) { 17 | config.currentModule = name; 18 | 19 | synchronize(function() { 20 | if ( config.currentModule ) { 21 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 22 | } 23 | 24 | config.currentModule = name; 25 | config.moduleTestEnvironment = testEnvironment; 26 | config.moduleStats = { all: 0, bad: 0 }; 27 | 28 | QUnit.moduleStart( name, testEnvironment ); 29 | }); 30 | }, 31 | 32 | asyncTest: function(testName, expected, callback) { 33 | if ( arguments.length === 2 ) { 34 | callback = expected; 35 | expected = 0; 36 | } 37 | 38 | QUnit.test(testName, expected, callback, true); 39 | }, 40 | 41 | test: function(testName, expected, callback, async) { 42 | var name = '' + testName + '', testEnvironment, testEnvironmentArg; 43 | 44 | if ( arguments.length === 2 ) { 45 | callback = expected; 46 | expected = null; 47 | } 48 | // is 2nd argument a testEnvironment? 49 | if ( expected && typeof expected === 'object') { 50 | testEnvironmentArg = expected; 51 | expected = null; 52 | } 53 | 54 | if ( config.currentModule ) { 55 | name = '' + config.currentModule + ": " + name; 56 | } 57 | 58 | if ( !validTest(config.currentModule + ": " + testName) ) { 59 | return; 60 | } 61 | 62 | synchronize(function() { 63 | 64 | testEnvironment = extend({ 65 | setup: function() {}, 66 | teardown: function() {} 67 | }, config.moduleTestEnvironment); 68 | if (testEnvironmentArg) { 69 | extend(testEnvironment,testEnvironmentArg); 70 | } 71 | 72 | QUnit.testStart( testName, testEnvironment ); 73 | 74 | // allow utility functions to access the current test environment 75 | QUnit.current_testEnvironment = testEnvironment; 76 | 77 | config.assertions = []; 78 | config.expected = expected; 79 | 80 | var tests = id("qunit-tests"); 81 | if (tests) { 82 | var b = document.createElement("strong"); 83 | b.innerHTML = "Running " + name; 84 | var li = document.createElement("li"); 85 | li.appendChild( b ); 86 | li.id = "current-test-output"; 87 | tests.appendChild( li ) 88 | } 89 | 90 | try { 91 | if ( !config.pollution ) { 92 | saveGlobal(); 93 | } 94 | 95 | testEnvironment.setup.call(testEnvironment); 96 | } catch(e) { 97 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 98 | } 99 | }); 100 | 101 | synchronize(function() { 102 | if ( async ) { 103 | QUnit.stop(); 104 | } 105 | 106 | try { 107 | callback.call(testEnvironment); 108 | } catch(e) { 109 | fail("Test " + name + " died, exception and test follows", e, callback); 110 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 111 | // else next test will carry the responsibility 112 | saveGlobal(); 113 | 114 | // Restart the tests if they're blocking 115 | if ( config.blocking ) { 116 | start(); 117 | } 118 | } 119 | }); 120 | 121 | synchronize(function() { 122 | try { 123 | checkPollution(); 124 | testEnvironment.teardown.call(testEnvironment); 125 | } catch(e) { 126 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 127 | } 128 | }); 129 | 130 | synchronize(function() { 131 | try { 132 | QUnit.reset(); 133 | } catch(e) { 134 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 135 | } 136 | 137 | if ( config.expected && config.expected != config.assertions.length ) { 138 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 139 | } 140 | 141 | var good = 0, bad = 0, 142 | tests = id("qunit-tests"); 143 | 144 | config.stats.all += config.assertions.length; 145 | config.moduleStats.all += config.assertions.length; 146 | 147 | if ( tests ) { 148 | var ol = document.createElement("ol"); 149 | 150 | for ( var i = 0; i < config.assertions.length; i++ ) { 151 | var assertion = config.assertions[i]; 152 | 153 | var li = document.createElement("li"); 154 | li.className = assertion.result ? "pass" : "fail"; 155 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 156 | ol.appendChild( li ); 157 | 158 | if ( assertion.result ) { 159 | good++; 160 | } else { 161 | bad++; 162 | config.stats.bad++; 163 | config.moduleStats.bad++; 164 | } 165 | } 166 | if (bad == 0) { 167 | ol.style.display = "none"; 168 | } 169 | 170 | var b = document.createElement("strong"); 171 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 172 | 173 | addEvent(b, "click", function() { 174 | var next = b.nextSibling, display = next.style.display; 175 | next.style.display = display === "none" ? "block" : "none"; 176 | }); 177 | 178 | addEvent(b, "dblclick", function(e) { 179 | var target = e && e.target ? e.target : window.event.srcElement; 180 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 181 | target = target.parentNode; 182 | } 183 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 184 | window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); 185 | } 186 | }); 187 | 188 | var li = id("current-test-output"); 189 | li.id = ""; 190 | li.className = bad ? "fail" : "pass"; 191 | li.removeChild( li.firstChild ); 192 | li.appendChild( b ); 193 | li.appendChild( ol ); 194 | 195 | if ( bad ) { 196 | var toolbar = id("qunit-testrunner-toolbar"); 197 | if ( toolbar ) { 198 | toolbar.style.display = "block"; 199 | id("qunit-filter-pass").disabled = null; 200 | id("qunit-filter-missing").disabled = null; 201 | } 202 | } 203 | 204 | } else { 205 | for ( var i = 0; i < config.assertions.length; i++ ) { 206 | if ( !config.assertions[i].result ) { 207 | bad++; 208 | config.stats.bad++; 209 | config.moduleStats.bad++; 210 | } 211 | } 212 | } 213 | 214 | QUnit.testDone( testName, bad, config.assertions.length ); 215 | 216 | if ( !window.setTimeout && !config.queue.length ) { 217 | done(); 218 | } 219 | }); 220 | 221 | synchronize( done ); 222 | }, 223 | 224 | /** 225 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 226 | */ 227 | expect: function(asserts) { 228 | config.expected = asserts; 229 | }, 230 | 231 | /** 232 | * Asserts true. 233 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 234 | */ 235 | ok: function(a, msg) { 236 | msg = escapeHtml(msg); 237 | QUnit.log(a, msg); 238 | 239 | config.assertions.push({ 240 | result: !!a, 241 | message: msg 242 | }); 243 | }, 244 | 245 | /** 246 | * Checks that the first two arguments are equal, with an optional message. 247 | * Prints out both actual and expected values. 248 | * 249 | * Prefered to ok( actual == expected, message ) 250 | * 251 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 252 | * 253 | * @param Object actual 254 | * @param Object expected 255 | * @param String message (optional) 256 | */ 257 | equal: function(actual, expected, message) { 258 | push(expected == actual, actual, expected, message); 259 | }, 260 | 261 | notEqual: function(actual, expected, message) { 262 | push(expected != actual, actual, expected, message); 263 | }, 264 | 265 | deepEqual: function(actual, expected, message) { 266 | push(QUnit.equiv(actual, expected), actual, expected, message); 267 | }, 268 | 269 | notDeepEqual: function(actual, expected, message) { 270 | push(!QUnit.equiv(actual, expected), actual, expected, message); 271 | }, 272 | 273 | strictEqual: function(actual, expected, message) { 274 | push(expected === actual, actual, expected, message); 275 | }, 276 | 277 | notStrictEqual: function(actual, expected, message) { 278 | push(expected !== actual, actual, expected, message); 279 | }, 280 | 281 | raises: function(fn, message) { 282 | try { 283 | fn(); 284 | ok( false, message ); 285 | } 286 | catch (e) { 287 | ok( true, message ); 288 | } 289 | }, 290 | 291 | start: function() { 292 | // A slight delay, to avoid any current callbacks 293 | if ( window.setTimeout ) { 294 | window.setTimeout(function() { 295 | if ( config.timeout ) { 296 | clearTimeout(config.timeout); 297 | } 298 | 299 | config.blocking = false; 300 | process(); 301 | }, 13); 302 | } else { 303 | config.blocking = false; 304 | process(); 305 | } 306 | }, 307 | 308 | stop: function(timeout) { 309 | config.blocking = true; 310 | 311 | if ( timeout && window.setTimeout ) { 312 | config.timeout = window.setTimeout(function() { 313 | QUnit.ok( false, "Test timed out" ); 314 | QUnit.start(); 315 | }, timeout); 316 | } 317 | } 318 | 319 | }; 320 | 321 | // Backwards compatibility, deprecated 322 | QUnit.equals = QUnit.equal; 323 | QUnit.same = QUnit.deepEqual; 324 | 325 | // Maintain internal state 326 | var config = { 327 | // The queue of tests to run 328 | queue: [], 329 | 330 | // block until document ready 331 | blocking: true 332 | }; 333 | 334 | // Load paramaters 335 | (function() { 336 | var location = window.location || { search: "", protocol: "file:" }, 337 | GETParams = location.search.slice(1).split('&'); 338 | 339 | for ( var i = 0; i < GETParams.length; i++ ) { 340 | GETParams[i] = decodeURIComponent( GETParams[i] ); 341 | if ( GETParams[i] === "noglobals" ) { 342 | GETParams.splice( i, 1 ); 343 | i--; 344 | config.noglobals = true; 345 | } else if ( GETParams[i].search('=') > -1 ) { 346 | GETParams.splice( i, 1 ); 347 | i--; 348 | } 349 | } 350 | 351 | // restrict modules/tests by get parameters 352 | config.filters = GETParams; 353 | 354 | // Figure out if we're running the tests from a server or not 355 | QUnit.isLocal = !!(location.protocol === 'file:'); 356 | })(); 357 | 358 | // Expose the API as global variables, unless an 'exports' 359 | // object exists, in that case we assume we're in CommonJS 360 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 361 | extend(window, QUnit); 362 | window.QUnit = QUnit; 363 | } else { 364 | extend(exports, QUnit); 365 | exports.QUnit = QUnit; 366 | } 367 | 368 | // define these after exposing globals to keep them in these QUnit namespace only 369 | extend(QUnit, { 370 | config: config, 371 | 372 | // Initialize the configuration options 373 | init: function() { 374 | extend(config, { 375 | stats: { all: 0, bad: 0 }, 376 | moduleStats: { all: 0, bad: 0 }, 377 | started: +new Date, 378 | updateRate: 1000, 379 | blocking: false, 380 | autostart: true, 381 | autorun: false, 382 | assertions: [], 383 | filters: [], 384 | queue: [] 385 | }); 386 | 387 | var tests = id("qunit-tests"), 388 | banner = id("qunit-banner"), 389 | result = id("qunit-testresult"); 390 | 391 | if ( tests ) { 392 | tests.innerHTML = ""; 393 | } 394 | 395 | if ( banner ) { 396 | banner.className = ""; 397 | } 398 | 399 | if ( result ) { 400 | result.parentNode.removeChild( result ); 401 | } 402 | }, 403 | 404 | /** 405 | * Resets the test setup. Useful for tests that modify the DOM. 406 | */ 407 | reset: function() { 408 | if ( window.jQuery ) { 409 | jQuery("#main, #qunit-fixture").html( config.fixture ); 410 | } 411 | }, 412 | 413 | /** 414 | * Trigger an event on an element. 415 | * 416 | * @example triggerEvent( document.body, "click" ); 417 | * 418 | * @param DOMElement elem 419 | * @param String type 420 | */ 421 | triggerEvent: function( elem, type, event ) { 422 | if ( document.createEvent ) { 423 | event = document.createEvent("MouseEvents"); 424 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 425 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 426 | elem.dispatchEvent( event ); 427 | 428 | } else if ( elem.fireEvent ) { 429 | elem.fireEvent("on"+type); 430 | } 431 | }, 432 | 433 | // Safe object type checking 434 | is: function( type, obj ) { 435 | return QUnit.objectType( obj ) == type; 436 | }, 437 | 438 | objectType: function( obj ) { 439 | if (typeof obj === "undefined") { 440 | return "undefined"; 441 | 442 | // consider: typeof null === object 443 | } 444 | if (obj === null) { 445 | return "null"; 446 | } 447 | 448 | var type = Object.prototype.toString.call( obj ) 449 | .match(/^\[object\s(.*)\]$/)[1] || ''; 450 | 451 | switch (type) { 452 | case 'Number': 453 | if (isNaN(obj)) { 454 | return "nan"; 455 | } else { 456 | return "number"; 457 | } 458 | case 'String': 459 | case 'Boolean': 460 | case 'Array': 461 | case 'Date': 462 | case 'RegExp': 463 | case 'Function': 464 | return type.toLowerCase(); 465 | } 466 | if (typeof obj === "object") { 467 | return "object"; 468 | } 469 | return undefined; 470 | }, 471 | 472 | // Logging callbacks 473 | begin: function() {}, 474 | done: function(failures, total) {}, 475 | log: function(result, message) {}, 476 | testStart: function(name, testEnvironment) {}, 477 | testDone: function(name, failures, total) {}, 478 | moduleStart: function(name, testEnvironment) {}, 479 | moduleDone: function(name, failures, total) {} 480 | }); 481 | 482 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 483 | config.autorun = true; 484 | } 485 | 486 | addEvent(window, "load", function() { 487 | QUnit.begin(); 488 | 489 | // Initialize the config, saving the execution queue 490 | var oldconfig = extend({}, config); 491 | QUnit.init(); 492 | extend(config, oldconfig); 493 | 494 | config.blocking = false; 495 | 496 | var userAgent = id("qunit-userAgent"); 497 | if ( userAgent ) { 498 | userAgent.innerHTML = navigator.userAgent; 499 | } 500 | var banner = id("qunit-header"); 501 | if ( banner ) { 502 | banner.innerHTML = '' + banner.innerHTML + ''; 503 | } 504 | 505 | var toolbar = id("qunit-testrunner-toolbar"); 506 | if ( toolbar ) { 507 | toolbar.style.display = "none"; 508 | 509 | var filter = document.createElement("input"); 510 | filter.type = "checkbox"; 511 | filter.id = "qunit-filter-pass"; 512 | filter.disabled = true; 513 | addEvent( filter, "click", function() { 514 | var li = document.getElementsByTagName("li"); 515 | for ( var i = 0; i < li.length; i++ ) { 516 | if ( li[i].className.indexOf("pass") > -1 ) { 517 | li[i].style.display = filter.checked ? "none" : ""; 518 | } 519 | } 520 | }); 521 | toolbar.appendChild( filter ); 522 | 523 | var label = document.createElement("label"); 524 | label.setAttribute("for", "qunit-filter-pass"); 525 | label.innerHTML = "Hide passed tests"; 526 | toolbar.appendChild( label ); 527 | 528 | var missing = document.createElement("input"); 529 | missing.type = "checkbox"; 530 | missing.id = "qunit-filter-missing"; 531 | missing.disabled = true; 532 | addEvent( missing, "click", function() { 533 | var li = document.getElementsByTagName("li"); 534 | for ( var i = 0; i < li.length; i++ ) { 535 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 536 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 537 | } 538 | } 539 | }); 540 | toolbar.appendChild( missing ); 541 | 542 | label = document.createElement("label"); 543 | label.setAttribute("for", "qunit-filter-missing"); 544 | label.innerHTML = "Hide missing tests (untested code is broken code)"; 545 | toolbar.appendChild( label ); 546 | } 547 | 548 | var main = id('main') || id('qunit-fixture'); 549 | if ( main ) { 550 | config.fixture = main.innerHTML; 551 | } 552 | 553 | if (config.autostart) { 554 | QUnit.start(); 555 | } 556 | }); 557 | 558 | function done() { 559 | if ( config.doneTimer && window.clearTimeout ) { 560 | window.clearTimeout( config.doneTimer ); 561 | config.doneTimer = null; 562 | } 563 | 564 | if ( config.queue.length ) { 565 | config.doneTimer = window.setTimeout(function(){ 566 | if ( !config.queue.length ) { 567 | done(); 568 | } else { 569 | synchronize( done ); 570 | } 571 | }, 13); 572 | 573 | return; 574 | } 575 | 576 | config.autorun = true; 577 | 578 | // Log the last module results 579 | if ( config.currentModule ) { 580 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 581 | } 582 | 583 | var banner = id("qunit-banner"), 584 | tests = id("qunit-tests"), 585 | html = ['Tests completed in ', 586 | +new Date - config.started, ' milliseconds.
    ', 587 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); 588 | 589 | if ( banner ) { 590 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 591 | } 592 | 593 | if ( tests ) { 594 | var result = id("qunit-testresult"); 595 | 596 | if ( !result ) { 597 | result = document.createElement("p"); 598 | result.id = "qunit-testresult"; 599 | result.className = "result"; 600 | tests.parentNode.insertBefore( result, tests.nextSibling ); 601 | } 602 | 603 | result.innerHTML = html; 604 | } 605 | 606 | QUnit.done( config.stats.bad, config.stats.all ); 607 | } 608 | 609 | function validTest( name ) { 610 | var i = config.filters.length, 611 | run = false; 612 | 613 | if ( !i ) { 614 | return true; 615 | } 616 | 617 | while ( i-- ) { 618 | var filter = config.filters[i], 619 | not = filter.charAt(0) == '!'; 620 | 621 | if ( not ) { 622 | filter = filter.slice(1); 623 | } 624 | 625 | if ( name.indexOf(filter) !== -1 ) { 626 | return !not; 627 | } 628 | 629 | if ( not ) { 630 | run = true; 631 | } 632 | } 633 | 634 | return run; 635 | } 636 | 637 | function escapeHtml(s) { 638 | if (!s) { 639 | return ""; 640 | } 641 | s = s + ""; 642 | return s.replace(/[\&"<>\\]/g, function(s) { 643 | switch(s) { 644 | case "&": return "&"; 645 | case "\\": return "\\\\"; 646 | case '"': return '\"'; 647 | case "<": return "<"; 648 | case ">": return ">"; 649 | default: return s; 650 | } 651 | }); 652 | } 653 | 654 | function push(result, actual, expected, message) { 655 | message = escapeHtml(message) || (result ? "okay" : "failed"); 656 | message = '' + message + ""; 657 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 658 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 659 | var output = message + ''; 660 | if (actual != expected) { 661 | output += ''; 662 | output += ''; 663 | } 664 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    "; 665 | 666 | // can't use ok, as that would double-escape messages 667 | QUnit.log(result, output); 668 | config.assertions.push({ 669 | result: !!result, 670 | message: output 671 | }); 672 | } 673 | 674 | function synchronize( callback ) { 675 | config.queue.push( callback ); 676 | 677 | if ( config.autorun && !config.blocking ) { 678 | process(); 679 | } 680 | } 681 | 682 | function process() { 683 | var start = (new Date()).getTime(); 684 | 685 | while ( config.queue.length && !config.blocking ) { 686 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 687 | config.queue.shift()(); 688 | 689 | } else { 690 | setTimeout( process, 13 ); 691 | break; 692 | } 693 | } 694 | } 695 | 696 | function saveGlobal() { 697 | config.pollution = []; 698 | 699 | if ( config.noglobals ) { 700 | for ( var key in window ) { 701 | config.pollution.push( key ); 702 | } 703 | } 704 | } 705 | 706 | function checkPollution( name ) { 707 | var old = config.pollution; 708 | saveGlobal(); 709 | 710 | var newGlobals = diff( old, config.pollution ); 711 | if ( newGlobals.length > 0 ) { 712 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 713 | config.expected++; 714 | } 715 | 716 | var deletedGlobals = diff( config.pollution, old ); 717 | if ( deletedGlobals.length > 0 ) { 718 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 719 | config.expected++; 720 | } 721 | } 722 | 723 | // returns a new Array with the elements that are in a but not in b 724 | function diff( a, b ) { 725 | var result = a.slice(); 726 | for ( var i = 0; i < result.length; i++ ) { 727 | for ( var j = 0; j < b.length; j++ ) { 728 | if ( result[i] === b[j] ) { 729 | result.splice(i, 1); 730 | i--; 731 | break; 732 | } 733 | } 734 | } 735 | return result; 736 | } 737 | 738 | function fail(message, exception, callback) { 739 | if ( typeof console !== "undefined" && console.error && console.warn ) { 740 | console.error(message); 741 | console.error(exception); 742 | console.warn(callback.toString()); 743 | 744 | } else if ( window.opera && opera.postError ) { 745 | opera.postError(message, exception, callback.toString); 746 | } 747 | } 748 | 749 | function extend(a, b) { 750 | for ( var prop in b ) { 751 | a[prop] = b[prop]; 752 | } 753 | 754 | return a; 755 | } 756 | 757 | function addEvent(elem, type, fn) { 758 | if ( elem.addEventListener ) { 759 | elem.addEventListener( type, fn, false ); 760 | } else if ( elem.attachEvent ) { 761 | elem.attachEvent( "on" + type, fn ); 762 | } else { 763 | fn(); 764 | } 765 | } 766 | 767 | function id(name) { 768 | return !!(typeof document !== "undefined" && document && document.getElementById) && 769 | document.getElementById( name ); 770 | } 771 | 772 | // Test for equality any JavaScript type. 773 | // Discussions and reference: http://philrathe.com/articles/equiv 774 | // Test suites: http://philrathe.com/tests/equiv 775 | // Author: Philippe Rathé 776 | QUnit.equiv = function () { 777 | 778 | var innerEquiv; // the real equiv function 779 | var callers = []; // stack to decide between skip/abort functions 780 | var parents = []; // stack to avoiding loops from circular referencing 781 | 782 | // Call the o related callback with the given arguments. 783 | function bindCallbacks(o, callbacks, args) { 784 | var prop = QUnit.objectType(o); 785 | if (prop) { 786 | if (QUnit.objectType(callbacks[prop]) === "function") { 787 | return callbacks[prop].apply(callbacks, args); 788 | } else { 789 | return callbacks[prop]; // or undefined 790 | } 791 | } 792 | } 793 | 794 | var callbacks = function () { 795 | 796 | // for string, boolean, number and null 797 | function useStrictEquality(b, a) { 798 | if (b instanceof a.constructor || a instanceof b.constructor) { 799 | // to catch short annotaion VS 'new' annotation of a declaration 800 | // e.g. var i = 1; 801 | // var j = new Number(1); 802 | return a == b; 803 | } else { 804 | return a === b; 805 | } 806 | } 807 | 808 | return { 809 | "string": useStrictEquality, 810 | "boolean": useStrictEquality, 811 | "number": useStrictEquality, 812 | "null": useStrictEquality, 813 | "undefined": useStrictEquality, 814 | 815 | "nan": function (b) { 816 | return isNaN(b); 817 | }, 818 | 819 | "date": function (b, a) { 820 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 821 | }, 822 | 823 | "regexp": function (b, a) { 824 | return QUnit.objectType(b) === "regexp" && 825 | a.source === b.source && // the regex itself 826 | a.global === b.global && // and its modifers (gmi) ... 827 | a.ignoreCase === b.ignoreCase && 828 | a.multiline === b.multiline; 829 | }, 830 | 831 | // - skip when the property is a method of an instance (OOP) 832 | // - abort otherwise, 833 | // initial === would have catch identical references anyway 834 | "function": function () { 835 | var caller = callers[callers.length - 1]; 836 | return caller !== Object && 837 | typeof caller !== "undefined"; 838 | }, 839 | 840 | "array": function (b, a) { 841 | var i, j, loop; 842 | var len; 843 | 844 | // b could be an object literal here 845 | if ( ! (QUnit.objectType(b) === "array")) { 846 | return false; 847 | } 848 | 849 | len = a.length; 850 | if (len !== b.length) { // safe and faster 851 | return false; 852 | } 853 | 854 | //track reference to avoid circular references 855 | parents.push(a); 856 | for (i = 0; i < len; i++) { 857 | loop = false; 858 | for(j=0;j= 0) { 1003 | type = "array"; 1004 | } else { 1005 | type = typeof obj; 1006 | } 1007 | return type; 1008 | }, 1009 | separator:function() { 1010 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1011 | }, 1012 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1013 | if ( !this.multiline ) 1014 | return ''; 1015 | var chr = this.indentChar; 1016 | if ( this.HTML ) 1017 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1018 | return Array( this._depth_ + (extra||0) ).join(chr); 1019 | }, 1020 | up:function( a ) { 1021 | this._depth_ += a || 1; 1022 | }, 1023 | down:function( a ) { 1024 | this._depth_ -= a || 1; 1025 | }, 1026 | setParser:function( name, parser ) { 1027 | this.parsers[name] = parser; 1028 | }, 1029 | // The next 3 are exposed so you can use them 1030 | quote:quote, 1031 | literal:literal, 1032 | join:join, 1033 | // 1034 | _depth_: 1, 1035 | // This is the list of parsers, to modify them, use jsDump.setParser 1036 | parsers:{ 1037 | window: '[Window]', 1038 | document: '[Document]', 1039 | error:'[ERROR]', //when no parser is found, shouldn't happen 1040 | unknown: '[Unknown]', 1041 | 'null':'null', 1042 | undefined:'undefined', 1043 | 'function':function( fn ) { 1044 | var ret = 'function', 1045 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1046 | if ( name ) 1047 | ret += ' ' + name; 1048 | ret += '('; 1049 | 1050 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 1051 | return join( ret, this.parse(fn,'functionCode'), '}' ); 1052 | }, 1053 | array: array, 1054 | nodelist: array, 1055 | arguments: array, 1056 | object:function( map ) { 1057 | var ret = [ ]; 1058 | this.up(); 1059 | for ( var key in map ) 1060 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 1061 | this.down(); 1062 | return join( '{', ret, '}' ); 1063 | }, 1064 | node:function( node ) { 1065 | var open = this.HTML ? '<' : '<', 1066 | close = this.HTML ? '>' : '>'; 1067 | 1068 | var tag = node.nodeName.toLowerCase(), 1069 | ret = open + tag; 1070 | 1071 | for ( var a in this.DOMAttrs ) { 1072 | var val = node[this.DOMAttrs[a]]; 1073 | if ( val ) 1074 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 1075 | } 1076 | return ret + close + open + '/' + tag + close; 1077 | }, 1078 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1079 | var l = fn.length; 1080 | if ( !l ) return ''; 1081 | 1082 | var args = Array(l); 1083 | while ( l-- ) 1084 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1085 | return ' ' + args.join(', ') + ' '; 1086 | }, 1087 | key:quote, //object calls it internally, the key part of an item in a map 1088 | functionCode:'[code]', //function calls it internally, it's the content of the function 1089 | attribute:quote, //node calls it internally, it's an html attribute value 1090 | string:quote, 1091 | date:quote, 1092 | regexp:literal, //regex 1093 | number:literal, 1094 | 'boolean':literal 1095 | }, 1096 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1097 | id:'id', 1098 | name:'name', 1099 | 'class':'className' 1100 | }, 1101 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1102 | indentChar:' ',//indentation unit 1103 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1104 | }; 1105 | 1106 | return jsDump; 1107 | })(); 1108 | 1109 | // from Sizzle.js 1110 | function getText( elems ) { 1111 | var ret = "", elem; 1112 | 1113 | for ( var i = 0; elems[i]; i++ ) { 1114 | elem = elems[i]; 1115 | 1116 | // Get the text from text nodes and CDATA nodes 1117 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1118 | ret += elem.nodeValue; 1119 | 1120 | // Traverse everything else, except comment nodes 1121 | } else if ( elem.nodeType !== 8 ) { 1122 | ret += getText( elem.childNodes ); 1123 | } 1124 | } 1125 | 1126 | return ret; 1127 | }; 1128 | 1129 | /* 1130 | * Javascript Diff Algorithm 1131 | * By John Resig (http://ejohn.org/) 1132 | * Modified by Chu Alan "sprite" 1133 | * 1134 | * Released under the MIT license. 1135 | * 1136 | * More Info: 1137 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1138 | * 1139 | * Usage: QUnit.diff(expected, actual) 1140 | * 1141 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1142 | */ 1143 | QUnit.diff = (function() { 1144 | function diff(o, n){ 1145 | var ns = new Object(); 1146 | var os = new Object(); 1147 | 1148 | for (var i = 0; i < n.length; i++) { 1149 | if (ns[n[i]] == null) 1150 | ns[n[i]] = { 1151 | rows: new Array(), 1152 | o: null 1153 | }; 1154 | ns[n[i]].rows.push(i); 1155 | } 1156 | 1157 | for (var i = 0; i < o.length; i++) { 1158 | if (os[o[i]] == null) 1159 | os[o[i]] = { 1160 | rows: new Array(), 1161 | n: null 1162 | }; 1163 | os[o[i]].rows.push(i); 1164 | } 1165 | 1166 | for (var i in ns) { 1167 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1168 | n[ns[i].rows[0]] = { 1169 | text: n[ns[i].rows[0]], 1170 | row: os[i].rows[0] 1171 | }; 1172 | o[os[i].rows[0]] = { 1173 | text: o[os[i].rows[0]], 1174 | row: ns[i].rows[0] 1175 | }; 1176 | } 1177 | } 1178 | 1179 | for (var i = 0; i < n.length - 1; i++) { 1180 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1181 | n[i + 1] == o[n[i].row + 1]) { 1182 | n[i + 1] = { 1183 | text: n[i + 1], 1184 | row: n[i].row + 1 1185 | }; 1186 | o[n[i].row + 1] = { 1187 | text: o[n[i].row + 1], 1188 | row: i + 1 1189 | }; 1190 | } 1191 | } 1192 | 1193 | for (var i = n.length - 1; i > 0; i--) { 1194 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1195 | n[i - 1] == o[n[i].row - 1]) { 1196 | n[i - 1] = { 1197 | text: n[i - 1], 1198 | row: n[i].row - 1 1199 | }; 1200 | o[n[i].row - 1] = { 1201 | text: o[n[i].row - 1], 1202 | row: i - 1 1203 | }; 1204 | } 1205 | } 1206 | 1207 | return { 1208 | o: o, 1209 | n: n 1210 | }; 1211 | } 1212 | 1213 | return function(o, n){ 1214 | o = o.replace(/\s+$/, ''); 1215 | n = n.replace(/\s+$/, ''); 1216 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1217 | 1218 | var str = ""; 1219 | 1220 | var oSpace = o.match(/\s+/g); 1221 | if (oSpace == null) { 1222 | oSpace = [" "]; 1223 | } 1224 | else { 1225 | oSpace.push(" "); 1226 | } 1227 | var nSpace = n.match(/\s+/g); 1228 | if (nSpace == null) { 1229 | nSpace = [" "]; 1230 | } 1231 | else { 1232 | nSpace.push(" "); 1233 | } 1234 | 1235 | if (out.n.length == 0) { 1236 | for (var i = 0; i < out.o.length; i++) { 1237 | str += '' + out.o[i] + oSpace[i] + ""; 1238 | } 1239 | } 1240 | else { 1241 | if (out.n[0].text == null) { 1242 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1243 | str += '' + out.o[n] + oSpace[n] + ""; 1244 | } 1245 | } 1246 | 1247 | for (var i = 0; i < out.n.length; i++) { 1248 | if (out.n[i].text == null) { 1249 | str += '' + out.n[i] + nSpace[i] + ""; 1250 | } 1251 | else { 1252 | var pre = ""; 1253 | 1254 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1255 | pre += '' + out.o[n] + oSpace[n] + ""; 1256 | } 1257 | str += " " + out.n[i].text + nSpace[i] + pre; 1258 | } 1259 | } 1260 | } 1261 | 1262 | return str; 1263 | } 1264 | })(); 1265 | 1266 | })(this); 1267 | --------------------------------------------------------------------------------