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 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
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 |
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 |
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 + '
Expected: ' + expected + ' ';
660 | if (actual != expected) {
661 | output += 'Result: ' + actual + ' ';
662 | output += 'Diff: ' + QUnit.diff(expected, actual) +' ';
663 | }
664 | output += "
";
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 |
--------------------------------------------------------------------------------