blade-qunit_adapter 0.4.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/assets/vendor/qunit.css +17 -7
- data/assets/vendor/qunit.js +2311 -1981
- data/lib/blade/qunit_adapter/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fd08fb11ea613b062d14e399961489d8f7f2ff1
|
4
|
+
data.tar.gz: 2e29452d5bfc9d8bd59c47e1e173f480fe4f6554
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62b825268156a481a4a0716ed68930c961c8b16d9c486b6c6650cb1f1393ff0513b5626442a0aa3ec12c774dcbbce0a9802c110bdf9a6d4114624f00811d2b7a
|
7
|
+
data.tar.gz: 952f1c457be0bfe9dda4dabfdcac27bedc7c31213e48c255c7f7a1b783652fe5de7120d38daa5f377a43020dd8215ba8063ff7d2dd388aabdcc6fc1da885f4e8
|
data/assets/vendor/qunit.css
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
/*!
|
2
|
-
* QUnit 1.
|
2
|
+
* QUnit 1.20.0
|
3
3
|
* http://qunitjs.com/
|
4
4
|
*
|
5
5
|
* Copyright jQuery Foundation and other contributors
|
6
6
|
* Released under the MIT license
|
7
7
|
* http://jquery.org/license
|
8
8
|
*
|
9
|
-
* Date: 2015-
|
9
|
+
* Date: 2015-10-27T17:53Z
|
10
10
|
*/
|
11
11
|
|
12
12
|
/** Font Family and Sizes */
|
13
13
|
|
14
|
-
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
14
|
+
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
|
15
15
|
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
16
16
|
}
|
17
17
|
|
18
|
-
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
18
|
+
#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
19
19
|
#qunit-tests { font-size: smaller; }
|
20
20
|
|
21
21
|
|
22
22
|
/** Resets */
|
23
23
|
|
24
|
-
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
24
|
+
#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
25
25
|
margin: 0;
|
26
26
|
padding: 0;
|
27
27
|
}
|
@@ -68,6 +68,12 @@
|
|
68
68
|
overflow: hidden;
|
69
69
|
}
|
70
70
|
|
71
|
+
#qunit-filteredTest {
|
72
|
+
padding: 0.5em 1em 0.5em 1em;
|
73
|
+
background-color: #F4FF77;
|
74
|
+
color: #366097;
|
75
|
+
}
|
76
|
+
|
71
77
|
#qunit-userAgent {
|
72
78
|
padding: 0.5em 1em 0.5em 1em;
|
73
79
|
background-color: #2B81AF;
|
@@ -118,8 +124,8 @@
|
|
118
124
|
#qunit-tests.hidepass li.pass {
|
119
125
|
visibility: hidden;
|
120
126
|
position: absolute;
|
121
|
-
width:
|
122
|
-
height:
|
127
|
+
width: 0;
|
128
|
+
height: 0;
|
123
129
|
padding: 0;
|
124
130
|
border: 0;
|
125
131
|
margin: 0;
|
@@ -162,6 +168,10 @@
|
|
162
168
|
border-radius: 5px;
|
163
169
|
}
|
164
170
|
|
171
|
+
.qunit-source {
|
172
|
+
margin: 0.6em 0 0.3em;
|
173
|
+
}
|
174
|
+
|
165
175
|
.qunit-collapsed {
|
166
176
|
display: none;
|
167
177
|
}
|
data/assets/vendor/qunit.js
CHANGED
@@ -1,102 +1,248 @@
|
|
1
1
|
/*!
|
2
|
-
* QUnit 1.
|
2
|
+
* QUnit 1.20.0
|
3
3
|
* http://qunitjs.com/
|
4
4
|
*
|
5
5
|
* Copyright jQuery Foundation and other contributors
|
6
6
|
* Released under the MIT license
|
7
7
|
* http://jquery.org/license
|
8
8
|
*
|
9
|
-
* Date: 2015-
|
9
|
+
* Date: 2015-10-27T17:53Z
|
10
10
|
*/
|
11
11
|
|
12
|
-
(function(
|
12
|
+
(function( global ) {
|
13
13
|
|
14
|
-
var QUnit
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
14
|
+
var QUnit = {};
|
15
|
+
|
16
|
+
var Date = global.Date;
|
17
|
+
var now = Date.now || function() {
|
18
|
+
return new Date().getTime();
|
19
|
+
};
|
20
|
+
|
21
|
+
var setTimeout = global.setTimeout;
|
22
|
+
var clearTimeout = global.clearTimeout;
|
23
|
+
|
24
|
+
// Store a local window from the global to allow direct references.
|
25
|
+
var window = global.window;
|
26
|
+
|
27
|
+
var defined = {
|
28
|
+
document: window && window.document !== undefined,
|
29
|
+
setTimeout: setTimeout !== undefined,
|
30
|
+
sessionStorage: (function() {
|
31
|
+
var x = "qunit-test-string";
|
32
|
+
try {
|
33
|
+
sessionStorage.setItem( x, x );
|
34
|
+
sessionStorage.removeItem( x );
|
35
|
+
return true;
|
36
|
+
} catch ( e ) {
|
37
|
+
return false;
|
38
|
+
}
|
39
|
+
}() )
|
40
|
+
};
|
41
|
+
|
42
|
+
var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
|
43
|
+
var globalStartCalled = false;
|
44
|
+
var runStarted = false;
|
45
|
+
|
46
|
+
var toString = Object.prototype.toString,
|
47
|
+
hasOwn = Object.prototype.hasOwnProperty;
|
48
|
+
|
49
|
+
// returns a new Array with the elements that are in a but not in b
|
50
|
+
function diff( a, b ) {
|
51
|
+
var i, j,
|
52
|
+
result = a.slice();
|
53
|
+
|
54
|
+
for ( i = 0; i < result.length; i++ ) {
|
55
|
+
for ( j = 0; j < b.length; j++ ) {
|
56
|
+
if ( result[ i ] === b[ j ] ) {
|
57
|
+
result.splice( i, 1 );
|
58
|
+
i--;
|
59
|
+
break;
|
41
60
|
}
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
}
|
62
|
+
}
|
63
|
+
return result;
|
64
|
+
}
|
65
|
+
|
66
|
+
// from jquery.js
|
67
|
+
function inArray( elem, array ) {
|
68
|
+
if ( array.indexOf ) {
|
69
|
+
return array.indexOf( elem );
|
70
|
+
}
|
71
|
+
|
72
|
+
for ( var i = 0, length = array.length; i < length; i++ ) {
|
73
|
+
if ( array[ i ] === elem ) {
|
74
|
+
return i;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
return -1;
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Makes a clone of an object using only Array or Object as base,
|
83
|
+
* and copies over the own enumerable properties.
|
84
|
+
*
|
85
|
+
* @param {Object} obj
|
86
|
+
* @return {Object} New object with only the own properties (recursively).
|
87
|
+
*/
|
88
|
+
function objectValues ( obj ) {
|
89
|
+
var key, val,
|
90
|
+
vals = QUnit.is( "array", obj ) ? [] : {};
|
91
|
+
for ( key in obj ) {
|
92
|
+
if ( hasOwn.call( obj, key ) ) {
|
93
|
+
val = obj[ key ];
|
94
|
+
vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
|
95
|
+
}
|
96
|
+
}
|
97
|
+
return vals;
|
98
|
+
}
|
99
|
+
|
100
|
+
function extend( a, b, undefOnly ) {
|
101
|
+
for ( var prop in b ) {
|
102
|
+
if ( hasOwn.call( b, prop ) ) {
|
103
|
+
|
104
|
+
// Avoid "Member not found" error in IE8 caused by messing with window.constructor
|
105
|
+
// This block runs on every environment, so `global` is being used instead of `window`
|
106
|
+
// to avoid errors on node.
|
107
|
+
if ( prop !== "constructor" || a !== global ) {
|
108
|
+
if ( b[ prop ] === undefined ) {
|
109
|
+
delete a[ prop ];
|
110
|
+
} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
|
111
|
+
a[ prop ] = b[ prop ];
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
return a;
|
118
|
+
}
|
119
|
+
|
120
|
+
function objectType( obj ) {
|
121
|
+
if ( typeof obj === "undefined" ) {
|
122
|
+
return "undefined";
|
123
|
+
}
|
124
|
+
|
125
|
+
// Consider: typeof null === object
|
126
|
+
if ( obj === null ) {
|
127
|
+
return "null";
|
128
|
+
}
|
129
|
+
|
130
|
+
var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
|
131
|
+
type = match && match[ 1 ];
|
132
|
+
|
133
|
+
switch ( type ) {
|
134
|
+
case "Number":
|
135
|
+
if ( isNaN( obj ) ) {
|
136
|
+
return "nan";
|
137
|
+
}
|
138
|
+
return "number";
|
139
|
+
case "String":
|
140
|
+
case "Boolean":
|
141
|
+
case "Array":
|
142
|
+
case "Set":
|
143
|
+
case "Map":
|
144
|
+
case "Date":
|
145
|
+
case "RegExp":
|
146
|
+
case "Function":
|
147
|
+
case "Symbol":
|
148
|
+
return type.toLowerCase();
|
149
|
+
}
|
150
|
+
if ( typeof obj === "object" ) {
|
151
|
+
return "object";
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
// Safe object type checking
|
156
|
+
function is( type, obj ) {
|
157
|
+
return QUnit.objectType( obj ) === type;
|
158
|
+
}
|
159
|
+
|
160
|
+
var getUrlParams = function() {
|
161
|
+
var i, current;
|
162
|
+
var urlParams = {};
|
163
|
+
var location = window.location;
|
164
|
+
var params = location.search.slice( 1 ).split( "&" );
|
165
|
+
var length = params.length;
|
166
|
+
|
167
|
+
if ( params[ 0 ] ) {
|
168
|
+
for ( i = 0; i < length; i++ ) {
|
169
|
+
current = params[ i ].split( "=" );
|
170
|
+
current[ 0 ] = decodeURIComponent( current[ 0 ] );
|
171
|
+
|
172
|
+
// allow just a key to turn on a flag, e.g., test.html?noglobals
|
173
|
+
current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
|
174
|
+
if ( urlParams[ current[ 0 ] ] ) {
|
175
|
+
urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
|
66
176
|
} else {
|
67
|
-
|
177
|
+
urlParams[ current[ 0 ] ] = current[ 1 ];
|
68
178
|
}
|
69
|
-
} else {
|
70
|
-
return errorString;
|
71
179
|
}
|
72
|
-
}
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
180
|
+
}
|
181
|
+
|
182
|
+
return urlParams;
|
183
|
+
};
|
184
|
+
|
185
|
+
// Doesn't support IE6 to IE9, it will return undefined on these browsers
|
186
|
+
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
|
187
|
+
function extractStacktrace( e, offset ) {
|
188
|
+
offset = offset === undefined ? 4 : offset;
|
189
|
+
|
190
|
+
var stack, include, i;
|
191
|
+
|
192
|
+
if ( e.stack ) {
|
193
|
+
stack = e.stack.split( "\n" );
|
194
|
+
if ( /^error$/i.test( stack[ 0 ] ) ) {
|
195
|
+
stack.shift();
|
196
|
+
}
|
197
|
+
if ( fileName ) {
|
198
|
+
include = [];
|
199
|
+
for ( i = offset; i < stack.length; i++ ) {
|
200
|
+
if ( stack[ i ].indexOf( fileName ) !== -1 ) {
|
201
|
+
break;
|
202
|
+
}
|
203
|
+
include.push( stack[ i ] );
|
204
|
+
}
|
205
|
+
if ( include.length ) {
|
206
|
+
return include.join( "\n" );
|
87
207
|
}
|
88
208
|
}
|
89
|
-
return
|
90
|
-
|
209
|
+
return stack[ offset ];
|
210
|
+
|
211
|
+
// Support: Safari <=6 only
|
212
|
+
} else if ( e.sourceURL ) {
|
213
|
+
|
214
|
+
// exclude useless self-reference for generated Error objects
|
215
|
+
if ( /qunit.js$/.test( e.sourceURL ) ) {
|
216
|
+
return;
|
217
|
+
}
|
218
|
+
|
219
|
+
// for actual exceptions, this is useful
|
220
|
+
return e.sourceURL + ":" + e.line;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
function sourceFromStacktrace( offset ) {
|
225
|
+
var error = new Error();
|
226
|
+
|
227
|
+
// Support: Safari <=7 only, IE <=10 - 11 only
|
228
|
+
// Not all browsers generate the `stack` property for `new Error()`, see also #636
|
229
|
+
if ( !error.stack ) {
|
230
|
+
try {
|
231
|
+
throw error;
|
232
|
+
} catch ( err ) {
|
233
|
+
error = err;
|
234
|
+
}
|
235
|
+
}
|
91
236
|
|
92
|
-
|
237
|
+
return extractStacktrace( error, offset );
|
238
|
+
}
|
93
239
|
|
94
240
|
/**
|
95
241
|
* Config object: Maintain internal state
|
96
242
|
* Later exposed as QUnit.config
|
97
243
|
* `config` initialized at top of scope
|
98
244
|
*/
|
99
|
-
config = {
|
245
|
+
var config = {
|
100
246
|
// The queue of tests to run
|
101
247
|
queue: [],
|
102
248
|
|
@@ -110,15 +256,19 @@ config = {
|
|
110
256
|
// by default, modify document.title when suite is done
|
111
257
|
altertitle: true,
|
112
258
|
|
259
|
+
// HTML Reporter: collapse every test except the first failing test
|
260
|
+
// If false, all failing tests will be expanded
|
261
|
+
collapse: true,
|
262
|
+
|
113
263
|
// by default, scroll to top of the page when suite is done
|
114
264
|
scrolltop: true,
|
115
265
|
|
116
|
-
// when enabled, all tests must call expect()
|
117
|
-
requireExpects: false,
|
118
|
-
|
119
266
|
// depth up-to which object will be dumped
|
120
267
|
maxDepth: 5,
|
121
268
|
|
269
|
+
// when enabled, all tests must call expect()
|
270
|
+
requireExpects: false,
|
271
|
+
|
122
272
|
// add checkboxes that are persisted in the query-string
|
123
273
|
// when enabled, the id is set to `true` as a `QUnit.config` property
|
124
274
|
urlConfig: [
|
@@ -131,7 +281,7 @@ config = {
|
|
131
281
|
id: "noglobals",
|
132
282
|
label: "Check for Globals",
|
133
283
|
tooltip: "Enabling this will test if any test introduces new properties on the " +
|
134
|
-
"`window`
|
284
|
+
"global object (`window` in Browsers). Stored as query-strings."
|
135
285
|
},
|
136
286
|
{
|
137
287
|
id: "notrycatch",
|
@@ -144,6 +294,9 @@ config = {
|
|
144
294
|
// Set of all modules.
|
145
295
|
modules: [],
|
146
296
|
|
297
|
+
// Stack of nested modules
|
298
|
+
moduleStack: [],
|
299
|
+
|
147
300
|
// The first unnamed module
|
148
301
|
currentModule: {
|
149
302
|
name: "",
|
@@ -153,128 +306,230 @@ config = {
|
|
153
306
|
callbacks: {}
|
154
307
|
};
|
155
308
|
|
309
|
+
var urlParams = defined.document ? getUrlParams() : {};
|
310
|
+
|
156
311
|
// Push a loose unnamed module to the modules collection
|
157
312
|
config.modules.push( config.currentModule );
|
158
313
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
location = window.location || { search: "", protocol: "file:" },
|
163
|
-
params = location.search.slice( 1 ).split( "&" ),
|
164
|
-
length = params.length,
|
165
|
-
urlParams = {};
|
314
|
+
if ( urlParams.filter === true ) {
|
315
|
+
delete urlParams.filter;
|
316
|
+
}
|
166
317
|
|
167
|
-
|
168
|
-
|
169
|
-
current = params[ i ].split( "=" );
|
170
|
-
current[ 0 ] = decodeURIComponent( current[ 0 ] );
|
318
|
+
// String search anywhere in moduleName+testName
|
319
|
+
config.filter = urlParams.filter;
|
171
320
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
}
|
179
|
-
}
|
321
|
+
config.testId = [];
|
322
|
+
if ( urlParams.testId ) {
|
323
|
+
// Ensure that urlParams.testId is an array
|
324
|
+
urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
|
325
|
+
for (var i = 0; i < urlParams.testId.length; i++ ) {
|
326
|
+
config.testId.push( urlParams.testId[ i ] );
|
180
327
|
}
|
328
|
+
}
|
181
329
|
|
182
|
-
|
183
|
-
delete urlParams.filter;
|
184
|
-
}
|
330
|
+
var loggingCallbacks = {};
|
185
331
|
|
186
|
-
|
332
|
+
// Register logging callbacks
|
333
|
+
function registerLoggingCallbacks( obj ) {
|
334
|
+
var i, l, key,
|
335
|
+
callbackNames = [ "begin", "done", "log", "testStart", "testDone",
|
336
|
+
"moduleStart", "moduleDone" ];
|
187
337
|
|
188
|
-
|
189
|
-
|
338
|
+
function registerLoggingCallback( key ) {
|
339
|
+
var loggingCallback = function( callback ) {
|
340
|
+
if ( objectType( callback ) !== "function" ) {
|
341
|
+
throw new Error(
|
342
|
+
"QUnit logging methods require a callback function as their first parameters."
|
343
|
+
);
|
344
|
+
}
|
190
345
|
|
191
|
-
|
192
|
-
|
193
|
-
Number.POSITIVE_INFINITY :
|
194
|
-
urlParams.maxDepth;
|
195
|
-
}
|
346
|
+
config.callbacks[ key ].push( callback );
|
347
|
+
};
|
196
348
|
|
197
|
-
|
198
|
-
|
349
|
+
// DEPRECATED: This will be removed on QUnit 2.0.0+
|
350
|
+
// Stores the registered functions allowing restoring
|
351
|
+
// at verifyLoggingCallbacks() if modified
|
352
|
+
loggingCallbacks[ key ] = loggingCallback;
|
199
353
|
|
200
|
-
|
201
|
-
urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
|
202
|
-
for ( i = 0; i < urlParams.testId.length; i++ ) {
|
203
|
-
config.testId.push( urlParams.testId[ i ] );
|
204
|
-
}
|
354
|
+
return loggingCallback;
|
205
355
|
}
|
206
356
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
// Expose the current QUnit version
|
211
|
-
QUnit.version = "1.18.0";
|
212
|
-
}());
|
213
|
-
|
214
|
-
// Root QUnit object.
|
215
|
-
// `QUnit` initialized at top of scope
|
216
|
-
extend( QUnit, {
|
217
|
-
|
218
|
-
// call on start of module test to prepend name to all tests
|
219
|
-
module: function( name, testEnvironment ) {
|
220
|
-
var currentModule = {
|
221
|
-
name: name,
|
222
|
-
testEnvironment: testEnvironment,
|
223
|
-
tests: []
|
224
|
-
};
|
357
|
+
for ( i = 0, l = callbackNames.length; i < l; i++ ) {
|
358
|
+
key = callbackNames[ i ];
|
225
359
|
|
226
|
-
//
|
227
|
-
|
228
|
-
|
229
|
-
testEnvironment.beforeEach = testEnvironment.setup;
|
230
|
-
delete testEnvironment.setup;
|
231
|
-
}
|
232
|
-
if ( testEnvironment && testEnvironment.teardown ) {
|
233
|
-
testEnvironment.afterEach = testEnvironment.teardown;
|
234
|
-
delete testEnvironment.teardown;
|
360
|
+
// Initialize key collection of logging callback
|
361
|
+
if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
|
362
|
+
config.callbacks[ key ] = [];
|
235
363
|
}
|
236
364
|
|
237
|
-
|
238
|
-
|
239
|
-
|
365
|
+
obj[ key ] = registerLoggingCallback( key );
|
366
|
+
}
|
367
|
+
}
|
240
368
|
|
241
|
-
|
242
|
-
|
243
|
-
if ( arguments.length === 2 ) {
|
244
|
-
callback = expected;
|
245
|
-
expected = null;
|
246
|
-
}
|
369
|
+
function runLoggingCallbacks( key, args ) {
|
370
|
+
var i, l, callbacks;
|
247
371
|
|
248
|
-
|
249
|
-
|
372
|
+
callbacks = config.callbacks[ key ];
|
373
|
+
for ( i = 0, l = callbacks.length; i < l; i++ ) {
|
374
|
+
callbacks[ i ]( args );
|
375
|
+
}
|
376
|
+
}
|
250
377
|
|
251
|
-
|
252
|
-
|
378
|
+
// DEPRECATED: This will be removed on 2.0.0+
|
379
|
+
// This function verifies if the loggingCallbacks were modified by the user
|
380
|
+
// If so, it will restore it, assign the given callback and print a console warning
|
381
|
+
function verifyLoggingCallbacks() {
|
382
|
+
var loggingCallback, userCallback;
|
253
383
|
|
254
|
-
|
255
|
-
|
256
|
-
expected = null;
|
257
|
-
}
|
384
|
+
for ( loggingCallback in loggingCallbacks ) {
|
385
|
+
if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
|
258
386
|
|
259
|
-
|
260
|
-
testName: testName,
|
261
|
-
expected: expected,
|
262
|
-
async: async,
|
263
|
-
callback: callback
|
264
|
-
});
|
387
|
+
userCallback = QUnit[ loggingCallback ];
|
265
388
|
|
266
|
-
|
267
|
-
|
389
|
+
// Restore the callback function
|
390
|
+
QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
|
268
391
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
392
|
+
// Assign the deprecated given callback
|
393
|
+
QUnit[ loggingCallback ]( userCallback );
|
394
|
+
|
395
|
+
if ( global.console && global.console.warn ) {
|
396
|
+
global.console.warn(
|
397
|
+
"QUnit." + loggingCallback + " was replaced with a new value.\n" +
|
398
|
+
"Please, check out the documentation on how to apply logging callbacks.\n" +
|
399
|
+
"Reference: http://api.qunitjs.com/category/callbacks/"
|
400
|
+
);
|
401
|
+
}
|
402
|
+
}
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
406
|
+
( function() {
|
407
|
+
if ( !defined.document ) {
|
408
|
+
return;
|
409
|
+
}
|
410
|
+
|
411
|
+
// `onErrorFnPrev` initialized at top of scope
|
412
|
+
// Preserve other handlers
|
413
|
+
var onErrorFnPrev = window.onerror;
|
414
|
+
|
415
|
+
// Cover uncaught exceptions
|
416
|
+
// Returning true will suppress the default browser handler,
|
417
|
+
// returning false will let it run.
|
418
|
+
window.onerror = function( error, filePath, linerNr ) {
|
419
|
+
var ret = false;
|
420
|
+
if ( onErrorFnPrev ) {
|
421
|
+
ret = onErrorFnPrev( error, filePath, linerNr );
|
422
|
+
}
|
423
|
+
|
424
|
+
// Treat return value as window.onerror itself does,
|
425
|
+
// Only do our handling if not suppressed.
|
426
|
+
if ( ret !== true ) {
|
427
|
+
if ( QUnit.config.current ) {
|
428
|
+
if ( QUnit.config.current.ignoreGlobalErrors ) {
|
429
|
+
return true;
|
430
|
+
}
|
431
|
+
QUnit.pushFailure( error, filePath + ":" + linerNr );
|
432
|
+
} else {
|
433
|
+
QUnit.test( "global failure", extend(function() {
|
434
|
+
QUnit.pushFailure( error, filePath + ":" + linerNr );
|
435
|
+
}, { validTest: true } ) );
|
436
|
+
}
|
437
|
+
return false;
|
438
|
+
}
|
439
|
+
|
440
|
+
return ret;
|
441
|
+
};
|
442
|
+
} )();
|
443
|
+
|
444
|
+
QUnit.urlParams = urlParams;
|
445
|
+
|
446
|
+
// Figure out if we're running the tests from a server or not
|
447
|
+
QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
|
448
|
+
|
449
|
+
// Expose the current QUnit version
|
450
|
+
QUnit.version = "1.20.0";
|
451
|
+
|
452
|
+
extend( QUnit, {
|
453
|
+
|
454
|
+
// call on start of module test to prepend name to all tests
|
455
|
+
module: function( name, testEnvironment, executeNow ) {
|
456
|
+
var module, moduleFns;
|
457
|
+
var currentModule = config.currentModule;
|
458
|
+
|
459
|
+
if ( arguments.length === 2 ) {
|
460
|
+
if ( testEnvironment instanceof Function ) {
|
461
|
+
executeNow = testEnvironment;
|
462
|
+
testEnvironment = undefined;
|
463
|
+
}
|
464
|
+
}
|
465
|
+
|
466
|
+
// DEPRECATED: handles setup/teardown functions,
|
467
|
+
// beforeEach and afterEach should be used instead
|
468
|
+
if ( testEnvironment && testEnvironment.setup ) {
|
469
|
+
testEnvironment.beforeEach = testEnvironment.setup;
|
470
|
+
delete testEnvironment.setup;
|
471
|
+
}
|
472
|
+
if ( testEnvironment && testEnvironment.teardown ) {
|
473
|
+
testEnvironment.afterEach = testEnvironment.teardown;
|
474
|
+
delete testEnvironment.teardown;
|
475
|
+
}
|
476
|
+
|
477
|
+
module = createModule();
|
478
|
+
|
479
|
+
moduleFns = {
|
480
|
+
beforeEach: setHook( module, "beforeEach" ),
|
481
|
+
afterEach: setHook( module, "afterEach" )
|
482
|
+
};
|
483
|
+
|
484
|
+
if ( executeNow instanceof Function ) {
|
485
|
+
config.moduleStack.push( module );
|
486
|
+
setCurrentModule( module );
|
487
|
+
executeNow.call( module.testEnvironment, moduleFns );
|
488
|
+
config.moduleStack.pop();
|
489
|
+
module = module.parentModule || currentModule;
|
490
|
+
}
|
491
|
+
|
492
|
+
setCurrentModule( module );
|
493
|
+
|
494
|
+
function createModule() {
|
495
|
+
var parentModule = config.moduleStack.length ?
|
496
|
+
config.moduleStack.slice( -1 )[ 0 ] : null;
|
497
|
+
var moduleName = parentModule !== null ?
|
498
|
+
[ parentModule.name, name ].join( " > " ) : name;
|
499
|
+
var module = {
|
500
|
+
name: moduleName,
|
501
|
+
parentModule: parentModule,
|
502
|
+
tests: []
|
503
|
+
};
|
504
|
+
|
505
|
+
var env = {};
|
506
|
+
if ( parentModule ) {
|
507
|
+
extend( env, parentModule.testEnvironment );
|
508
|
+
delete env.beforeEach;
|
509
|
+
delete env.afterEach;
|
510
|
+
}
|
511
|
+
extend( env, testEnvironment );
|
512
|
+
module.testEnvironment = env;
|
513
|
+
|
514
|
+
config.modules.push( module );
|
515
|
+
return module;
|
516
|
+
}
|
517
|
+
|
518
|
+
function setCurrentModule( module ) {
|
519
|
+
config.currentModule = module;
|
520
|
+
}
|
274
521
|
|
275
|
-
test.queue();
|
276
522
|
},
|
277
523
|
|
524
|
+
// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
|
525
|
+
asyncTest: asyncTest,
|
526
|
+
|
527
|
+
test: test,
|
528
|
+
|
529
|
+
skip: skip,
|
530
|
+
|
531
|
+
only: only,
|
532
|
+
|
278
533
|
// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
|
279
534
|
// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
|
280
535
|
start: function( count ) {
|
@@ -301,6 +556,17 @@ extend( QUnit, {
|
|
301
556
|
// If a test is running, adjust its semaphore
|
302
557
|
config.current.semaphore -= count || 1;
|
303
558
|
|
559
|
+
// If semaphore is non-numeric, throw error
|
560
|
+
if ( isNaN( config.current.semaphore ) ) {
|
561
|
+
config.current.semaphore = 0;
|
562
|
+
|
563
|
+
QUnit.pushFailure(
|
564
|
+
"Called start() with a non-numeric decrement.",
|
565
|
+
sourceFromStacktrace( 2 )
|
566
|
+
);
|
567
|
+
return;
|
568
|
+
}
|
569
|
+
|
304
570
|
// Don't start until equal number of stop-calls
|
305
571
|
if ( config.current.semaphore > 0 ) {
|
306
572
|
return;
|
@@ -337,43 +603,9 @@ extend( QUnit, {
|
|
337
603
|
|
338
604
|
config: config,
|
339
605
|
|
340
|
-
|
341
|
-
is: function( type, obj ) {
|
342
|
-
return QUnit.objectType( obj ) === type;
|
343
|
-
},
|
344
|
-
|
345
|
-
objectType: function( obj ) {
|
346
|
-
if ( typeof obj === "undefined" ) {
|
347
|
-
return "undefined";
|
348
|
-
}
|
349
|
-
|
350
|
-
// Consider: typeof null === object
|
351
|
-
if ( obj === null ) {
|
352
|
-
return "null";
|
353
|
-
}
|
354
|
-
|
355
|
-
var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
|
356
|
-
type = match && match[ 1 ] || "";
|
606
|
+
is: is,
|
357
607
|
|
358
|
-
|
359
|
-
case "Number":
|
360
|
-
if ( isNaN( obj ) ) {
|
361
|
-
return "nan";
|
362
|
-
}
|
363
|
-
return "number";
|
364
|
-
case "String":
|
365
|
-
case "Boolean":
|
366
|
-
case "Array":
|
367
|
-
case "Date":
|
368
|
-
case "RegExp":
|
369
|
-
case "Function":
|
370
|
-
return type.toLowerCase();
|
371
|
-
}
|
372
|
-
if ( typeof obj === "object" ) {
|
373
|
-
return "object";
|
374
|
-
}
|
375
|
-
return undefined;
|
376
|
-
},
|
608
|
+
objectType: objectType,
|
377
609
|
|
378
610
|
extend: extend,
|
379
611
|
|
@@ -395,77 +627,115 @@ extend( QUnit, {
|
|
395
627
|
if ( config.autostart ) {
|
396
628
|
resumeProcessing();
|
397
629
|
}
|
630
|
+
},
|
631
|
+
|
632
|
+
stack: function( offset ) {
|
633
|
+
offset = ( offset || 0 ) + 2;
|
634
|
+
return sourceFromStacktrace( offset );
|
398
635
|
}
|
399
636
|
});
|
400
637
|
|
401
|
-
|
402
|
-
(function() {
|
403
|
-
var i, l, key,
|
404
|
-
callbacks = [ "begin", "done", "log", "testStart", "testDone",
|
405
|
-
"moduleStart", "moduleDone" ];
|
638
|
+
registerLoggingCallbacks( QUnit );
|
406
639
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
throw new Error(
|
411
|
-
"QUnit logging methods require a callback function as their first parameters."
|
412
|
-
);
|
413
|
-
}
|
640
|
+
function begin() {
|
641
|
+
var i, l,
|
642
|
+
modulesLog = [];
|
414
643
|
|
415
|
-
|
416
|
-
|
644
|
+
// If the test run hasn't officially begun yet
|
645
|
+
if ( !config.started ) {
|
417
646
|
|
418
|
-
//
|
419
|
-
|
420
|
-
// at verifyLoggingCallbacks() if modified
|
421
|
-
loggingCallbacks[ key ] = loggingCallback;
|
647
|
+
// Record the time of the test run's beginning
|
648
|
+
config.started = now();
|
422
649
|
|
423
|
-
|
424
|
-
}
|
650
|
+
verifyLoggingCallbacks();
|
425
651
|
|
426
|
-
|
427
|
-
|
652
|
+
// Delete the loose unnamed module if unused.
|
653
|
+
if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
|
654
|
+
config.modules.shift();
|
655
|
+
}
|
428
656
|
|
429
|
-
//
|
430
|
-
|
431
|
-
|
657
|
+
// Avoid unnecessary information by not logging modules' test environments
|
658
|
+
for ( i = 0, l = config.modules.length; i < l; i++ ) {
|
659
|
+
modulesLog.push({
|
660
|
+
name: config.modules[ i ].name,
|
661
|
+
tests: config.modules[ i ].tests
|
662
|
+
});
|
432
663
|
}
|
433
664
|
|
434
|
-
|
665
|
+
// The test run is officially beginning now
|
666
|
+
runLoggingCallbacks( "begin", {
|
667
|
+
totalTests: Test.count,
|
668
|
+
modules: modulesLog
|
669
|
+
});
|
435
670
|
}
|
436
|
-
})();
|
437
671
|
|
438
|
-
|
439
|
-
|
440
|
-
|
672
|
+
config.blocking = false;
|
673
|
+
process( true );
|
674
|
+
}
|
441
675
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
window.onerror = function( error, filePath, linerNr ) {
|
446
|
-
var ret = false;
|
447
|
-
if ( onErrorFnPrev ) {
|
448
|
-
ret = onErrorFnPrev( error, filePath, linerNr );
|
676
|
+
function process( last ) {
|
677
|
+
function next() {
|
678
|
+
process( last );
|
449
679
|
}
|
680
|
+
var start = now();
|
681
|
+
config.depth = ( config.depth || 0 ) + 1;
|
450
682
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
683
|
+
while ( config.queue.length && !config.blocking ) {
|
684
|
+
if ( !defined.setTimeout || config.updateRate <= 0 ||
|
685
|
+
( ( now() - start ) < config.updateRate ) ) {
|
686
|
+
if ( config.current ) {
|
687
|
+
|
688
|
+
// Reset async tracking for each phase of the Test lifecycle
|
689
|
+
config.current.usedAsync = false;
|
457
690
|
}
|
458
|
-
|
691
|
+
config.queue.shift()();
|
459
692
|
} else {
|
460
|
-
|
461
|
-
|
462
|
-
}, { validTest: true } ) );
|
693
|
+
setTimeout( next, 13 );
|
694
|
+
break;
|
463
695
|
}
|
464
|
-
return false;
|
465
696
|
}
|
697
|
+
config.depth--;
|
698
|
+
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
|
699
|
+
done();
|
700
|
+
}
|
701
|
+
}
|
466
702
|
|
467
|
-
|
468
|
-
|
703
|
+
function pauseProcessing() {
|
704
|
+
config.blocking = true;
|
705
|
+
|
706
|
+
if ( config.testTimeout && defined.setTimeout ) {
|
707
|
+
clearTimeout( config.timeout );
|
708
|
+
config.timeout = setTimeout(function() {
|
709
|
+
if ( config.current ) {
|
710
|
+
config.current.semaphore = 0;
|
711
|
+
QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
|
712
|
+
} else {
|
713
|
+
throw new Error( "Test timed out" );
|
714
|
+
}
|
715
|
+
resumeProcessing();
|
716
|
+
}, config.testTimeout );
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
function resumeProcessing() {
|
721
|
+
runStarted = true;
|
722
|
+
|
723
|
+
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
|
724
|
+
if ( defined.setTimeout ) {
|
725
|
+
setTimeout(function() {
|
726
|
+
if ( config.current && config.current.semaphore > 0 ) {
|
727
|
+
return;
|
728
|
+
}
|
729
|
+
if ( config.timeout ) {
|
730
|
+
clearTimeout( config.timeout );
|
731
|
+
}
|
732
|
+
|
733
|
+
begin();
|
734
|
+
}, 13 );
|
735
|
+
} else {
|
736
|
+
begin();
|
737
|
+
}
|
738
|
+
}
|
469
739
|
|
470
740
|
function done() {
|
471
741
|
var runtime, passed;
|
@@ -496,312 +766,34 @@ function done() {
|
|
496
766
|
});
|
497
767
|
}
|
498
768
|
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
var stack, include, i;
|
769
|
+
function setHook( module, hookName ) {
|
770
|
+
if ( module.testEnvironment === undefined ) {
|
771
|
+
module.testEnvironment = {};
|
772
|
+
}
|
505
773
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
}
|
511
|
-
if ( fileName ) {
|
512
|
-
include = [];
|
513
|
-
for ( i = offset; i < stack.length; i++ ) {
|
514
|
-
if ( stack[ i ].indexOf( fileName ) !== -1 ) {
|
515
|
-
break;
|
516
|
-
}
|
517
|
-
include.push( stack[ i ] );
|
518
|
-
}
|
519
|
-
if ( include.length ) {
|
520
|
-
return include.join( "\n" );
|
521
|
-
}
|
522
|
-
}
|
523
|
-
return stack[ offset ];
|
774
|
+
return function( callback ) {
|
775
|
+
module.testEnvironment[ hookName ] = callback;
|
776
|
+
};
|
777
|
+
}
|
524
778
|
|
525
|
-
|
526
|
-
} else if ( e.sourceURL ) {
|
779
|
+
var focused = false;
|
527
780
|
|
528
|
-
|
529
|
-
|
530
|
-
return;
|
531
|
-
}
|
781
|
+
function Test( settings ) {
|
782
|
+
var i, l;
|
532
783
|
|
533
|
-
|
534
|
-
return e.sourceURL + ":" + e.line;
|
535
|
-
}
|
536
|
-
}
|
784
|
+
++Test.count;
|
537
785
|
|
538
|
-
|
539
|
-
|
786
|
+
extend( this, settings );
|
787
|
+
this.assertions = [];
|
788
|
+
this.semaphore = 0;
|
789
|
+
this.usedAsync = false;
|
790
|
+
this.module = config.currentModule;
|
791
|
+
this.stack = sourceFromStacktrace( 3 );
|
540
792
|
|
541
|
-
//
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
throw error;
|
546
|
-
} catch ( err ) {
|
547
|
-
error = err;
|
548
|
-
}
|
549
|
-
}
|
550
|
-
|
551
|
-
return extractStacktrace( error, offset );
|
552
|
-
}
|
553
|
-
|
554
|
-
function synchronize( callback, last ) {
|
555
|
-
if ( QUnit.objectType( callback ) === "array" ) {
|
556
|
-
while ( callback.length ) {
|
557
|
-
synchronize( callback.shift() );
|
558
|
-
}
|
559
|
-
return;
|
560
|
-
}
|
561
|
-
config.queue.push( callback );
|
562
|
-
|
563
|
-
if ( config.autorun && !config.blocking ) {
|
564
|
-
process( last );
|
565
|
-
}
|
566
|
-
}
|
567
|
-
|
568
|
-
function process( last ) {
|
569
|
-
function next() {
|
570
|
-
process( last );
|
571
|
-
}
|
572
|
-
var start = now();
|
573
|
-
config.depth = ( config.depth || 0 ) + 1;
|
574
|
-
|
575
|
-
while ( config.queue.length && !config.blocking ) {
|
576
|
-
if ( !defined.setTimeout || config.updateRate <= 0 ||
|
577
|
-
( ( now() - start ) < config.updateRate ) ) {
|
578
|
-
if ( config.current ) {
|
579
|
-
|
580
|
-
// Reset async tracking for each phase of the Test lifecycle
|
581
|
-
config.current.usedAsync = false;
|
582
|
-
}
|
583
|
-
config.queue.shift()();
|
584
|
-
} else {
|
585
|
-
setTimeout( next, 13 );
|
586
|
-
break;
|
587
|
-
}
|
588
|
-
}
|
589
|
-
config.depth--;
|
590
|
-
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
|
591
|
-
done();
|
592
|
-
}
|
593
|
-
}
|
594
|
-
|
595
|
-
function begin() {
|
596
|
-
var i, l,
|
597
|
-
modulesLog = [];
|
598
|
-
|
599
|
-
// If the test run hasn't officially begun yet
|
600
|
-
if ( !config.started ) {
|
601
|
-
|
602
|
-
// Record the time of the test run's beginning
|
603
|
-
config.started = now();
|
604
|
-
|
605
|
-
verifyLoggingCallbacks();
|
606
|
-
|
607
|
-
// Delete the loose unnamed module if unused.
|
608
|
-
if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
|
609
|
-
config.modules.shift();
|
610
|
-
}
|
611
|
-
|
612
|
-
// Avoid unnecessary information by not logging modules' test environments
|
613
|
-
for ( i = 0, l = config.modules.length; i < l; i++ ) {
|
614
|
-
modulesLog.push({
|
615
|
-
name: config.modules[ i ].name,
|
616
|
-
tests: config.modules[ i ].tests
|
617
|
-
});
|
618
|
-
}
|
619
|
-
|
620
|
-
// The test run is officially beginning now
|
621
|
-
runLoggingCallbacks( "begin", {
|
622
|
-
totalTests: Test.count,
|
623
|
-
modules: modulesLog
|
624
|
-
});
|
625
|
-
}
|
626
|
-
|
627
|
-
config.blocking = false;
|
628
|
-
process( true );
|
629
|
-
}
|
630
|
-
|
631
|
-
function resumeProcessing() {
|
632
|
-
runStarted = true;
|
633
|
-
|
634
|
-
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
|
635
|
-
if ( defined.setTimeout ) {
|
636
|
-
setTimeout(function() {
|
637
|
-
if ( config.current && config.current.semaphore > 0 ) {
|
638
|
-
return;
|
639
|
-
}
|
640
|
-
if ( config.timeout ) {
|
641
|
-
clearTimeout( config.timeout );
|
642
|
-
}
|
643
|
-
|
644
|
-
begin();
|
645
|
-
}, 13 );
|
646
|
-
} else {
|
647
|
-
begin();
|
648
|
-
}
|
649
|
-
}
|
650
|
-
|
651
|
-
function pauseProcessing() {
|
652
|
-
config.blocking = true;
|
653
|
-
|
654
|
-
if ( config.testTimeout && defined.setTimeout ) {
|
655
|
-
clearTimeout( config.timeout );
|
656
|
-
config.timeout = setTimeout(function() {
|
657
|
-
if ( config.current ) {
|
658
|
-
config.current.semaphore = 0;
|
659
|
-
QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
|
660
|
-
} else {
|
661
|
-
throw new Error( "Test timed out" );
|
662
|
-
}
|
663
|
-
resumeProcessing();
|
664
|
-
}, config.testTimeout );
|
665
|
-
}
|
666
|
-
}
|
667
|
-
|
668
|
-
function saveGlobal() {
|
669
|
-
config.pollution = [];
|
670
|
-
|
671
|
-
if ( config.noglobals ) {
|
672
|
-
for ( var key in window ) {
|
673
|
-
if ( hasOwn.call( window, key ) ) {
|
674
|
-
// in Opera sometimes DOM element ids show up here, ignore them
|
675
|
-
if ( /^qunit-test-output/.test( key ) ) {
|
676
|
-
continue;
|
677
|
-
}
|
678
|
-
config.pollution.push( key );
|
679
|
-
}
|
680
|
-
}
|
681
|
-
}
|
682
|
-
}
|
683
|
-
|
684
|
-
function checkPollution() {
|
685
|
-
var newGlobals,
|
686
|
-
deletedGlobals,
|
687
|
-
old = config.pollution;
|
688
|
-
|
689
|
-
saveGlobal();
|
690
|
-
|
691
|
-
newGlobals = diff( config.pollution, old );
|
692
|
-
if ( newGlobals.length > 0 ) {
|
693
|
-
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
|
694
|
-
}
|
695
|
-
|
696
|
-
deletedGlobals = diff( old, config.pollution );
|
697
|
-
if ( deletedGlobals.length > 0 ) {
|
698
|
-
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
|
699
|
-
}
|
700
|
-
}
|
701
|
-
|
702
|
-
// returns a new Array with the elements that are in a but not in b
|
703
|
-
function diff( a, b ) {
|
704
|
-
var i, j,
|
705
|
-
result = a.slice();
|
706
|
-
|
707
|
-
for ( i = 0; i < result.length; i++ ) {
|
708
|
-
for ( j = 0; j < b.length; j++ ) {
|
709
|
-
if ( result[ i ] === b[ j ] ) {
|
710
|
-
result.splice( i, 1 );
|
711
|
-
i--;
|
712
|
-
break;
|
713
|
-
}
|
714
|
-
}
|
715
|
-
}
|
716
|
-
return result;
|
717
|
-
}
|
718
|
-
|
719
|
-
function extend( a, b, undefOnly ) {
|
720
|
-
for ( var prop in b ) {
|
721
|
-
if ( hasOwn.call( b, prop ) ) {
|
722
|
-
|
723
|
-
// Avoid "Member not found" error in IE8 caused by messing with window.constructor
|
724
|
-
if ( !( prop === "constructor" && a === window ) ) {
|
725
|
-
if ( b[ prop ] === undefined ) {
|
726
|
-
delete a[ prop ];
|
727
|
-
} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
|
728
|
-
a[ prop ] = b[ prop ];
|
729
|
-
}
|
730
|
-
}
|
731
|
-
}
|
732
|
-
}
|
733
|
-
|
734
|
-
return a;
|
735
|
-
}
|
736
|
-
|
737
|
-
function runLoggingCallbacks( key, args ) {
|
738
|
-
var i, l, callbacks;
|
739
|
-
|
740
|
-
callbacks = config.callbacks[ key ];
|
741
|
-
for ( i = 0, l = callbacks.length; i < l; i++ ) {
|
742
|
-
callbacks[ i ]( args );
|
743
|
-
}
|
744
|
-
}
|
745
|
-
|
746
|
-
// DEPRECATED: This will be removed on 2.0.0+
|
747
|
-
// This function verifies if the loggingCallbacks were modified by the user
|
748
|
-
// If so, it will restore it, assign the given callback and print a console warning
|
749
|
-
function verifyLoggingCallbacks() {
|
750
|
-
var loggingCallback, userCallback;
|
751
|
-
|
752
|
-
for ( loggingCallback in loggingCallbacks ) {
|
753
|
-
if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
|
754
|
-
|
755
|
-
userCallback = QUnit[ loggingCallback ];
|
756
|
-
|
757
|
-
// Restore the callback function
|
758
|
-
QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
|
759
|
-
|
760
|
-
// Assign the deprecated given callback
|
761
|
-
QUnit[ loggingCallback ]( userCallback );
|
762
|
-
|
763
|
-
if ( window.console && window.console.warn ) {
|
764
|
-
window.console.warn(
|
765
|
-
"QUnit." + loggingCallback + " was replaced with a new value.\n" +
|
766
|
-
"Please, check out the documentation on how to apply logging callbacks.\n" +
|
767
|
-
"Reference: http://api.qunitjs.com/category/callbacks/"
|
768
|
-
);
|
769
|
-
}
|
770
|
-
}
|
771
|
-
}
|
772
|
-
}
|
773
|
-
|
774
|
-
// from jquery.js
|
775
|
-
function inArray( elem, array ) {
|
776
|
-
if ( array.indexOf ) {
|
777
|
-
return array.indexOf( elem );
|
778
|
-
}
|
779
|
-
|
780
|
-
for ( var i = 0, length = array.length; i < length; i++ ) {
|
781
|
-
if ( array[ i ] === elem ) {
|
782
|
-
return i;
|
783
|
-
}
|
784
|
-
}
|
785
|
-
|
786
|
-
return -1;
|
787
|
-
}
|
788
|
-
|
789
|
-
function Test( settings ) {
|
790
|
-
var i, l;
|
791
|
-
|
792
|
-
++Test.count;
|
793
|
-
|
794
|
-
extend( this, settings );
|
795
|
-
this.assertions = [];
|
796
|
-
this.semaphore = 0;
|
797
|
-
this.usedAsync = false;
|
798
|
-
this.module = config.currentModule;
|
799
|
-
this.stack = sourceFromStacktrace( 3 );
|
800
|
-
|
801
|
-
// Register unique strings
|
802
|
-
for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
|
803
|
-
if ( this.module.tests[ i ].name === this.testName ) {
|
804
|
-
this.testName += " ";
|
793
|
+
// Register unique strings
|
794
|
+
for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
|
795
|
+
if ( this.module.tests[ i ].name === this.testName ) {
|
796
|
+
this.testName += " ";
|
805
797
|
}
|
806
798
|
}
|
807
799
|
|
@@ -858,9 +850,11 @@ Test.prototype = {
|
|
858
850
|
|
859
851
|
config.current = this;
|
860
852
|
|
853
|
+
if ( this.module.testEnvironment ) {
|
854
|
+
delete this.module.testEnvironment.beforeEach;
|
855
|
+
delete this.module.testEnvironment.afterEach;
|
856
|
+
}
|
861
857
|
this.testEnvironment = extend( {}, this.module.testEnvironment );
|
862
|
-
delete this.testEnvironment.beforeEach;
|
863
|
-
delete this.testEnvironment.afterEach;
|
864
858
|
|
865
859
|
this.started = now();
|
866
860
|
runLoggingCallbacks( "testStart", {
|
@@ -886,14 +880,12 @@ Test.prototype = {
|
|
886
880
|
this.callbackStarted = now();
|
887
881
|
|
888
882
|
if ( config.notrycatch ) {
|
889
|
-
|
890
|
-
this.resolvePromise( promise );
|
883
|
+
runTest( this );
|
891
884
|
return;
|
892
885
|
}
|
893
886
|
|
894
887
|
try {
|
895
|
-
|
896
|
-
this.resolvePromise( promise );
|
888
|
+
runTest( this );
|
897
889
|
} catch ( e ) {
|
898
890
|
this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
|
899
891
|
this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
|
@@ -906,6 +898,11 @@ Test.prototype = {
|
|
906
898
|
QUnit.start();
|
907
899
|
}
|
908
900
|
}
|
901
|
+
|
902
|
+
function runTest( test ) {
|
903
|
+
promise = test.callback.call( test.testEnvironment, test.assert );
|
904
|
+
test.resolvePromise( promise );
|
905
|
+
}
|
909
906
|
},
|
910
907
|
|
911
908
|
after: function() {
|
@@ -918,16 +915,19 @@ Test.prototype = {
|
|
918
915
|
return function runHook() {
|
919
916
|
config.current = test;
|
920
917
|
if ( config.notrycatch ) {
|
921
|
-
|
922
|
-
test.resolvePromise( promise, hookName );
|
918
|
+
callHook();
|
923
919
|
return;
|
924
920
|
}
|
925
921
|
try {
|
926
|
-
|
927
|
-
test.resolvePromise( promise, hookName );
|
922
|
+
callHook();
|
928
923
|
} catch ( error ) {
|
929
924
|
test.pushFailure( hookName + " failed on " + test.testName + ": " +
|
930
|
-
|
925
|
+
( error.message || error ), extractStacktrace( error, 0 ) );
|
926
|
+
}
|
927
|
+
|
928
|
+
function callHook() {
|
929
|
+
promise = hook.call( test.testEnvironment, test.assert );
|
930
|
+
test.resolvePromise( promise, hookName );
|
931
931
|
}
|
932
932
|
};
|
933
933
|
},
|
@@ -936,16 +936,20 @@ Test.prototype = {
|
|
936
936
|
hooks: function( handler ) {
|
937
937
|
var hooks = [];
|
938
938
|
|
939
|
-
|
940
|
-
|
941
|
-
|
939
|
+
function processHooks( test, module ) {
|
940
|
+
if ( module.parentModule ) {
|
941
|
+
processHooks( test, module.parentModule );
|
942
|
+
}
|
943
|
+
if ( module.testEnvironment &&
|
944
|
+
QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
|
945
|
+
hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
|
946
|
+
}
|
942
947
|
}
|
943
948
|
|
944
|
-
|
945
|
-
|
946
|
-
|
949
|
+
// Hooks are ignored on skipped tests
|
950
|
+
if ( !this.skip ) {
|
951
|
+
processHooks( this, this.module );
|
947
952
|
}
|
948
|
-
|
949
953
|
return hooks;
|
950
954
|
},
|
951
955
|
|
@@ -990,6 +994,9 @@ Test.prototype = {
|
|
990
994
|
assertions: this.assertions,
|
991
995
|
testId: this.testId,
|
992
996
|
|
997
|
+
// Source of Test
|
998
|
+
source: this.stack,
|
999
|
+
|
993
1000
|
// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
|
994
1001
|
duration: this.runtime
|
995
1002
|
});
|
@@ -1003,7 +1010,7 @@ Test.prototype = {
|
|
1003
1010
|
},
|
1004
1011
|
|
1005
1012
|
queue: function() {
|
1006
|
-
var
|
1013
|
+
var priority,
|
1007
1014
|
test = this;
|
1008
1015
|
|
1009
1016
|
if ( !this.valid() ) {
|
@@ -1019,7 +1026,6 @@ Test.prototype = {
|
|
1019
1026
|
},
|
1020
1027
|
|
1021
1028
|
test.hooks( "beforeEach" ),
|
1022
|
-
|
1023
1029
|
function() {
|
1024
1030
|
test.run();
|
1025
1031
|
},
|
@@ -1035,19 +1041,14 @@ Test.prototype = {
|
|
1035
1041
|
]);
|
1036
1042
|
}
|
1037
1043
|
|
1038
|
-
//
|
1039
|
-
|
1040
|
-
bad = QUnit.config.reorder && defined.sessionStorage &&
|
1044
|
+
// Prioritize previously failed tests, detected from sessionStorage
|
1045
|
+
priority = QUnit.config.reorder && defined.sessionStorage &&
|
1041
1046
|
+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
|
1042
1047
|
|
1043
|
-
|
1044
|
-
run();
|
1045
|
-
} else {
|
1046
|
-
synchronize( run, true );
|
1047
|
-
}
|
1048
|
+
return synchronize( run, priority );
|
1048
1049
|
},
|
1049
1050
|
|
1050
|
-
push: function( result, actual, expected, message ) {
|
1051
|
+
push: function( result, actual, expected, message, negative ) {
|
1051
1052
|
var source,
|
1052
1053
|
details = {
|
1053
1054
|
module: this.module.name,
|
@@ -1057,6 +1058,7 @@ Test.prototype = {
|
|
1057
1058
|
actual: actual,
|
1058
1059
|
expected: expected,
|
1059
1060
|
testId: this.testId,
|
1061
|
+
negative: negative || false,
|
1060
1062
|
runtime: now() - this.started
|
1061
1063
|
};
|
1062
1064
|
|
@@ -1077,7 +1079,7 @@ Test.prototype = {
|
|
1077
1079
|
},
|
1078
1080
|
|
1079
1081
|
pushFailure: function( message, source, actual ) {
|
1080
|
-
if ( !this instanceof Test ) {
|
1082
|
+
if ( !( this instanceof Test ) ) {
|
1081
1083
|
throw new Error( "pushFailure() assertion outside test context, was " +
|
1082
1084
|
sourceFromStacktrace( 2 ) );
|
1083
1085
|
}
|
@@ -1113,7 +1115,7 @@ Test.prototype = {
|
|
1113
1115
|
QUnit.stop();
|
1114
1116
|
then.call(
|
1115
1117
|
promise,
|
1116
|
-
QUnit.start,
|
1118
|
+
function() { QUnit.start(); },
|
1117
1119
|
function( error ) {
|
1118
1120
|
message = "Promise rejected " +
|
1119
1121
|
( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
|
@@ -1137,6 +1139,17 @@ Test.prototype = {
|
|
1137
1139
|
module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
|
1138
1140
|
fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
|
1139
1141
|
|
1142
|
+
function testInModuleChain( testModule ) {
|
1143
|
+
var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
|
1144
|
+
if ( testModuleName === module ) {
|
1145
|
+
return true;
|
1146
|
+
} else if ( testModule.parentModule ) {
|
1147
|
+
return testInModuleChain( testModule.parentModule );
|
1148
|
+
} else {
|
1149
|
+
return false;
|
1150
|
+
}
|
1151
|
+
}
|
1152
|
+
|
1140
1153
|
// Internally-generated tests are always valid
|
1141
1154
|
if ( this.callback && this.callback.validTest ) {
|
1142
1155
|
return true;
|
@@ -1146,7 +1159,7 @@ Test.prototype = {
|
|
1146
1159
|
return false;
|
1147
1160
|
}
|
1148
1161
|
|
1149
|
-
if ( module && (
|
1162
|
+
if ( module && !testInModuleChain( this.module ) ) {
|
1150
1163
|
return false;
|
1151
1164
|
}
|
1152
1165
|
|
@@ -1167,7 +1180,6 @@ Test.prototype = {
|
|
1167
1180
|
// Otherwise, do the opposite
|
1168
1181
|
return !include;
|
1169
1182
|
}
|
1170
|
-
|
1171
1183
|
};
|
1172
1184
|
|
1173
1185
|
// Resets the test setup. Useful for tests that modify the DOM.
|
@@ -1180,7 +1192,7 @@ QUnit.reset = function() {
|
|
1180
1192
|
|
1181
1193
|
// Return on non-browser environments
|
1182
1194
|
// This is necessary to not break on node tests
|
1183
|
-
if (
|
1195
|
+
if ( !defined.document ) {
|
1184
1196
|
return;
|
1185
1197
|
}
|
1186
1198
|
|
@@ -1228,6 +1240,145 @@ function generateHash( module, testName ) {
|
|
1228
1240
|
return hex.slice( -8 );
|
1229
1241
|
}
|
1230
1242
|
|
1243
|
+
function synchronize( callback, priority ) {
|
1244
|
+
var last = !priority;
|
1245
|
+
|
1246
|
+
if ( QUnit.objectType( callback ) === "array" ) {
|
1247
|
+
while ( callback.length ) {
|
1248
|
+
synchronize( callback.shift() );
|
1249
|
+
}
|
1250
|
+
return;
|
1251
|
+
}
|
1252
|
+
|
1253
|
+
if ( priority ) {
|
1254
|
+
priorityFill( callback );
|
1255
|
+
} else {
|
1256
|
+
config.queue.push( callback );
|
1257
|
+
}
|
1258
|
+
|
1259
|
+
if ( config.autorun && !config.blocking ) {
|
1260
|
+
process( last );
|
1261
|
+
}
|
1262
|
+
}
|
1263
|
+
|
1264
|
+
// Place previously failed tests on a queue priority line, respecting the order they get assigned.
|
1265
|
+
function priorityFill( callback ) {
|
1266
|
+
var queue, prioritizedQueue;
|
1267
|
+
|
1268
|
+
queue = config.queue.slice( priorityFill.pos );
|
1269
|
+
prioritizedQueue = config.queue.slice( 0, -config.queue.length + priorityFill.pos );
|
1270
|
+
|
1271
|
+
queue.unshift( callback );
|
1272
|
+
queue.unshift.apply( queue, prioritizedQueue );
|
1273
|
+
|
1274
|
+
config.queue = queue;
|
1275
|
+
|
1276
|
+
priorityFill.pos += 1;
|
1277
|
+
}
|
1278
|
+
priorityFill.pos = 0;
|
1279
|
+
|
1280
|
+
function saveGlobal() {
|
1281
|
+
config.pollution = [];
|
1282
|
+
|
1283
|
+
if ( config.noglobals ) {
|
1284
|
+
for ( var key in global ) {
|
1285
|
+
if ( hasOwn.call( global, key ) ) {
|
1286
|
+
|
1287
|
+
// in Opera sometimes DOM element ids show up here, ignore them
|
1288
|
+
if ( /^qunit-test-output/.test( key ) ) {
|
1289
|
+
continue;
|
1290
|
+
}
|
1291
|
+
config.pollution.push( key );
|
1292
|
+
}
|
1293
|
+
}
|
1294
|
+
}
|
1295
|
+
}
|
1296
|
+
|
1297
|
+
function checkPollution() {
|
1298
|
+
var newGlobals,
|
1299
|
+
deletedGlobals,
|
1300
|
+
old = config.pollution;
|
1301
|
+
|
1302
|
+
saveGlobal();
|
1303
|
+
|
1304
|
+
newGlobals = diff( config.pollution, old );
|
1305
|
+
if ( newGlobals.length > 0 ) {
|
1306
|
+
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
|
1307
|
+
}
|
1308
|
+
|
1309
|
+
deletedGlobals = diff( old, config.pollution );
|
1310
|
+
if ( deletedGlobals.length > 0 ) {
|
1311
|
+
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
|
1312
|
+
}
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
// Will be exposed as QUnit.asyncTest
|
1316
|
+
function asyncTest( testName, expected, callback ) {
|
1317
|
+
if ( arguments.length === 2 ) {
|
1318
|
+
callback = expected;
|
1319
|
+
expected = null;
|
1320
|
+
}
|
1321
|
+
|
1322
|
+
QUnit.test( testName, expected, callback, true );
|
1323
|
+
}
|
1324
|
+
|
1325
|
+
// Will be exposed as QUnit.test
|
1326
|
+
function test( testName, expected, callback, async ) {
|
1327
|
+
if ( focused ) { return; }
|
1328
|
+
|
1329
|
+
var newTest;
|
1330
|
+
|
1331
|
+
if ( arguments.length === 2 ) {
|
1332
|
+
callback = expected;
|
1333
|
+
expected = null;
|
1334
|
+
}
|
1335
|
+
|
1336
|
+
newTest = new Test({
|
1337
|
+
testName: testName,
|
1338
|
+
expected: expected,
|
1339
|
+
async: async,
|
1340
|
+
callback: callback
|
1341
|
+
});
|
1342
|
+
|
1343
|
+
newTest.queue();
|
1344
|
+
}
|
1345
|
+
|
1346
|
+
// Will be exposed as QUnit.skip
|
1347
|
+
function skip( testName ) {
|
1348
|
+
if ( focused ) { return; }
|
1349
|
+
|
1350
|
+
var test = new Test({
|
1351
|
+
testName: testName,
|
1352
|
+
skip: true
|
1353
|
+
});
|
1354
|
+
|
1355
|
+
test.queue();
|
1356
|
+
}
|
1357
|
+
|
1358
|
+
// Will be exposed as QUnit.only
|
1359
|
+
function only( testName, expected, callback, async ) {
|
1360
|
+
var newTest;
|
1361
|
+
|
1362
|
+
if ( focused ) { return; }
|
1363
|
+
|
1364
|
+
QUnit.config.queue.length = 0;
|
1365
|
+
focused = true;
|
1366
|
+
|
1367
|
+
if ( arguments.length === 2 ) {
|
1368
|
+
callback = expected;
|
1369
|
+
expected = null;
|
1370
|
+
}
|
1371
|
+
|
1372
|
+
newTest = new Test({
|
1373
|
+
testName: testName,
|
1374
|
+
expected: expected,
|
1375
|
+
async: async,
|
1376
|
+
callback: callback
|
1377
|
+
});
|
1378
|
+
|
1379
|
+
newTest.queue();
|
1380
|
+
}
|
1381
|
+
|
1231
1382
|
function Assert( testContext ) {
|
1232
1383
|
this.test = testContext;
|
1233
1384
|
}
|
@@ -1245,30 +1396,41 @@ QUnit.assert = Assert.prototype = {
|
|
1245
1396
|
}
|
1246
1397
|
},
|
1247
1398
|
|
1248
|
-
// Increment this Test's semaphore counter, then return a
|
1399
|
+
// Increment this Test's semaphore counter, then return a function that
|
1249
1400
|
// decrements that counter a maximum of once.
|
1250
|
-
async: function() {
|
1401
|
+
async: function( count ) {
|
1251
1402
|
var test = this.test,
|
1252
|
-
popped = false
|
1403
|
+
popped = false,
|
1404
|
+
acceptCallCount = count;
|
1405
|
+
|
1406
|
+
if ( typeof acceptCallCount === "undefined" ) {
|
1407
|
+
acceptCallCount = 1;
|
1408
|
+
}
|
1253
1409
|
|
1254
1410
|
test.semaphore += 1;
|
1255
1411
|
test.usedAsync = true;
|
1256
1412
|
pauseProcessing();
|
1257
1413
|
|
1258
1414
|
return function done() {
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
resumeProcessing();
|
1263
|
-
} else {
|
1264
|
-
test.pushFailure( "Called the callback returned from `assert.async` more than once",
|
1415
|
+
|
1416
|
+
if ( popped ) {
|
1417
|
+
test.pushFailure( "Too many calls to the `assert.async` callback",
|
1265
1418
|
sourceFromStacktrace( 2 ) );
|
1419
|
+
return;
|
1420
|
+
}
|
1421
|
+
acceptCallCount -= 1;
|
1422
|
+
if ( acceptCallCount > 0 ) {
|
1423
|
+
return;
|
1266
1424
|
}
|
1425
|
+
|
1426
|
+
test.semaphore -= 1;
|
1427
|
+
popped = true;
|
1428
|
+
resumeProcessing();
|
1267
1429
|
};
|
1268
1430
|
},
|
1269
1431
|
|
1270
1432
|
// Exports test.push() to the user API
|
1271
|
-
push: function( /* result, actual, expected, message */ ) {
|
1433
|
+
push: function( /* result, actual, expected, message, negative */ ) {
|
1272
1434
|
var assert = this,
|
1273
1435
|
currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
|
1274
1436
|
|
@@ -1303,7 +1465,7 @@ QUnit.assert = Assert.prototype = {
|
|
1303
1465
|
notOk: function( result, message ) {
|
1304
1466
|
message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
|
1305
1467
|
QUnit.dump.parse( result ) );
|
1306
|
-
this.push( !result, result, false, message );
|
1468
|
+
this.push( !result, result, false, message, true );
|
1307
1469
|
},
|
1308
1470
|
|
1309
1471
|
equal: function( actual, expected, message ) {
|
@@ -1313,7 +1475,7 @@ QUnit.assert = Assert.prototype = {
|
|
1313
1475
|
|
1314
1476
|
notEqual: function( actual, expected, message ) {
|
1315
1477
|
/*jshint eqeqeq:false */
|
1316
|
-
this.push( expected != actual, actual, expected, message );
|
1478
|
+
this.push( expected != actual, actual, expected, message, true );
|
1317
1479
|
},
|
1318
1480
|
|
1319
1481
|
propEqual: function( actual, expected, message ) {
|
@@ -1325,7 +1487,7 @@ QUnit.assert = Assert.prototype = {
|
|
1325
1487
|
notPropEqual: function( actual, expected, message ) {
|
1326
1488
|
actual = objectValues( actual );
|
1327
1489
|
expected = objectValues( expected );
|
1328
|
-
this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
|
1490
|
+
this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
|
1329
1491
|
},
|
1330
1492
|
|
1331
1493
|
deepEqual: function( actual, expected, message ) {
|
@@ -1333,7 +1495,7 @@ QUnit.assert = Assert.prototype = {
|
|
1333
1495
|
},
|
1334
1496
|
|
1335
1497
|
notDeepEqual: function( actual, expected, message ) {
|
1336
|
-
this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
|
1498
|
+
this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
|
1337
1499
|
},
|
1338
1500
|
|
1339
1501
|
strictEqual: function( actual, expected, message ) {
|
@@ -1341,7 +1503,7 @@ QUnit.assert = Assert.prototype = {
|
|
1341
1503
|
},
|
1342
1504
|
|
1343
1505
|
notStrictEqual: function( actual, expected, message ) {
|
1344
|
-
this.push( expected !== actual, actual, expected, message );
|
1506
|
+
this.push( expected !== actual, actual, expected, message, true );
|
1345
1507
|
},
|
1346
1508
|
|
1347
1509
|
"throws": function( block, expected, message ) {
|
@@ -1401,229 +1563,305 @@ QUnit.assert = Assert.prototype = {
|
|
1401
1563
|
}
|
1402
1564
|
};
|
1403
1565
|
|
1404
|
-
// Provide an alternative to assert.throws(), for
|
1566
|
+
// Provide an alternative to assert.throws(), for environments that consider throws a reserved word
|
1405
1567
|
// Known to us are: Closure Compiler, Narwhal
|
1406
1568
|
(function() {
|
1407
1569
|
/*jshint sub:true */
|
1408
1570
|
Assert.prototype.raises = Assert.prototype[ "throws" ];
|
1409
1571
|
}());
|
1410
1572
|
|
1573
|
+
function errorString( error ) {
|
1574
|
+
var name, message,
|
1575
|
+
resultErrorString = error.toString();
|
1576
|
+
if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
|
1577
|
+
name = error.name ? error.name.toString() : "Error";
|
1578
|
+
message = error.message ? error.message.toString() : "";
|
1579
|
+
if ( name && message ) {
|
1580
|
+
return name + ": " + message;
|
1581
|
+
} else if ( name ) {
|
1582
|
+
return name;
|
1583
|
+
} else if ( message ) {
|
1584
|
+
return message;
|
1585
|
+
} else {
|
1586
|
+
return "Error";
|
1587
|
+
}
|
1588
|
+
} else {
|
1589
|
+
return resultErrorString;
|
1590
|
+
}
|
1591
|
+
}
|
1592
|
+
|
1411
1593
|
// Test for equality any JavaScript type.
|
1412
|
-
// Author: Philippe
|
1594
|
+
// Author: Philippe Rathé <prathe@gmail.com>
|
1413
1595
|
QUnit.equiv = (function() {
|
1414
1596
|
|
1415
|
-
//
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
} else {
|
1422
|
-
return callbacks[ prop ]; // or undefined
|
1423
|
-
}
|
1424
|
-
}
|
1425
|
-
}
|
1597
|
+
// Stack to decide between skip/abort functions
|
1598
|
+
var callers = [];
|
1599
|
+
|
1600
|
+
// Stack to avoiding loops from circular referencing
|
1601
|
+
var parents = [];
|
1602
|
+
var parentsB = [];
|
1426
1603
|
|
1427
|
-
|
1428
|
-
var innerEquiv,
|
1604
|
+
function useStrictEquality( b, a ) {
|
1429
1605
|
|
1430
|
-
|
1431
|
-
|
1606
|
+
/*jshint eqeqeq:false */
|
1607
|
+
if ( b instanceof a.constructor || a instanceof b.constructor ) {
|
1608
|
+
|
1609
|
+
// To catch short annotation VS 'new' annotation of a declaration. e.g.:
|
1610
|
+
// `var i = 1;`
|
1611
|
+
// `var j = new Number(1);`
|
1612
|
+
return a == b;
|
1613
|
+
} else {
|
1614
|
+
return a === b;
|
1615
|
+
}
|
1616
|
+
}
|
1432
1617
|
|
1433
|
-
|
1434
|
-
|
1435
|
-
parentsB = [],
|
1618
|
+
function compareConstructors( a, b ) {
|
1619
|
+
var getProto = Object.getPrototypeOf || function( obj ) {
|
1436
1620
|
|
1437
|
-
|
1438
|
-
/* jshint camelcase: false, proto: true */
|
1621
|
+
/*jshint proto: true */
|
1439
1622
|
return obj.__proto__;
|
1440
|
-
}
|
1441
|
-
|
1623
|
+
};
|
1624
|
+
var protoA = getProto( a );
|
1625
|
+
var protoB = getProto( b );
|
1442
1626
|
|
1443
|
-
|
1444
|
-
|
1627
|
+
// Comparing constructors is more strict than using `instanceof`
|
1628
|
+
if ( a.constructor === b.constructor ) {
|
1629
|
+
return true;
|
1630
|
+
}
|
1445
1631
|
|
1446
|
-
|
1447
|
-
|
1632
|
+
// Ref #851
|
1633
|
+
// If the obj prototype descends from a null constructor, treat it
|
1634
|
+
// as a null prototype.
|
1635
|
+
if ( protoA && protoA.constructor === null ) {
|
1636
|
+
protoA = null;
|
1637
|
+
}
|
1638
|
+
if ( protoB && protoB.constructor === null ) {
|
1639
|
+
protoB = null;
|
1640
|
+
}
|
1448
1641
|
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
return a === b;
|
1456
|
-
}
|
1457
|
-
}
|
1642
|
+
// Allow objects with no prototype to be equivalent to
|
1643
|
+
// objects with Object as their constructor.
|
1644
|
+
if ( ( protoA === null && protoB === Object.prototype ) ||
|
1645
|
+
( protoB === null && protoA === Object.prototype ) ) {
|
1646
|
+
return true;
|
1647
|
+
}
|
1458
1648
|
|
1459
|
-
|
1460
|
-
|
1461
|
-
"boolean": useStrictEquality,
|
1462
|
-
"number": useStrictEquality,
|
1463
|
-
"null": useStrictEquality,
|
1464
|
-
"undefined": useStrictEquality,
|
1649
|
+
return false;
|
1650
|
+
}
|
1465
1651
|
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1652
|
+
var callbacks = {
|
1653
|
+
"string": useStrictEquality,
|
1654
|
+
"boolean": useStrictEquality,
|
1655
|
+
"number": useStrictEquality,
|
1656
|
+
"null": useStrictEquality,
|
1657
|
+
"undefined": useStrictEquality,
|
1658
|
+
"symbol": useStrictEquality,
|
1469
1659
|
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1660
|
+
"nan": function( b ) {
|
1661
|
+
return isNaN( b );
|
1662
|
+
},
|
1473
1663
|
|
1474
|
-
|
1475
|
-
|
1664
|
+
"date": function( b, a ) {
|
1665
|
+
return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
|
1666
|
+
},
|
1476
1667
|
|
1477
|
-
|
1478
|
-
|
1668
|
+
"regexp": function( b, a ) {
|
1669
|
+
return QUnit.objectType( b ) === "regexp" &&
|
1479
1670
|
|
1480
|
-
|
1481
|
-
|
1671
|
+
// The regex itself
|
1672
|
+
a.source === b.source &&
|
1482
1673
|
|
1483
|
-
|
1484
|
-
|
1485
|
-
a.multiline === b.multiline &&
|
1486
|
-
a.sticky === b.sticky;
|
1487
|
-
},
|
1674
|
+
// And its modifiers
|
1675
|
+
a.global === b.global &&
|
1488
1676
|
|
1489
|
-
//
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
return caller !== Object && typeof caller !== "undefined";
|
1495
|
-
},
|
1677
|
+
// (gmi) ...
|
1678
|
+
a.ignoreCase === b.ignoreCase &&
|
1679
|
+
a.multiline === b.multiline &&
|
1680
|
+
a.sticky === b.sticky;
|
1681
|
+
},
|
1496
1682
|
|
1497
|
-
|
1498
|
-
|
1683
|
+
// - skip when the property is a method of an instance (OOP)
|
1684
|
+
// - abort otherwise,
|
1685
|
+
// initial === would have catch identical references anyway
|
1686
|
+
"function": function() {
|
1687
|
+
var caller = callers[ callers.length - 1 ];
|
1688
|
+
return caller !== Object && typeof caller !== "undefined";
|
1689
|
+
},
|
1499
1690
|
|
1500
|
-
|
1501
|
-
|
1502
|
-
return false;
|
1503
|
-
}
|
1691
|
+
"array": function( b, a ) {
|
1692
|
+
var i, j, len, loop, aCircular, bCircular;
|
1504
1693
|
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
}
|
1694
|
+
// b could be an object literal here
|
1695
|
+
if ( QUnit.objectType( b ) !== "array" ) {
|
1696
|
+
return false;
|
1697
|
+
}
|
1510
1698
|
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
}
|
1529
|
-
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
|
1699
|
+
len = a.length;
|
1700
|
+
if ( len !== b.length ) {
|
1701
|
+
// safe and faster
|
1702
|
+
return false;
|
1703
|
+
}
|
1704
|
+
|
1705
|
+
// Track reference to avoid circular references
|
1706
|
+
parents.push( a );
|
1707
|
+
parentsB.push( b );
|
1708
|
+
for ( i = 0; i < len; i++ ) {
|
1709
|
+
loop = false;
|
1710
|
+
for ( j = 0; j < parents.length; j++ ) {
|
1711
|
+
aCircular = parents[ j ] === a[ i ];
|
1712
|
+
bCircular = parentsB[ j ] === b[ i ];
|
1713
|
+
if ( aCircular || bCircular ) {
|
1714
|
+
if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
|
1715
|
+
loop = true;
|
1716
|
+
} else {
|
1530
1717
|
parents.pop();
|
1531
1718
|
parentsB.pop();
|
1532
1719
|
return false;
|
1533
1720
|
}
|
1534
1721
|
}
|
1722
|
+
}
|
1723
|
+
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
|
1535
1724
|
parents.pop();
|
1536
1725
|
parentsB.pop();
|
1537
|
-
return
|
1538
|
-
}
|
1726
|
+
return false;
|
1727
|
+
}
|
1728
|
+
}
|
1729
|
+
parents.pop();
|
1730
|
+
parentsB.pop();
|
1731
|
+
return true;
|
1732
|
+
},
|
1539
1733
|
|
1540
|
-
|
1734
|
+
"set": function( b, a ) {
|
1735
|
+
var aArray, bArray;
|
1541
1736
|
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
aProperties = [],
|
1547
|
-
bProperties = [];
|
1737
|
+
// `b` could be any object here
|
1738
|
+
if ( QUnit.objectType( b ) !== "set" ) {
|
1739
|
+
return false;
|
1740
|
+
}
|
1548
1741
|
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1742
|
+
aArray = [];
|
1743
|
+
a.forEach( function( v ) {
|
1744
|
+
aArray.push( v );
|
1745
|
+
});
|
1746
|
+
bArray = [];
|
1747
|
+
b.forEach( function( v ) {
|
1748
|
+
bArray.push( v );
|
1749
|
+
});
|
1552
1750
|
|
1553
|
-
|
1554
|
-
|
1555
|
-
if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
|
1556
|
-
( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
|
1557
|
-
return false;
|
1558
|
-
}
|
1559
|
-
}
|
1751
|
+
return innerEquiv( bArray, aArray );
|
1752
|
+
},
|
1560
1753
|
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1754
|
+
"map": function( b, a ) {
|
1755
|
+
var aArray, bArray;
|
1756
|
+
|
1757
|
+
// `b` could be any object here
|
1758
|
+
if ( QUnit.objectType( b ) !== "map" ) {
|
1759
|
+
return false;
|
1760
|
+
}
|
1761
|
+
|
1762
|
+
aArray = [];
|
1763
|
+
a.forEach( function( v, k ) {
|
1764
|
+
aArray.push( [ k, v ] );
|
1765
|
+
});
|
1766
|
+
bArray = [];
|
1767
|
+
b.forEach( function( v, k ) {
|
1768
|
+
bArray.push( [ k, v ] );
|
1769
|
+
});
|
1770
|
+
|
1771
|
+
return innerEquiv( bArray, aArray );
|
1772
|
+
},
|
1773
|
+
|
1774
|
+
"object": function( b, a ) {
|
1775
|
+
var i, j, loop, aCircular, bCircular;
|
1776
|
+
|
1777
|
+
// Default to true
|
1778
|
+
var eq = true;
|
1779
|
+
var aProperties = [];
|
1780
|
+
var bProperties = [];
|
1781
|
+
|
1782
|
+
if ( compareConstructors( a, b ) === false ) {
|
1783
|
+
return false;
|
1784
|
+
}
|
1785
|
+
|
1786
|
+
// Stack constructor before traversing properties
|
1787
|
+
callers.push( a.constructor );
|
1788
|
+
|
1789
|
+
// Track reference to avoid circular references
|
1790
|
+
parents.push( a );
|
1791
|
+
parentsB.push( b );
|
1792
|
+
|
1793
|
+
// Be strict: don't ensure hasOwnProperty and go deep
|
1794
|
+
for ( i in a ) {
|
1795
|
+
loop = false;
|
1796
|
+
for ( j = 0; j < parents.length; j++ ) {
|
1797
|
+
aCircular = parents[ j ] === a[ i ];
|
1798
|
+
bCircular = parentsB[ j ] === b[ i ];
|
1799
|
+
if ( aCircular || bCircular ) {
|
1800
|
+
if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
|
1801
|
+
loop = true;
|
1802
|
+
} else {
|
1585
1803
|
eq = false;
|
1586
1804
|
break;
|
1587
1805
|
}
|
1588
1806
|
}
|
1807
|
+
}
|
1808
|
+
aProperties.push( i );
|
1809
|
+
if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
|
1810
|
+
eq = false;
|
1811
|
+
break;
|
1812
|
+
}
|
1813
|
+
}
|
1589
1814
|
|
1590
|
-
|
1591
|
-
|
1592
|
-
callers.pop(); // unstack, we are done
|
1815
|
+
parents.pop();
|
1816
|
+
parentsB.pop();
|
1593
1817
|
|
1594
|
-
|
1595
|
-
|
1596
|
-
}
|
1818
|
+
// Unstack, we are done
|
1819
|
+
callers.pop();
|
1597
1820
|
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1821
|
+
for ( i in b ) {
|
1822
|
+
|
1823
|
+
// Collect b's properties
|
1824
|
+
bProperties.push( i );
|
1825
|
+
}
|
1826
|
+
|
1827
|
+
// Ensures identical properties name
|
1828
|
+
return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
|
1829
|
+
}
|
1830
|
+
};
|
1603
1831
|
|
1604
|
-
|
1832
|
+
function typeEquiv( a, b ) {
|
1833
|
+
var prop = QUnit.objectType( a );
|
1834
|
+
return callbacks[ prop ]( b, a );
|
1835
|
+
}
|
1836
|
+
|
1837
|
+
// The real equiv function
|
1838
|
+
function innerEquiv() {
|
1605
1839
|
var args = [].slice.apply( arguments );
|
1606
1840
|
if ( args.length < 2 ) {
|
1607
|
-
|
1841
|
+
|
1842
|
+
// End transition
|
1843
|
+
return true;
|
1608
1844
|
}
|
1609
1845
|
|
1610
1846
|
return ( (function( a, b ) {
|
1611
1847
|
if ( a === b ) {
|
1612
|
-
|
1848
|
+
|
1849
|
+
// Catch the most you can
|
1850
|
+
return true;
|
1613
1851
|
} else if ( a === null || b === null || typeof a === "undefined" ||
|
1614
1852
|
typeof b === "undefined" ||
|
1615
1853
|
QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
|
1616
1854
|
|
1617
|
-
//
|
1855
|
+
// Don't lose time with error prone cases
|
1618
1856
|
return false;
|
1619
1857
|
} else {
|
1620
|
-
return
|
1858
|
+
return typeEquiv( a, b );
|
1621
1859
|
}
|
1622
1860
|
|
1623
|
-
|
1861
|
+
// Apply transition with (1..n) arguments
|
1624
1862
|
}( args[ 0 ], args[ 1 ] ) ) &&
|
1625
1863
|
innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
|
1626
|
-
}
|
1864
|
+
}
|
1627
1865
|
|
1628
1866
|
return innerEquiv;
|
1629
1867
|
}());
|
@@ -1632,7 +1870,7 @@ QUnit.equiv = (function() {
|
|
1632
1870
|
// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
|
1633
1871
|
QUnit.dump = (function() {
|
1634
1872
|
function quote( str ) {
|
1635
|
-
return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
|
1873
|
+
return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
|
1636
1874
|
}
|
1637
1875
|
function literal( o ) {
|
1638
1876
|
return o + "";
|
@@ -1839,1225 +2077,1255 @@ QUnit.dump = (function() {
|
|
1839
2077
|
}
|
1840
2078
|
}
|
1841
2079
|
}
|
1842
|
-
ret += close;
|
2080
|
+
ret += close;
|
2081
|
+
|
2082
|
+
// Show content of TextNode or CDATASection
|
2083
|
+
if ( node.nodeType === 3 || node.nodeType === 4 ) {
|
2084
|
+
ret += node.nodeValue;
|
2085
|
+
}
|
2086
|
+
|
2087
|
+
return ret + open + "/" + tag + close;
|
2088
|
+
},
|
2089
|
+
|
2090
|
+
// function calls it internally, it's the arguments part of the function
|
2091
|
+
functionArgs: function( fn ) {
|
2092
|
+
var args,
|
2093
|
+
l = fn.length;
|
2094
|
+
|
2095
|
+
if ( !l ) {
|
2096
|
+
return "";
|
2097
|
+
}
|
2098
|
+
|
2099
|
+
args = new Array( l );
|
2100
|
+
while ( l-- ) {
|
2101
|
+
|
2102
|
+
// 97 is 'a'
|
2103
|
+
args[ l ] = String.fromCharCode( 97 + l );
|
2104
|
+
}
|
2105
|
+
return " " + args.join( ", " ) + " ";
|
2106
|
+
},
|
2107
|
+
// object calls it internally, the key part of an item in a map
|
2108
|
+
key: quote,
|
2109
|
+
// function calls it internally, it's the content of the function
|
2110
|
+
functionCode: "[code]",
|
2111
|
+
// node calls it internally, it's an html attribute value
|
2112
|
+
attribute: quote,
|
2113
|
+
string: quote,
|
2114
|
+
date: quote,
|
2115
|
+
regexp: literal,
|
2116
|
+
number: literal,
|
2117
|
+
"boolean": literal
|
2118
|
+
},
|
2119
|
+
// if true, entities are escaped ( <, >, \t, space and \n )
|
2120
|
+
HTML: false,
|
2121
|
+
// indentation unit
|
2122
|
+
indentChar: " ",
|
2123
|
+
// if true, items in a collection, are separated by a \n, else just a space.
|
2124
|
+
multiline: true
|
2125
|
+
};
|
2126
|
+
|
2127
|
+
return dump;
|
2128
|
+
}());
|
2129
|
+
|
2130
|
+
// back compat
|
2131
|
+
QUnit.jsDump = QUnit.dump;
|
2132
|
+
|
2133
|
+
// For browser, export only select globals
|
2134
|
+
if ( defined.document ) {
|
2135
|
+
|
2136
|
+
// Deprecated
|
2137
|
+
// Extend assert methods to QUnit and Global scope through Backwards compatibility
|
2138
|
+
(function() {
|
2139
|
+
var i,
|
2140
|
+
assertions = Assert.prototype;
|
2141
|
+
|
2142
|
+
function applyCurrent( current ) {
|
2143
|
+
return function() {
|
2144
|
+
var assert = new Assert( QUnit.config.current );
|
2145
|
+
current.apply( assert, arguments );
|
2146
|
+
};
|
2147
|
+
}
|
2148
|
+
|
2149
|
+
for ( i in assertions ) {
|
2150
|
+
QUnit[ i ] = applyCurrent( assertions[ i ] );
|
2151
|
+
}
|
2152
|
+
})();
|
2153
|
+
|
2154
|
+
(function() {
|
2155
|
+
var i, l,
|
2156
|
+
keys = [
|
2157
|
+
"test",
|
2158
|
+
"module",
|
2159
|
+
"expect",
|
2160
|
+
"asyncTest",
|
2161
|
+
"start",
|
2162
|
+
"stop",
|
2163
|
+
"ok",
|
2164
|
+
"notOk",
|
2165
|
+
"equal",
|
2166
|
+
"notEqual",
|
2167
|
+
"propEqual",
|
2168
|
+
"notPropEqual",
|
2169
|
+
"deepEqual",
|
2170
|
+
"notDeepEqual",
|
2171
|
+
"strictEqual",
|
2172
|
+
"notStrictEqual",
|
2173
|
+
"throws",
|
2174
|
+
"raises"
|
2175
|
+
];
|
2176
|
+
|
2177
|
+
for ( i = 0, l = keys.length; i < l; i++ ) {
|
2178
|
+
window[ keys[ i ] ] = QUnit[ keys[ i ] ];
|
2179
|
+
}
|
2180
|
+
})();
|
2181
|
+
|
2182
|
+
window.QUnit = QUnit;
|
2183
|
+
}
|
2184
|
+
|
2185
|
+
// For nodejs
|
2186
|
+
if ( typeof module !== "undefined" && module && module.exports ) {
|
2187
|
+
module.exports = QUnit;
|
2188
|
+
|
2189
|
+
// For consistency with CommonJS environments' exports
|
2190
|
+
module.exports.QUnit = QUnit;
|
2191
|
+
}
|
2192
|
+
|
2193
|
+
// For CommonJS with exports, but without module.exports, like Rhino
|
2194
|
+
if ( typeof exports !== "undefined" && exports ) {
|
2195
|
+
exports.QUnit = QUnit;
|
2196
|
+
}
|
2197
|
+
|
2198
|
+
if ( typeof define === "function" && define.amd ) {
|
2199
|
+
define( function() {
|
2200
|
+
return QUnit;
|
2201
|
+
} );
|
2202
|
+
QUnit.config.autostart = false;
|
2203
|
+
}
|
2204
|
+
|
2205
|
+
/*
|
2206
|
+
* This file is a modified version of google-diff-match-patch's JavaScript implementation
|
2207
|
+
* (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
|
2208
|
+
* modifications are licensed as more fully set forth in LICENSE.txt.
|
2209
|
+
*
|
2210
|
+
* The original source of google-diff-match-patch is attributable and licensed as follows:
|
2211
|
+
*
|
2212
|
+
* Copyright 2006 Google Inc.
|
2213
|
+
* http://code.google.com/p/google-diff-match-patch/
|
2214
|
+
*
|
2215
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
2216
|
+
* you may not use this file except in compliance with the License.
|
2217
|
+
* You may obtain a copy of the License at
|
2218
|
+
*
|
2219
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
2220
|
+
*
|
2221
|
+
* Unless required by applicable law or agreed to in writing, software
|
2222
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
2223
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
2224
|
+
* See the License for the specific language governing permissions and
|
2225
|
+
* limitations under the License.
|
2226
|
+
*
|
2227
|
+
* More Info:
|
2228
|
+
* https://code.google.com/p/google-diff-match-patch/
|
2229
|
+
*
|
2230
|
+
* Usage: QUnit.diff(expected, actual)
|
2231
|
+
*
|
2232
|
+
*/
|
2233
|
+
QUnit.diff = ( function() {
|
2234
|
+
function DiffMatchPatch() {
|
2235
|
+
}
|
2236
|
+
|
2237
|
+
// DIFF FUNCTIONS
|
2238
|
+
|
2239
|
+
/**
|
2240
|
+
* The data structure representing a diff is an array of tuples:
|
2241
|
+
* [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
|
2242
|
+
* which means: delete 'Hello', add 'Goodbye' and keep ' world.'
|
2243
|
+
*/
|
2244
|
+
var DIFF_DELETE = -1,
|
2245
|
+
DIFF_INSERT = 1,
|
2246
|
+
DIFF_EQUAL = 0;
|
2247
|
+
|
2248
|
+
/**
|
2249
|
+
* Find the differences between two texts. Simplifies the problem by stripping
|
2250
|
+
* any common prefix or suffix off the texts before diffing.
|
2251
|
+
* @param {string} text1 Old string to be diffed.
|
2252
|
+
* @param {string} text2 New string to be diffed.
|
2253
|
+
* @param {boolean=} optChecklines Optional speedup flag. If present and false,
|
2254
|
+
* then don't run a line-level diff first to identify the changed areas.
|
2255
|
+
* Defaults to true, which does a faster, slightly less optimal diff.
|
2256
|
+
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2257
|
+
*/
|
2258
|
+
DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
|
2259
|
+
var deadline, checklines, commonlength,
|
2260
|
+
commonprefix, commonsuffix, diffs;
|
2261
|
+
|
2262
|
+
// The diff must be complete in up to 1 second.
|
2263
|
+
deadline = ( new Date() ).getTime() + 1000;
|
2264
|
+
|
2265
|
+
// Check for null inputs.
|
2266
|
+
if ( text1 === null || text2 === null ) {
|
2267
|
+
throw new Error( "Null input. (DiffMain)" );
|
2268
|
+
}
|
2269
|
+
|
2270
|
+
// Check for equality (speedup).
|
2271
|
+
if ( text1 === text2 ) {
|
2272
|
+
if ( text1 ) {
|
2273
|
+
return [
|
2274
|
+
[ DIFF_EQUAL, text1 ]
|
2275
|
+
];
|
2276
|
+
}
|
2277
|
+
return [];
|
2278
|
+
}
|
2279
|
+
|
2280
|
+
if ( typeof optChecklines === "undefined" ) {
|
2281
|
+
optChecklines = true;
|
2282
|
+
}
|
2283
|
+
|
2284
|
+
checklines = optChecklines;
|
2285
|
+
|
2286
|
+
// Trim off common prefix (speedup).
|
2287
|
+
commonlength = this.diffCommonPrefix( text1, text2 );
|
2288
|
+
commonprefix = text1.substring( 0, commonlength );
|
2289
|
+
text1 = text1.substring( commonlength );
|
2290
|
+
text2 = text2.substring( commonlength );
|
2291
|
+
|
2292
|
+
// Trim off common suffix (speedup).
|
2293
|
+
commonlength = this.diffCommonSuffix( text1, text2 );
|
2294
|
+
commonsuffix = text1.substring( text1.length - commonlength );
|
2295
|
+
text1 = text1.substring( 0, text1.length - commonlength );
|
2296
|
+
text2 = text2.substring( 0, text2.length - commonlength );
|
2297
|
+
|
2298
|
+
// Compute the diff on the middle block.
|
2299
|
+
diffs = this.diffCompute( text1, text2, checklines, deadline );
|
2300
|
+
|
2301
|
+
// Restore the prefix and suffix.
|
2302
|
+
if ( commonprefix ) {
|
2303
|
+
diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
|
2304
|
+
}
|
2305
|
+
if ( commonsuffix ) {
|
2306
|
+
diffs.push( [ DIFF_EQUAL, commonsuffix ] );
|
2307
|
+
}
|
2308
|
+
this.diffCleanupMerge( diffs );
|
2309
|
+
return diffs;
|
2310
|
+
};
|
2311
|
+
|
2312
|
+
/**
|
2313
|
+
* Reduce the number of edits by eliminating operationally trivial equalities.
|
2314
|
+
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2315
|
+
*/
|
2316
|
+
DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
|
2317
|
+
var changes, equalities, equalitiesLength, lastequality,
|
2318
|
+
pointer, preIns, preDel, postIns, postDel;
|
2319
|
+
changes = false;
|
2320
|
+
equalities = []; // Stack of indices where equalities are found.
|
2321
|
+
equalitiesLength = 0; // Keeping our own length var is faster in JS.
|
2322
|
+
/** @type {?string} */
|
2323
|
+
lastequality = null;
|
2324
|
+
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
|
2325
|
+
pointer = 0; // Index of current position.
|
2326
|
+
// Is there an insertion operation before the last equality.
|
2327
|
+
preIns = false;
|
2328
|
+
// Is there a deletion operation before the last equality.
|
2329
|
+
preDel = false;
|
2330
|
+
// Is there an insertion operation after the last equality.
|
2331
|
+
postIns = false;
|
2332
|
+
// Is there a deletion operation after the last equality.
|
2333
|
+
postDel = false;
|
2334
|
+
while ( pointer < diffs.length ) {
|
2335
|
+
|
2336
|
+
// Equality found.
|
2337
|
+
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
|
2338
|
+
if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
|
2339
|
+
|
2340
|
+
// Candidate found.
|
2341
|
+
equalities[ equalitiesLength++ ] = pointer;
|
2342
|
+
preIns = postIns;
|
2343
|
+
preDel = postDel;
|
2344
|
+
lastequality = diffs[ pointer ][ 1 ];
|
2345
|
+
} else {
|
2346
|
+
|
2347
|
+
// Not a candidate, and can never become one.
|
2348
|
+
equalitiesLength = 0;
|
2349
|
+
lastequality = null;
|
2350
|
+
}
|
2351
|
+
postIns = postDel = false;
|
2352
|
+
|
2353
|
+
// An insertion or deletion.
|
2354
|
+
} else {
|
2355
|
+
|
2356
|
+
if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
|
2357
|
+
postDel = true;
|
2358
|
+
} else {
|
2359
|
+
postIns = true;
|
2360
|
+
}
|
2361
|
+
|
2362
|
+
/*
|
2363
|
+
* Five types to be split:
|
2364
|
+
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
|
2365
|
+
* <ins>A</ins>X<ins>C</ins><del>D</del>
|
2366
|
+
* <ins>A</ins><del>B</del>X<ins>C</ins>
|
2367
|
+
* <ins>A</del>X<ins>C</ins><del>D</del>
|
2368
|
+
* <ins>A</ins><del>B</del>X<del>C</del>
|
2369
|
+
*/
|
2370
|
+
if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
|
2371
|
+
( ( lastequality.length < 2 ) &&
|
2372
|
+
( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
|
2373
|
+
|
2374
|
+
// Duplicate record.
|
2375
|
+
diffs.splice(
|
2376
|
+
equalities[ equalitiesLength - 1 ],
|
2377
|
+
0,
|
2378
|
+
[ DIFF_DELETE, lastequality ]
|
2379
|
+
);
|
2380
|
+
|
2381
|
+
// Change second copy to insert.
|
2382
|
+
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
|
2383
|
+
equalitiesLength--; // Throw away the equality we just deleted;
|
2384
|
+
lastequality = null;
|
2385
|
+
if ( preIns && preDel ) {
|
2386
|
+
// No changes made which could affect previous entry, keep going.
|
2387
|
+
postIns = postDel = true;
|
2388
|
+
equalitiesLength = 0;
|
2389
|
+
} else {
|
2390
|
+
equalitiesLength--; // Throw away the previous equality.
|
2391
|
+
pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
|
2392
|
+
postIns = postDel = false;
|
2393
|
+
}
|
2394
|
+
changes = true;
|
2395
|
+
}
|
2396
|
+
}
|
2397
|
+
pointer++;
|
2398
|
+
}
|
2399
|
+
|
2400
|
+
if ( changes ) {
|
2401
|
+
this.diffCleanupMerge( diffs );
|
2402
|
+
}
|
2403
|
+
};
|
2404
|
+
|
2405
|
+
/**
|
2406
|
+
* Convert a diff array into a pretty HTML report.
|
2407
|
+
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2408
|
+
* @param {integer} string to be beautified.
|
2409
|
+
* @return {string} HTML representation.
|
2410
|
+
*/
|
2411
|
+
DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
|
2412
|
+
var op, data, x,
|
2413
|
+
html = [];
|
2414
|
+
for ( x = 0; x < diffs.length; x++ ) {
|
2415
|
+
op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
|
2416
|
+
data = diffs[ x ][ 1 ]; // Text of change.
|
2417
|
+
switch ( op ) {
|
2418
|
+
case DIFF_INSERT:
|
2419
|
+
html[ x ] = "<ins>" + data + "</ins>";
|
2420
|
+
break;
|
2421
|
+
case DIFF_DELETE:
|
2422
|
+
html[ x ] = "<del>" + data + "</del>";
|
2423
|
+
break;
|
2424
|
+
case DIFF_EQUAL:
|
2425
|
+
html[ x ] = "<span>" + data + "</span>";
|
2426
|
+
break;
|
2427
|
+
}
|
2428
|
+
}
|
2429
|
+
return html.join( "" );
|
2430
|
+
};
|
2431
|
+
|
2432
|
+
/**
|
2433
|
+
* Determine the common prefix of two strings.
|
2434
|
+
* @param {string} text1 First string.
|
2435
|
+
* @param {string} text2 Second string.
|
2436
|
+
* @return {number} The number of characters common to the start of each
|
2437
|
+
* string.
|
2438
|
+
*/
|
2439
|
+
DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
|
2440
|
+
var pointermid, pointermax, pointermin, pointerstart;
|
2441
|
+
// Quick check for common null cases.
|
2442
|
+
if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
|
2443
|
+
return 0;
|
2444
|
+
}
|
2445
|
+
// Binary search.
|
2446
|
+
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
2447
|
+
pointermin = 0;
|
2448
|
+
pointermax = Math.min( text1.length, text2.length );
|
2449
|
+
pointermid = pointermax;
|
2450
|
+
pointerstart = 0;
|
2451
|
+
while ( pointermin < pointermid ) {
|
2452
|
+
if ( text1.substring( pointerstart, pointermid ) ===
|
2453
|
+
text2.substring( pointerstart, pointermid ) ) {
|
2454
|
+
pointermin = pointermid;
|
2455
|
+
pointerstart = pointermin;
|
2456
|
+
} else {
|
2457
|
+
pointermax = pointermid;
|
2458
|
+
}
|
2459
|
+
pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
|
2460
|
+
}
|
2461
|
+
return pointermid;
|
2462
|
+
};
|
2463
|
+
|
2464
|
+
/**
|
2465
|
+
* Determine the common suffix of two strings.
|
2466
|
+
* @param {string} text1 First string.
|
2467
|
+
* @param {string} text2 Second string.
|
2468
|
+
* @return {number} The number of characters common to the end of each string.
|
2469
|
+
*/
|
2470
|
+
DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
|
2471
|
+
var pointermid, pointermax, pointermin, pointerend;
|
2472
|
+
// Quick check for common null cases.
|
2473
|
+
if ( !text1 ||
|
2474
|
+
!text2 ||
|
2475
|
+
text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
|
2476
|
+
return 0;
|
2477
|
+
}
|
2478
|
+
// Binary search.
|
2479
|
+
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
2480
|
+
pointermin = 0;
|
2481
|
+
pointermax = Math.min( text1.length, text2.length );
|
2482
|
+
pointermid = pointermax;
|
2483
|
+
pointerend = 0;
|
2484
|
+
while ( pointermin < pointermid ) {
|
2485
|
+
if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
|
2486
|
+
text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
|
2487
|
+
pointermin = pointermid;
|
2488
|
+
pointerend = pointermin;
|
2489
|
+
} else {
|
2490
|
+
pointermax = pointermid;
|
2491
|
+
}
|
2492
|
+
pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
|
2493
|
+
}
|
2494
|
+
return pointermid;
|
2495
|
+
};
|
2496
|
+
|
2497
|
+
/**
|
2498
|
+
* Find the differences between two texts. Assumes that the texts do not
|
2499
|
+
* have any common prefix or suffix.
|
2500
|
+
* @param {string} text1 Old string to be diffed.
|
2501
|
+
* @param {string} text2 New string to be diffed.
|
2502
|
+
* @param {boolean} checklines Speedup flag. If false, then don't run a
|
2503
|
+
* line-level diff first to identify the changed areas.
|
2504
|
+
* If true, then run a faster, slightly less optimal diff.
|
2505
|
+
* @param {number} deadline Time when the diff should be complete by.
|
2506
|
+
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2507
|
+
* @private
|
2508
|
+
*/
|
2509
|
+
DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
|
2510
|
+
var diffs, longtext, shorttext, i, hm,
|
2511
|
+
text1A, text2A, text1B, text2B,
|
2512
|
+
midCommon, diffsA, diffsB;
|
2513
|
+
|
2514
|
+
if ( !text1 ) {
|
2515
|
+
// Just add some text (speedup).
|
2516
|
+
return [
|
2517
|
+
[ DIFF_INSERT, text2 ]
|
2518
|
+
];
|
2519
|
+
}
|
2520
|
+
|
2521
|
+
if ( !text2 ) {
|
2522
|
+
// Just delete some text (speedup).
|
2523
|
+
return [
|
2524
|
+
[ DIFF_DELETE, text1 ]
|
2525
|
+
];
|
2526
|
+
}
|
2527
|
+
|
2528
|
+
longtext = text1.length > text2.length ? text1 : text2;
|
2529
|
+
shorttext = text1.length > text2.length ? text2 : text1;
|
2530
|
+
i = longtext.indexOf( shorttext );
|
2531
|
+
if ( i !== -1 ) {
|
2532
|
+
// Shorter text is inside the longer text (speedup).
|
2533
|
+
diffs = [
|
2534
|
+
[ DIFF_INSERT, longtext.substring( 0, i ) ],
|
2535
|
+
[ DIFF_EQUAL, shorttext ],
|
2536
|
+
[ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
|
2537
|
+
];
|
2538
|
+
// Swap insertions for deletions if diff is reversed.
|
2539
|
+
if ( text1.length > text2.length ) {
|
2540
|
+
diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
|
2541
|
+
}
|
2542
|
+
return diffs;
|
2543
|
+
}
|
2544
|
+
|
2545
|
+
if ( shorttext.length === 1 ) {
|
2546
|
+
// Single character string.
|
2547
|
+
// After the previous speedup, the character can't be an equality.
|
2548
|
+
return [
|
2549
|
+
[ DIFF_DELETE, text1 ],
|
2550
|
+
[ DIFF_INSERT, text2 ]
|
2551
|
+
];
|
2552
|
+
}
|
2553
|
+
|
2554
|
+
// Check to see if the problem can be split in two.
|
2555
|
+
hm = this.diffHalfMatch( text1, text2 );
|
2556
|
+
if ( hm ) {
|
2557
|
+
// A half-match was found, sort out the return data.
|
2558
|
+
text1A = hm[ 0 ];
|
2559
|
+
text1B = hm[ 1 ];
|
2560
|
+
text2A = hm[ 2 ];
|
2561
|
+
text2B = hm[ 3 ];
|
2562
|
+
midCommon = hm[ 4 ];
|
2563
|
+
// Send both pairs off for separate processing.
|
2564
|
+
diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
|
2565
|
+
diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
|
2566
|
+
// Merge the results.
|
2567
|
+
return diffsA.concat( [
|
2568
|
+
[ DIFF_EQUAL, midCommon ]
|
2569
|
+
], diffsB );
|
2570
|
+
}
|
2571
|
+
|
2572
|
+
if ( checklines && text1.length > 100 && text2.length > 100 ) {
|
2573
|
+
return this.diffLineMode( text1, text2, deadline );
|
2574
|
+
}
|
2575
|
+
|
2576
|
+
return this.diffBisect( text1, text2, deadline );
|
2577
|
+
};
|
2578
|
+
|
2579
|
+
/**
|
2580
|
+
* Do the two texts share a substring which is at least half the length of the
|
2581
|
+
* longer text?
|
2582
|
+
* This speedup can produce non-minimal diffs.
|
2583
|
+
* @param {string} text1 First string.
|
2584
|
+
* @param {string} text2 Second string.
|
2585
|
+
* @return {Array.<string>} Five element Array, containing the prefix of
|
2586
|
+
* text1, the suffix of text1, the prefix of text2, the suffix of
|
2587
|
+
* text2 and the common middle. Or null if there was no match.
|
2588
|
+
* @private
|
2589
|
+
*/
|
2590
|
+
DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
|
2591
|
+
var longtext, shorttext, dmp,
|
2592
|
+
text1A, text2B, text2A, text1B, midCommon,
|
2593
|
+
hm1, hm2, hm;
|
2594
|
+
|
2595
|
+
longtext = text1.length > text2.length ? text1 : text2;
|
2596
|
+
shorttext = text1.length > text2.length ? text2 : text1;
|
2597
|
+
if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
|
2598
|
+
return null; // Pointless.
|
2599
|
+
}
|
2600
|
+
dmp = this; // 'this' becomes 'window' in a closure.
|
2601
|
+
|
2602
|
+
/**
|
2603
|
+
* Does a substring of shorttext exist within longtext such that the substring
|
2604
|
+
* is at least half the length of longtext?
|
2605
|
+
* Closure, but does not reference any external variables.
|
2606
|
+
* @param {string} longtext Longer string.
|
2607
|
+
* @param {string} shorttext Shorter string.
|
2608
|
+
* @param {number} i Start index of quarter length substring within longtext.
|
2609
|
+
* @return {Array.<string>} Five element Array, containing the prefix of
|
2610
|
+
* longtext, the suffix of longtext, the prefix of shorttext, the suffix
|
2611
|
+
* of shorttext and the common middle. Or null if there was no match.
|
2612
|
+
* @private
|
2613
|
+
*/
|
2614
|
+
function diffHalfMatchI( longtext, shorttext, i ) {
|
2615
|
+
var seed, j, bestCommon, prefixLength, suffixLength,
|
2616
|
+
bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
|
2617
|
+
// Start with a 1/4 length substring at position i as a seed.
|
2618
|
+
seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
|
2619
|
+
j = -1;
|
2620
|
+
bestCommon = "";
|
2621
|
+
while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
|
2622
|
+
prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
|
2623
|
+
shorttext.substring( j ) );
|
2624
|
+
suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
|
2625
|
+
shorttext.substring( 0, j ) );
|
2626
|
+
if ( bestCommon.length < suffixLength + prefixLength ) {
|
2627
|
+
bestCommon = shorttext.substring( j - suffixLength, j ) +
|
2628
|
+
shorttext.substring( j, j + prefixLength );
|
2629
|
+
bestLongtextA = longtext.substring( 0, i - suffixLength );
|
2630
|
+
bestLongtextB = longtext.substring( i + prefixLength );
|
2631
|
+
bestShorttextA = shorttext.substring( 0, j - suffixLength );
|
2632
|
+
bestShorttextB = shorttext.substring( j + prefixLength );
|
2633
|
+
}
|
2634
|
+
}
|
2635
|
+
if ( bestCommon.length * 2 >= longtext.length ) {
|
2636
|
+
return [ bestLongtextA, bestLongtextB,
|
2637
|
+
bestShorttextA, bestShorttextB, bestCommon
|
2638
|
+
];
|
2639
|
+
} else {
|
2640
|
+
return null;
|
2641
|
+
}
|
2642
|
+
}
|
2643
|
+
|
2644
|
+
// First check if the second quarter is the seed for a half-match.
|
2645
|
+
hm1 = diffHalfMatchI( longtext, shorttext,
|
2646
|
+
Math.ceil( longtext.length / 4 ) );
|
2647
|
+
// Check again based on the third quarter.
|
2648
|
+
hm2 = diffHalfMatchI( longtext, shorttext,
|
2649
|
+
Math.ceil( longtext.length / 2 ) );
|
2650
|
+
if ( !hm1 && !hm2 ) {
|
2651
|
+
return null;
|
2652
|
+
} else if ( !hm2 ) {
|
2653
|
+
hm = hm1;
|
2654
|
+
} else if ( !hm1 ) {
|
2655
|
+
hm = hm2;
|
2656
|
+
} else {
|
2657
|
+
// Both matched. Select the longest.
|
2658
|
+
hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
|
2659
|
+
}
|
2660
|
+
|
2661
|
+
// A half-match was found, sort out the return data.
|
2662
|
+
text1A, text1B, text2A, text2B;
|
2663
|
+
if ( text1.length > text2.length ) {
|
2664
|
+
text1A = hm[ 0 ];
|
2665
|
+
text1B = hm[ 1 ];
|
2666
|
+
text2A = hm[ 2 ];
|
2667
|
+
text2B = hm[ 3 ];
|
2668
|
+
} else {
|
2669
|
+
text2A = hm[ 0 ];
|
2670
|
+
text2B = hm[ 1 ];
|
2671
|
+
text1A = hm[ 2 ];
|
2672
|
+
text1B = hm[ 3 ];
|
2673
|
+
}
|
2674
|
+
midCommon = hm[ 4 ];
|
2675
|
+
return [ text1A, text1B, text2A, text2B, midCommon ];
|
2676
|
+
};
|
2677
|
+
|
2678
|
+
/**
|
2679
|
+
* Do a quick line-level diff on both strings, then rediff the parts for
|
2680
|
+
* greater accuracy.
|
2681
|
+
* This speedup can produce non-minimal diffs.
|
2682
|
+
* @param {string} text1 Old string to be diffed.
|
2683
|
+
* @param {string} text2 New string to be diffed.
|
2684
|
+
* @param {number} deadline Time when the diff should be complete by.
|
2685
|
+
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2686
|
+
* @private
|
2687
|
+
*/
|
2688
|
+
DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
|
2689
|
+
var a, diffs, linearray, pointer, countInsert,
|
2690
|
+
countDelete, textInsert, textDelete, j;
|
2691
|
+
// Scan the text on a line-by-line basis first.
|
2692
|
+
a = this.diffLinesToChars( text1, text2 );
|
2693
|
+
text1 = a.chars1;
|
2694
|
+
text2 = a.chars2;
|
2695
|
+
linearray = a.lineArray;
|
2696
|
+
|
2697
|
+
diffs = this.DiffMain( text1, text2, false, deadline );
|
2698
|
+
|
2699
|
+
// Convert the diff back to original text.
|
2700
|
+
this.diffCharsToLines( diffs, linearray );
|
2701
|
+
// Eliminate freak matches (e.g. blank lines)
|
2702
|
+
this.diffCleanupSemantic( diffs );
|
2703
|
+
|
2704
|
+
// Rediff any replacement blocks, this time character-by-character.
|
2705
|
+
// Add a dummy entry at the end.
|
2706
|
+
diffs.push( [ DIFF_EQUAL, "" ] );
|
2707
|
+
pointer = 0;
|
2708
|
+
countDelete = 0;
|
2709
|
+
countInsert = 0;
|
2710
|
+
textDelete = "";
|
2711
|
+
textInsert = "";
|
2712
|
+
while ( pointer < diffs.length ) {
|
2713
|
+
switch ( diffs[ pointer ][ 0 ] ) {
|
2714
|
+
case DIFF_INSERT:
|
2715
|
+
countInsert++;
|
2716
|
+
textInsert += diffs[ pointer ][ 1 ];
|
2717
|
+
break;
|
2718
|
+
case DIFF_DELETE:
|
2719
|
+
countDelete++;
|
2720
|
+
textDelete += diffs[ pointer ][ 1 ];
|
2721
|
+
break;
|
2722
|
+
case DIFF_EQUAL:
|
2723
|
+
// Upon reaching an equality, check for prior redundancies.
|
2724
|
+
if ( countDelete >= 1 && countInsert >= 1 ) {
|
2725
|
+
// Delete the offending records and add the merged ones.
|
2726
|
+
diffs.splice( pointer - countDelete - countInsert,
|
2727
|
+
countDelete + countInsert );
|
2728
|
+
pointer = pointer - countDelete - countInsert;
|
2729
|
+
a = this.DiffMain( textDelete, textInsert, false, deadline );
|
2730
|
+
for ( j = a.length - 1; j >= 0; j-- ) {
|
2731
|
+
diffs.splice( pointer, 0, a[ j ] );
|
2732
|
+
}
|
2733
|
+
pointer = pointer + a.length;
|
2734
|
+
}
|
2735
|
+
countInsert = 0;
|
2736
|
+
countDelete = 0;
|
2737
|
+
textDelete = "";
|
2738
|
+
textInsert = "";
|
2739
|
+
break;
|
2740
|
+
}
|
2741
|
+
pointer++;
|
2742
|
+
}
|
2743
|
+
diffs.pop(); // Remove the dummy entry at the end.
|
2744
|
+
|
2745
|
+
return diffs;
|
2746
|
+
};
|
2747
|
+
|
2748
|
+
/**
|
2749
|
+
* Find the 'middle snake' of a diff, split the problem in two
|
2750
|
+
* and return the recursively constructed diff.
|
2751
|
+
* See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
|
2752
|
+
* @param {string} text1 Old string to be diffed.
|
2753
|
+
* @param {string} text2 New string to be diffed.
|
2754
|
+
* @param {number} deadline Time at which to bail if not yet complete.
|
2755
|
+
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2756
|
+
* @private
|
2757
|
+
*/
|
2758
|
+
DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
|
2759
|
+
var text1Length, text2Length, maxD, vOffset, vLength,
|
2760
|
+
v1, v2, x, delta, front, k1start, k1end, k2start,
|
2761
|
+
k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
|
2762
|
+
// Cache the text lengths to prevent multiple calls.
|
2763
|
+
text1Length = text1.length;
|
2764
|
+
text2Length = text2.length;
|
2765
|
+
maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
|
2766
|
+
vOffset = maxD;
|
2767
|
+
vLength = 2 * maxD;
|
2768
|
+
v1 = new Array( vLength );
|
2769
|
+
v2 = new Array( vLength );
|
2770
|
+
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
|
2771
|
+
// integers and undefined.
|
2772
|
+
for ( x = 0; x < vLength; x++ ) {
|
2773
|
+
v1[ x ] = -1;
|
2774
|
+
v2[ x ] = -1;
|
2775
|
+
}
|
2776
|
+
v1[ vOffset + 1 ] = 0;
|
2777
|
+
v2[ vOffset + 1 ] = 0;
|
2778
|
+
delta = text1Length - text2Length;
|
2779
|
+
// If the total number of characters is odd, then the front path will collide
|
2780
|
+
// with the reverse path.
|
2781
|
+
front = ( delta % 2 !== 0 );
|
2782
|
+
// Offsets for start and end of k loop.
|
2783
|
+
// Prevents mapping of space beyond the grid.
|
2784
|
+
k1start = 0;
|
2785
|
+
k1end = 0;
|
2786
|
+
k2start = 0;
|
2787
|
+
k2end = 0;
|
2788
|
+
for ( d = 0; d < maxD; d++ ) {
|
2789
|
+
// Bail out if deadline is reached.
|
2790
|
+
if ( ( new Date() ).getTime() > deadline ) {
|
2791
|
+
break;
|
2792
|
+
}
|
2793
|
+
|
2794
|
+
// Walk the front path one step.
|
2795
|
+
for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
|
2796
|
+
k1Offset = vOffset + k1;
|
2797
|
+
if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
|
2798
|
+
x1 = v1[ k1Offset + 1 ];
|
2799
|
+
} else {
|
2800
|
+
x1 = v1[ k1Offset - 1 ] + 1;
|
2801
|
+
}
|
2802
|
+
y1 = x1 - k1;
|
2803
|
+
while ( x1 < text1Length && y1 < text2Length &&
|
2804
|
+
text1.charAt( x1 ) === text2.charAt( y1 ) ) {
|
2805
|
+
x1++;
|
2806
|
+
y1++;
|
2807
|
+
}
|
2808
|
+
v1[ k1Offset ] = x1;
|
2809
|
+
if ( x1 > text1Length ) {
|
2810
|
+
// Ran off the right of the graph.
|
2811
|
+
k1end += 2;
|
2812
|
+
} else if ( y1 > text2Length ) {
|
2813
|
+
// Ran off the bottom of the graph.
|
2814
|
+
k1start += 2;
|
2815
|
+
} else if ( front ) {
|
2816
|
+
k2Offset = vOffset + delta - k1;
|
2817
|
+
if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
|
2818
|
+
// Mirror x2 onto top-left coordinate system.
|
2819
|
+
x2 = text1Length - v2[ k2Offset ];
|
2820
|
+
if ( x1 >= x2 ) {
|
2821
|
+
// Overlap detected.
|
2822
|
+
return this.diffBisectSplit( text1, text2, x1, y1, deadline );
|
2823
|
+
}
|
2824
|
+
}
|
2825
|
+
}
|
2826
|
+
}
|
2827
|
+
|
2828
|
+
// Walk the reverse path one step.
|
2829
|
+
for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
|
2830
|
+
k2Offset = vOffset + k2;
|
2831
|
+
if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
|
2832
|
+
x2 = v2[ k2Offset + 1 ];
|
2833
|
+
} else {
|
2834
|
+
x2 = v2[ k2Offset - 1 ] + 1;
|
2835
|
+
}
|
2836
|
+
y2 = x2 - k2;
|
2837
|
+
while ( x2 < text1Length && y2 < text2Length &&
|
2838
|
+
text1.charAt( text1Length - x2 - 1 ) ===
|
2839
|
+
text2.charAt( text2Length - y2 - 1 ) ) {
|
2840
|
+
x2++;
|
2841
|
+
y2++;
|
2842
|
+
}
|
2843
|
+
v2[ k2Offset ] = x2;
|
2844
|
+
if ( x2 > text1Length ) {
|
2845
|
+
// Ran off the left of the graph.
|
2846
|
+
k2end += 2;
|
2847
|
+
} else if ( y2 > text2Length ) {
|
2848
|
+
// Ran off the top of the graph.
|
2849
|
+
k2start += 2;
|
2850
|
+
} else if ( !front ) {
|
2851
|
+
k1Offset = vOffset + delta - k2;
|
2852
|
+
if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
|
2853
|
+
x1 = v1[ k1Offset ];
|
2854
|
+
y1 = vOffset + x1 - k1Offset;
|
2855
|
+
// Mirror x2 onto top-left coordinate system.
|
2856
|
+
x2 = text1Length - x2;
|
2857
|
+
if ( x1 >= x2 ) {
|
2858
|
+
// Overlap detected.
|
2859
|
+
return this.diffBisectSplit( text1, text2, x1, y1, deadline );
|
2860
|
+
}
|
2861
|
+
}
|
2862
|
+
}
|
2863
|
+
}
|
2864
|
+
}
|
2865
|
+
// Diff took too long and hit the deadline or
|
2866
|
+
// number of diffs equals number of characters, no commonality at all.
|
2867
|
+
return [
|
2868
|
+
[ DIFF_DELETE, text1 ],
|
2869
|
+
[ DIFF_INSERT, text2 ]
|
2870
|
+
];
|
2871
|
+
};
|
1843
2872
|
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
2873
|
+
/**
|
2874
|
+
* Given the location of the 'middle snake', split the diff in two parts
|
2875
|
+
* and recurse.
|
2876
|
+
* @param {string} text1 Old string to be diffed.
|
2877
|
+
* @param {string} text2 New string to be diffed.
|
2878
|
+
* @param {number} x Index of split point in text1.
|
2879
|
+
* @param {number} y Index of split point in text2.
|
2880
|
+
* @param {number} deadline Time at which to bail if not yet complete.
|
2881
|
+
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2882
|
+
* @private
|
2883
|
+
*/
|
2884
|
+
DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
|
2885
|
+
var text1a, text1b, text2a, text2b, diffs, diffsb;
|
2886
|
+
text1a = text1.substring( 0, x );
|
2887
|
+
text2a = text2.substring( 0, y );
|
2888
|
+
text1b = text1.substring( x );
|
2889
|
+
text2b = text2.substring( y );
|
2890
|
+
|
2891
|
+
// Compute both diffs serially.
|
2892
|
+
diffs = this.DiffMain( text1a, text2a, false, deadline );
|
2893
|
+
diffsb = this.DiffMain( text1b, text2b, false, deadline );
|
2894
|
+
|
2895
|
+
return diffs.concat( diffsb );
|
2896
|
+
};
|
1848
2897
|
|
1849
|
-
|
1850
|
-
|
2898
|
+
/**
|
2899
|
+
* Reduce the number of edits by eliminating semantically trivial equalities.
|
2900
|
+
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2901
|
+
*/
|
2902
|
+
DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
|
2903
|
+
var changes, equalities, equalitiesLength, lastequality,
|
2904
|
+
pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
|
2905
|
+
lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
|
2906
|
+
changes = false;
|
2907
|
+
equalities = []; // Stack of indices where equalities are found.
|
2908
|
+
equalitiesLength = 0; // Keeping our own length var is faster in JS.
|
2909
|
+
/** @type {?string} */
|
2910
|
+
lastequality = null;
|
2911
|
+
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
|
2912
|
+
pointer = 0; // Index of current position.
|
2913
|
+
// Number of characters that changed prior to the equality.
|
2914
|
+
lengthInsertions1 = 0;
|
2915
|
+
lengthDeletions1 = 0;
|
2916
|
+
// Number of characters that changed after the equality.
|
2917
|
+
lengthInsertions2 = 0;
|
2918
|
+
lengthDeletions2 = 0;
|
2919
|
+
while ( pointer < diffs.length ) {
|
2920
|
+
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
|
2921
|
+
equalities[ equalitiesLength++ ] = pointer;
|
2922
|
+
lengthInsertions1 = lengthInsertions2;
|
2923
|
+
lengthDeletions1 = lengthDeletions2;
|
2924
|
+
lengthInsertions2 = 0;
|
2925
|
+
lengthDeletions2 = 0;
|
2926
|
+
lastequality = diffs[ pointer ][ 1 ];
|
2927
|
+
} else { // An insertion or deletion.
|
2928
|
+
if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
|
2929
|
+
lengthInsertions2 += diffs[ pointer ][ 1 ].length;
|
2930
|
+
} else {
|
2931
|
+
lengthDeletions2 += diffs[ pointer ][ 1 ].length;
|
2932
|
+
}
|
2933
|
+
// Eliminate an equality that is smaller or equal to the edits on both
|
2934
|
+
// sides of it.
|
2935
|
+
if ( lastequality && ( lastequality.length <=
|
2936
|
+
Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
|
2937
|
+
( lastequality.length <= Math.max( lengthInsertions2,
|
2938
|
+
lengthDeletions2 ) ) ) {
|
2939
|
+
|
2940
|
+
// Duplicate record.
|
2941
|
+
diffs.splice(
|
2942
|
+
equalities[ equalitiesLength - 1 ],
|
2943
|
+
0,
|
2944
|
+
[ DIFF_DELETE, lastequality ]
|
2945
|
+
);
|
2946
|
+
|
2947
|
+
// Change second copy to insert.
|
2948
|
+
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
|
2949
|
+
|
2950
|
+
// Throw away the equality we just deleted.
|
2951
|
+
equalitiesLength--;
|
2952
|
+
|
2953
|
+
// Throw away the previous equality (it needs to be reevaluated).
|
2954
|
+
equalitiesLength--;
|
2955
|
+
pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
|
2956
|
+
|
2957
|
+
// Reset the counters.
|
2958
|
+
lengthInsertions1 = 0;
|
2959
|
+
lengthDeletions1 = 0;
|
2960
|
+
lengthInsertions2 = 0;
|
2961
|
+
lengthDeletions2 = 0;
|
2962
|
+
lastequality = null;
|
2963
|
+
changes = true;
|
2964
|
+
}
|
2965
|
+
}
|
2966
|
+
pointer++;
|
2967
|
+
}
|
1851
2968
|
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
2969
|
+
// Normalize the diff.
|
2970
|
+
if ( changes ) {
|
2971
|
+
this.diffCleanupMerge( diffs );
|
2972
|
+
}
|
1856
2973
|
|
1857
|
-
|
1858
|
-
|
2974
|
+
// Find any overlaps between deletions and insertions.
|
2975
|
+
// e.g: <del>abcxxx</del><ins>xxxdef</ins>
|
2976
|
+
// -> <del>abc</del>xxx<ins>def</ins>
|
2977
|
+
// e.g: <del>xxxabc</del><ins>defxxx</ins>
|
2978
|
+
// -> <ins>def</ins>xxx<del>abc</del>
|
2979
|
+
// Only extract an overlap if it is as big as the edit ahead or behind it.
|
2980
|
+
pointer = 1;
|
2981
|
+
while ( pointer < diffs.length ) {
|
2982
|
+
if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
|
2983
|
+
diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
|
2984
|
+
deletion = diffs[ pointer - 1 ][ 1 ];
|
2985
|
+
insertion = diffs[ pointer ][ 1 ];
|
2986
|
+
overlapLength1 = this.diffCommonOverlap( deletion, insertion );
|
2987
|
+
overlapLength2 = this.diffCommonOverlap( insertion, deletion );
|
2988
|
+
if ( overlapLength1 >= overlapLength2 ) {
|
2989
|
+
if ( overlapLength1 >= deletion.length / 2 ||
|
2990
|
+
overlapLength1 >= insertion.length / 2 ) {
|
2991
|
+
// Overlap found. Insert an equality and trim the surrounding edits.
|
2992
|
+
diffs.splice(
|
2993
|
+
pointer,
|
2994
|
+
0,
|
2995
|
+
[ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
|
2996
|
+
);
|
2997
|
+
diffs[ pointer - 1 ][ 1 ] =
|
2998
|
+
deletion.substring( 0, deletion.length - overlapLength1 );
|
2999
|
+
diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
|
3000
|
+
pointer++;
|
1859
3001
|
}
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
//
|
1865
|
-
|
3002
|
+
} else {
|
3003
|
+
if ( overlapLength2 >= deletion.length / 2 ||
|
3004
|
+
overlapLength2 >= insertion.length / 2 ) {
|
3005
|
+
|
3006
|
+
// Reverse overlap found.
|
3007
|
+
// Insert an equality and swap and trim the surrounding edits.
|
3008
|
+
diffs.splice(
|
3009
|
+
pointer,
|
3010
|
+
0,
|
3011
|
+
[ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
|
3012
|
+
);
|
3013
|
+
|
3014
|
+
diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
|
3015
|
+
diffs[ pointer - 1 ][ 1 ] =
|
3016
|
+
insertion.substring( 0, insertion.length - overlapLength2 );
|
3017
|
+
diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
|
3018
|
+
diffs[ pointer + 1 ][ 1 ] =
|
3019
|
+
deletion.substring( overlapLength2 );
|
3020
|
+
pointer++;
|
1866
3021
|
}
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1873
|
-
// node calls it internally, it's an html attribute value
|
1874
|
-
attribute: quote,
|
1875
|
-
string: quote,
|
1876
|
-
date: quote,
|
1877
|
-
regexp: literal,
|
1878
|
-
number: literal,
|
1879
|
-
"boolean": literal
|
1880
|
-
},
|
1881
|
-
// if true, entities are escaped ( <, >, \t, space and \n )
|
1882
|
-
HTML: false,
|
1883
|
-
// indentation unit
|
1884
|
-
indentChar: " ",
|
1885
|
-
// if true, items in a collection, are separated by a \n, else just a space.
|
1886
|
-
multiline: true
|
1887
|
-
};
|
1888
|
-
|
1889
|
-
return dump;
|
1890
|
-
}());
|
3022
|
+
}
|
3023
|
+
pointer++;
|
3024
|
+
}
|
3025
|
+
pointer++;
|
3026
|
+
}
|
3027
|
+
};
|
1891
3028
|
|
1892
|
-
|
1893
|
-
|
3029
|
+
/**
|
3030
|
+
* Determine if the suffix of one string is the prefix of another.
|
3031
|
+
* @param {string} text1 First string.
|
3032
|
+
* @param {string} text2 Second string.
|
3033
|
+
* @return {number} The number of characters common to the end of the first
|
3034
|
+
* string and the start of the second string.
|
3035
|
+
* @private
|
3036
|
+
*/
|
3037
|
+
DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
|
3038
|
+
var text1Length, text2Length, textLength,
|
3039
|
+
best, length, pattern, found;
|
3040
|
+
// Cache the text lengths to prevent multiple calls.
|
3041
|
+
text1Length = text1.length;
|
3042
|
+
text2Length = text2.length;
|
3043
|
+
// Eliminate the null case.
|
3044
|
+
if ( text1Length === 0 || text2Length === 0 ) {
|
3045
|
+
return 0;
|
3046
|
+
}
|
3047
|
+
// Truncate the longer string.
|
3048
|
+
if ( text1Length > text2Length ) {
|
3049
|
+
text1 = text1.substring( text1Length - text2Length );
|
3050
|
+
} else if ( text1Length < text2Length ) {
|
3051
|
+
text2 = text2.substring( 0, text1Length );
|
3052
|
+
}
|
3053
|
+
textLength = Math.min( text1Length, text2Length );
|
3054
|
+
// Quick check for the worst case.
|
3055
|
+
if ( text1 === text2 ) {
|
3056
|
+
return textLength;
|
3057
|
+
}
|
1894
3058
|
|
1895
|
-
//
|
1896
|
-
|
3059
|
+
// Start by looking for a single character match
|
3060
|
+
// and increase length until no match is found.
|
3061
|
+
// Performance analysis: http://neil.fraser.name/news/2010/11/04/
|
3062
|
+
best = 0;
|
3063
|
+
length = 1;
|
3064
|
+
while ( true ) {
|
3065
|
+
pattern = text1.substring( textLength - length );
|
3066
|
+
found = text2.indexOf( pattern );
|
3067
|
+
if ( found === -1 ) {
|
3068
|
+
return best;
|
3069
|
+
}
|
3070
|
+
length += found;
|
3071
|
+
if ( found === 0 || text1.substring( textLength - length ) ===
|
3072
|
+
text2.substring( 0, length ) ) {
|
3073
|
+
best = length;
|
3074
|
+
length++;
|
3075
|
+
}
|
3076
|
+
}
|
3077
|
+
};
|
1897
3078
|
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
3079
|
+
/**
|
3080
|
+
* Split two texts into an array of strings. Reduce the texts to a string of
|
3081
|
+
* hashes where each Unicode character represents one line.
|
3082
|
+
* @param {string} text1 First string.
|
3083
|
+
* @param {string} text2 Second string.
|
3084
|
+
* @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
|
3085
|
+
* An object containing the encoded text1, the encoded text2 and
|
3086
|
+
* the array of unique strings.
|
3087
|
+
* The zeroth element of the array of unique strings is intentionally blank.
|
3088
|
+
* @private
|
3089
|
+
*/
|
3090
|
+
DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
|
3091
|
+
var lineArray, lineHash, chars1, chars2;
|
3092
|
+
lineArray = []; // e.g. lineArray[4] === 'Hello\n'
|
3093
|
+
lineHash = {}; // e.g. lineHash['Hello\n'] === 4
|
3094
|
+
|
3095
|
+
// '\x00' is a valid character, but various debuggers don't like it.
|
3096
|
+
// So we'll insert a junk entry to avoid generating a null character.
|
3097
|
+
lineArray[ 0 ] = "";
|
3098
|
+
|
3099
|
+
/**
|
3100
|
+
* Split a text into an array of strings. Reduce the texts to a string of
|
3101
|
+
* hashes where each Unicode character represents one line.
|
3102
|
+
* Modifies linearray and linehash through being a closure.
|
3103
|
+
* @param {string} text String to encode.
|
3104
|
+
* @return {string} Encoded string.
|
3105
|
+
* @private
|
3106
|
+
*/
|
3107
|
+
function diffLinesToCharsMunge( text ) {
|
3108
|
+
var chars, lineStart, lineEnd, lineArrayLength, line;
|
3109
|
+
chars = "";
|
3110
|
+
// Walk the text, pulling out a substring for each line.
|
3111
|
+
// text.split('\n') would would temporarily double our memory footprint.
|
3112
|
+
// Modifying text would create many large strings to garbage collect.
|
3113
|
+
lineStart = 0;
|
3114
|
+
lineEnd = -1;
|
3115
|
+
// Keeping our own length variable is faster than looking it up.
|
3116
|
+
lineArrayLength = lineArray.length;
|
3117
|
+
while ( lineEnd < text.length - 1 ) {
|
3118
|
+
lineEnd = text.indexOf( "\n", lineStart );
|
3119
|
+
if ( lineEnd === -1 ) {
|
3120
|
+
lineEnd = text.length - 1;
|
3121
|
+
}
|
3122
|
+
line = text.substring( lineStart, lineEnd + 1 );
|
3123
|
+
lineStart = lineEnd + 1;
|
1903
3124
|
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
3125
|
+
if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
|
3126
|
+
( lineHash[ line ] !== undefined ) ) {
|
3127
|
+
chars += String.fromCharCode( lineHash[ line ] );
|
3128
|
+
} else {
|
3129
|
+
chars += String.fromCharCode( lineArrayLength );
|
3130
|
+
lineHash[ line ] = lineArrayLength;
|
3131
|
+
lineArray[ lineArrayLength++ ] = line;
|
3132
|
+
}
|
3133
|
+
}
|
3134
|
+
return chars;
|
1909
3135
|
}
|
1910
3136
|
|
1911
|
-
|
1912
|
-
|
3137
|
+
chars1 = diffLinesToCharsMunge( text1 );
|
3138
|
+
chars2 = diffLinesToCharsMunge( text2 );
|
3139
|
+
return {
|
3140
|
+
chars1: chars1,
|
3141
|
+
chars2: chars2,
|
3142
|
+
lineArray: lineArray
|
3143
|
+
};
|
3144
|
+
};
|
3145
|
+
|
3146
|
+
/**
|
3147
|
+
* Rehydrate the text in a diff from a string of line hashes to real lines of
|
3148
|
+
* text.
|
3149
|
+
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
3150
|
+
* @param {!Array.<string>} lineArray Array of unique strings.
|
3151
|
+
* @private
|
3152
|
+
*/
|
3153
|
+
DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
|
3154
|
+
var x, chars, text, y;
|
3155
|
+
for ( x = 0; x < diffs.length; x++ ) {
|
3156
|
+
chars = diffs[ x ][ 1 ];
|
3157
|
+
text = [];
|
3158
|
+
for ( y = 0; y < chars.length; y++ ) {
|
3159
|
+
text[ y ] = lineArray[ chars.charCodeAt( y ) ];
|
3160
|
+
}
|
3161
|
+
diffs[ x ][ 1 ] = text.join( "" );
|
1913
3162
|
}
|
1914
|
-
}
|
3163
|
+
};
|
1915
3164
|
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1928
|
-
|
1929
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
3165
|
+
/**
|
3166
|
+
* Reorder and merge like edit sections. Merge equalities.
|
3167
|
+
* Any edit section can move as long as it doesn't cross an equality.
|
3168
|
+
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
3169
|
+
*/
|
3170
|
+
DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
|
3171
|
+
var pointer, countDelete, countInsert, textInsert, textDelete,
|
3172
|
+
commonlength, changes, diffPointer, position;
|
3173
|
+
diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
|
3174
|
+
pointer = 0;
|
3175
|
+
countDelete = 0;
|
3176
|
+
countInsert = 0;
|
3177
|
+
textDelete = "";
|
3178
|
+
textInsert = "";
|
3179
|
+
commonlength;
|
3180
|
+
while ( pointer < diffs.length ) {
|
3181
|
+
switch ( diffs[ pointer ][ 0 ] ) {
|
3182
|
+
case DIFF_INSERT:
|
3183
|
+
countInsert++;
|
3184
|
+
textInsert += diffs[ pointer ][ 1 ];
|
3185
|
+
pointer++;
|
3186
|
+
break;
|
3187
|
+
case DIFF_DELETE:
|
3188
|
+
countDelete++;
|
3189
|
+
textDelete += diffs[ pointer ][ 1 ];
|
3190
|
+
pointer++;
|
3191
|
+
break;
|
3192
|
+
case DIFF_EQUAL:
|
3193
|
+
// Upon reaching an equality, check for prior redundancies.
|
3194
|
+
if ( countDelete + countInsert > 1 ) {
|
3195
|
+
if ( countDelete !== 0 && countInsert !== 0 ) {
|
3196
|
+
// Factor out any common prefixies.
|
3197
|
+
commonlength = this.diffCommonPrefix( textInsert, textDelete );
|
3198
|
+
if ( commonlength !== 0 ) {
|
3199
|
+
if ( ( pointer - countDelete - countInsert ) > 0 &&
|
3200
|
+
diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
|
3201
|
+
DIFF_EQUAL ) {
|
3202
|
+
diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
|
3203
|
+
textInsert.substring( 0, commonlength );
|
3204
|
+
} else {
|
3205
|
+
diffs.splice( 0, 0, [ DIFF_EQUAL,
|
3206
|
+
textInsert.substring( 0, commonlength )
|
3207
|
+
] );
|
3208
|
+
pointer++;
|
3209
|
+
}
|
3210
|
+
textInsert = textInsert.substring( commonlength );
|
3211
|
+
textDelete = textDelete.substring( commonlength );
|
3212
|
+
}
|
3213
|
+
// Factor out any common suffixies.
|
3214
|
+
commonlength = this.diffCommonSuffix( textInsert, textDelete );
|
3215
|
+
if ( commonlength !== 0 ) {
|
3216
|
+
diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
|
3217
|
+
commonlength ) + diffs[ pointer ][ 1 ];
|
3218
|
+
textInsert = textInsert.substring( 0, textInsert.length -
|
3219
|
+
commonlength );
|
3220
|
+
textDelete = textDelete.substring( 0, textDelete.length -
|
3221
|
+
commonlength );
|
3222
|
+
}
|
3223
|
+
}
|
3224
|
+
// Delete the offending records and add the merged ones.
|
3225
|
+
if ( countDelete === 0 ) {
|
3226
|
+
diffs.splice( pointer - countInsert,
|
3227
|
+
countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
|
3228
|
+
} else if ( countInsert === 0 ) {
|
3229
|
+
diffs.splice( pointer - countDelete,
|
3230
|
+
countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
|
3231
|
+
} else {
|
3232
|
+
diffs.splice(
|
3233
|
+
pointer - countDelete - countInsert,
|
3234
|
+
countDelete + countInsert,
|
3235
|
+
[ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
|
3236
|
+
);
|
3237
|
+
}
|
3238
|
+
pointer = pointer - countDelete - countInsert +
|
3239
|
+
( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
|
3240
|
+
} else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
|
1937
3241
|
|
1938
|
-
|
1939
|
-
|
3242
|
+
// Merge this equality with the previous one.
|
3243
|
+
diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
|
3244
|
+
diffs.splice( pointer, 1 );
|
3245
|
+
} else {
|
3246
|
+
pointer++;
|
3247
|
+
}
|
3248
|
+
countInsert = 0;
|
3249
|
+
countDelete = 0;
|
3250
|
+
textDelete = "";
|
3251
|
+
textInsert = "";
|
3252
|
+
break;
|
3253
|
+
}
|
3254
|
+
}
|
3255
|
+
if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
|
3256
|
+
diffs.pop(); // Remove the dummy entry at the end.
|
1940
3257
|
}
|
1941
|
-
})();
|
1942
3258
|
|
1943
|
-
|
1944
|
-
|
3259
|
+
// Second pass: look for single edits surrounded on both sides by equalities
|
3260
|
+
// which can be shifted sideways to eliminate an equality.
|
3261
|
+
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
|
3262
|
+
changes = false;
|
3263
|
+
pointer = 1;
|
1945
3264
|
|
1946
|
-
//
|
1947
|
-
|
1948
|
-
|
3265
|
+
// Intentionally ignore the first and last element (don't need checking).
|
3266
|
+
while ( pointer < diffs.length - 1 ) {
|
3267
|
+
if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
|
3268
|
+
diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
|
1949
3269
|
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
3270
|
+
diffPointer = diffs[ pointer ][ 1 ];
|
3271
|
+
position = diffPointer.substring(
|
3272
|
+
diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
|
3273
|
+
);
|
1953
3274
|
|
1954
|
-
//
|
1955
|
-
if (
|
1956
|
-
|
1957
|
-
|
3275
|
+
// This is a single edit surrounded by equalities.
|
3276
|
+
if ( position === diffs[ pointer - 1 ][ 1 ] ) {
|
3277
|
+
|
3278
|
+
// Shift the edit over the previous equality.
|
3279
|
+
diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
|
3280
|
+
diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
|
3281
|
+
diffs[ pointer - 1 ][ 1 ].length );
|
3282
|
+
diffs[ pointer + 1 ][ 1 ] =
|
3283
|
+
diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
|
3284
|
+
diffs.splice( pointer - 1, 1 );
|
3285
|
+
changes = true;
|
3286
|
+
} else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
|
3287
|
+
diffs[ pointer + 1 ][ 1 ] ) {
|
3288
|
+
|
3289
|
+
// Shift the edit over the next equality.
|
3290
|
+
diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
|
3291
|
+
diffs[ pointer ][ 1 ] =
|
3292
|
+
diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
|
3293
|
+
diffs[ pointer + 1 ][ 1 ];
|
3294
|
+
diffs.splice( pointer + 1, 1 );
|
3295
|
+
changes = true;
|
3296
|
+
}
|
3297
|
+
}
|
3298
|
+
pointer++;
|
3299
|
+
}
|
3300
|
+
// If shifts were made, the diff needs reordering and another shift sweep.
|
3301
|
+
if ( changes ) {
|
3302
|
+
this.diffCleanupMerge( diffs );
|
3303
|
+
}
|
3304
|
+
};
|
1958
3305
|
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
3306
|
+
return function( o, n ) {
|
3307
|
+
var diff, output, text;
|
3308
|
+
diff = new DiffMatchPatch();
|
3309
|
+
output = diff.DiffMain( o, n );
|
3310
|
+
diff.diffCleanupEfficiency( output );
|
3311
|
+
text = diff.diffPrettyHtml( output );
|
3312
|
+
|
3313
|
+
return text;
|
3314
|
+
};
|
3315
|
+
}() );
|
1965
3316
|
|
1966
3317
|
// Get a reference to the global object, like window in browsers
|
1967
3318
|
}( (function() {
|
1968
3319
|
return this;
|
1969
3320
|
})() ));
|
1970
3321
|
|
1971
|
-
/*istanbul ignore next */
|
1972
|
-
// jscs:disable maximumLineLength
|
1973
|
-
/*
|
1974
|
-
* This file is a modified version of google-diff-match-patch's JavaScript implementation
|
1975
|
-
* (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
|
1976
|
-
* modifications are licensed as more fully set forth in LICENSE.txt.
|
1977
|
-
*
|
1978
|
-
* The original source of google-diff-match-patch is attributable and licensed as follows:
|
1979
|
-
*
|
1980
|
-
* Copyright 2006 Google Inc.
|
1981
|
-
* http://code.google.com/p/google-diff-match-patch/
|
1982
|
-
*
|
1983
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
1984
|
-
* you may not use this file except in compliance with the License.
|
1985
|
-
* You may obtain a copy of the License at
|
1986
|
-
*
|
1987
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
1988
|
-
*
|
1989
|
-
* Unless required by applicable law or agreed to in writing, software
|
1990
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
1991
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
1992
|
-
* See the License for the specific language governing permissions and
|
1993
|
-
* limitations under the License.
|
1994
|
-
*
|
1995
|
-
* More Info:
|
1996
|
-
* https://code.google.com/p/google-diff-match-patch/
|
1997
|
-
*
|
1998
|
-
* Usage: QUnit.diff(expected, actual)
|
1999
|
-
*
|
2000
|
-
* QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
|
2001
|
-
*/
|
2002
|
-
QUnit.diff = (function() {
|
2003
|
-
|
2004
|
-
function DiffMatchPatch() {
|
2005
|
-
|
2006
|
-
// Defaults.
|
2007
|
-
// Redefine these in your program to override the defaults.
|
2008
|
-
|
2009
|
-
// Number of seconds to map a diff before giving up (0 for infinity).
|
2010
|
-
this.DiffTimeout = 1.0;
|
2011
|
-
// Cost of an empty edit operation in terms of edit characters.
|
2012
|
-
this.DiffEditCost = 4;
|
2013
|
-
}
|
2014
|
-
|
2015
|
-
// DIFF FUNCTIONS
|
2016
|
-
|
2017
|
-
/**
|
2018
|
-
* The data structure representing a diff is an array of tuples:
|
2019
|
-
* [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
|
2020
|
-
* which means: delete 'Hello', add 'Goodbye' and keep ' world.'
|
2021
|
-
*/
|
2022
|
-
var DIFF_DELETE = -1,
|
2023
|
-
DIFF_INSERT = 1,
|
2024
|
-
DIFF_EQUAL = 0;
|
2025
|
-
|
2026
|
-
/**
|
2027
|
-
* Find the differences between two texts. Simplifies the problem by stripping
|
2028
|
-
* any common prefix or suffix off the texts before diffing.
|
2029
|
-
* @param {string} text1 Old string to be diffed.
|
2030
|
-
* @param {string} text2 New string to be diffed.
|
2031
|
-
* @param {boolean=} optChecklines Optional speedup flag. If present and false,
|
2032
|
-
* then don't run a line-level diff first to identify the changed areas.
|
2033
|
-
* Defaults to true, which does a faster, slightly less optimal diff.
|
2034
|
-
* @param {number} optDeadline Optional time when the diff should be complete
|
2035
|
-
* by. Used internally for recursive calls. Users should set DiffTimeout
|
2036
|
-
* instead.
|
2037
|
-
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2038
|
-
*/
|
2039
|
-
DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
|
2040
|
-
var deadline, checklines, commonlength,
|
2041
|
-
commonprefix, commonsuffix, diffs;
|
2042
|
-
// Set a deadline by which time the diff must be complete.
|
2043
|
-
if ( typeof optDeadline === "undefined" ) {
|
2044
|
-
if ( this.DiffTimeout <= 0 ) {
|
2045
|
-
optDeadline = Number.MAX_VALUE;
|
2046
|
-
} else {
|
2047
|
-
optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
|
2048
|
-
}
|
2049
|
-
}
|
2050
|
-
deadline = optDeadline;
|
2051
|
-
|
2052
|
-
// Check for null inputs.
|
2053
|
-
if ( text1 === null || text2 === null ) {
|
2054
|
-
throw new Error( "Null input. (DiffMain)" );
|
2055
|
-
}
|
2056
|
-
|
2057
|
-
// Check for equality (speedup).
|
2058
|
-
if ( text1 === text2 ) {
|
2059
|
-
if ( text1 ) {
|
2060
|
-
return [
|
2061
|
-
[ DIFF_EQUAL, text1 ]
|
2062
|
-
];
|
2063
|
-
}
|
2064
|
-
return [];
|
2065
|
-
}
|
2066
|
-
|
2067
|
-
if ( typeof optChecklines === "undefined" ) {
|
2068
|
-
optChecklines = true;
|
2069
|
-
}
|
2070
|
-
|
2071
|
-
checklines = optChecklines;
|
2072
|
-
|
2073
|
-
// Trim off common prefix (speedup).
|
2074
|
-
commonlength = this.diffCommonPrefix( text1, text2 );
|
2075
|
-
commonprefix = text1.substring( 0, commonlength );
|
2076
|
-
text1 = text1.substring( commonlength );
|
2077
|
-
text2 = text2.substring( commonlength );
|
2078
|
-
|
2079
|
-
// Trim off common suffix (speedup).
|
2080
|
-
/////////
|
2081
|
-
commonlength = this.diffCommonSuffix( text1, text2 );
|
2082
|
-
commonsuffix = text1.substring( text1.length - commonlength );
|
2083
|
-
text1 = text1.substring( 0, text1.length - commonlength );
|
2084
|
-
text2 = text2.substring( 0, text2.length - commonlength );
|
2085
|
-
|
2086
|
-
// Compute the diff on the middle block.
|
2087
|
-
diffs = this.diffCompute( text1, text2, checklines, deadline );
|
2088
|
-
|
2089
|
-
// Restore the prefix and suffix.
|
2090
|
-
if ( commonprefix ) {
|
2091
|
-
diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
|
2092
|
-
}
|
2093
|
-
if ( commonsuffix ) {
|
2094
|
-
diffs.push( [ DIFF_EQUAL, commonsuffix ] );
|
2095
|
-
}
|
2096
|
-
this.diffCleanupMerge( diffs );
|
2097
|
-
return diffs;
|
2098
|
-
};
|
2099
|
-
|
2100
|
-
/**
|
2101
|
-
* Reduce the number of edits by eliminating operationally trivial equalities.
|
2102
|
-
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2103
|
-
*/
|
2104
|
-
DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
|
2105
|
-
var changes, equalities, equalitiesLength, lastequality,
|
2106
|
-
pointer, preIns, preDel, postIns, postDel;
|
2107
|
-
changes = false;
|
2108
|
-
equalities = []; // Stack of indices where equalities are found.
|
2109
|
-
equalitiesLength = 0; // Keeping our own length var is faster in JS.
|
2110
|
-
/** @type {?string} */
|
2111
|
-
lastequality = null;
|
2112
|
-
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
|
2113
|
-
pointer = 0; // Index of current position.
|
2114
|
-
// Is there an insertion operation before the last equality.
|
2115
|
-
preIns = false;
|
2116
|
-
// Is there a deletion operation before the last equality.
|
2117
|
-
preDel = false;
|
2118
|
-
// Is there an insertion operation after the last equality.
|
2119
|
-
postIns = false;
|
2120
|
-
// Is there a deletion operation after the last equality.
|
2121
|
-
postDel = false;
|
2122
|
-
while ( pointer < diffs.length ) {
|
2123
|
-
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
|
2124
|
-
if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
|
2125
|
-
// Candidate found.
|
2126
|
-
equalities[ equalitiesLength++ ] = pointer;
|
2127
|
-
preIns = postIns;
|
2128
|
-
preDel = postDel;
|
2129
|
-
lastequality = diffs[ pointer ][ 1 ];
|
2130
|
-
} else {
|
2131
|
-
// Not a candidate, and can never become one.
|
2132
|
-
equalitiesLength = 0;
|
2133
|
-
lastequality = null;
|
2134
|
-
}
|
2135
|
-
postIns = postDel = false;
|
2136
|
-
} else { // An insertion or deletion.
|
2137
|
-
if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
|
2138
|
-
postDel = true;
|
2139
|
-
} else {
|
2140
|
-
postIns = true;
|
2141
|
-
}
|
2142
|
-
/*
|
2143
|
-
* Five types to be split:
|
2144
|
-
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
|
2145
|
-
* <ins>A</ins>X<ins>C</ins><del>D</del>
|
2146
|
-
* <ins>A</ins><del>B</del>X<ins>C</ins>
|
2147
|
-
* <ins>A</del>X<ins>C</ins><del>D</del>
|
2148
|
-
* <ins>A</ins><del>B</del>X<del>C</del>
|
2149
|
-
*/
|
2150
|
-
if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
|
2151
|
-
( ( lastequality.length < this.DiffEditCost / 2 ) &&
|
2152
|
-
( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
|
2153
|
-
// Duplicate record.
|
2154
|
-
diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
|
2155
|
-
// Change second copy to insert.
|
2156
|
-
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
|
2157
|
-
equalitiesLength--; // Throw away the equality we just deleted;
|
2158
|
-
lastequality = null;
|
2159
|
-
if (preIns && preDel) {
|
2160
|
-
// No changes made which could affect previous entry, keep going.
|
2161
|
-
postIns = postDel = true;
|
2162
|
-
equalitiesLength = 0;
|
2163
|
-
} else {
|
2164
|
-
equalitiesLength--; // Throw away the previous equality.
|
2165
|
-
pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
|
2166
|
-
postIns = postDel = false;
|
2167
|
-
}
|
2168
|
-
changes = true;
|
2169
|
-
}
|
2170
|
-
}
|
2171
|
-
pointer++;
|
2172
|
-
}
|
2173
|
-
|
2174
|
-
if ( changes ) {
|
2175
|
-
this.diffCleanupMerge( diffs );
|
2176
|
-
}
|
2177
|
-
};
|
2178
|
-
|
2179
|
-
/**
|
2180
|
-
* Convert a diff array into a pretty HTML report.
|
2181
|
-
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2182
|
-
* @param {integer} string to be beautified.
|
2183
|
-
* @return {string} HTML representation.
|
2184
|
-
*/
|
2185
|
-
DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
|
2186
|
-
var op, data, x, html = [];
|
2187
|
-
for ( x = 0; x < diffs.length; x++ ) {
|
2188
|
-
op = diffs[x][0]; // Operation (insert, delete, equal)
|
2189
|
-
data = diffs[x][1]; // Text of change.
|
2190
|
-
switch ( op ) {
|
2191
|
-
case DIFF_INSERT:
|
2192
|
-
html[x] = "<ins>" + data + "</ins>";
|
2193
|
-
break;
|
2194
|
-
case DIFF_DELETE:
|
2195
|
-
html[x] = "<del>" + data + "</del>";
|
2196
|
-
break;
|
2197
|
-
case DIFF_EQUAL:
|
2198
|
-
html[x] = "<span>" + data + "</span>";
|
2199
|
-
break;
|
2200
|
-
}
|
2201
|
-
}
|
2202
|
-
return html.join("");
|
2203
|
-
};
|
2204
|
-
|
2205
|
-
/**
|
2206
|
-
* Determine the common prefix of two strings.
|
2207
|
-
* @param {string} text1 First string.
|
2208
|
-
* @param {string} text2 Second string.
|
2209
|
-
* @return {number} The number of characters common to the start of each
|
2210
|
-
* string.
|
2211
|
-
*/
|
2212
|
-
DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
|
2213
|
-
var pointermid, pointermax, pointermin, pointerstart;
|
2214
|
-
// Quick check for common null cases.
|
2215
|
-
if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
|
2216
|
-
return 0;
|
2217
|
-
}
|
2218
|
-
// Binary search.
|
2219
|
-
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
2220
|
-
pointermin = 0;
|
2221
|
-
pointermax = Math.min( text1.length, text2.length );
|
2222
|
-
pointermid = pointermax;
|
2223
|
-
pointerstart = 0;
|
2224
|
-
while ( pointermin < pointermid ) {
|
2225
|
-
if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
|
2226
|
-
pointermin = pointermid;
|
2227
|
-
pointerstart = pointermin;
|
2228
|
-
} else {
|
2229
|
-
pointermax = pointermid;
|
2230
|
-
}
|
2231
|
-
pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
|
2232
|
-
}
|
2233
|
-
return pointermid;
|
2234
|
-
};
|
2235
|
-
|
2236
|
-
/**
|
2237
|
-
* Determine the common suffix of two strings.
|
2238
|
-
* @param {string} text1 First string.
|
2239
|
-
* @param {string} text2 Second string.
|
2240
|
-
* @return {number} The number of characters common to the end of each string.
|
2241
|
-
*/
|
2242
|
-
DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
|
2243
|
-
var pointermid, pointermax, pointermin, pointerend;
|
2244
|
-
// Quick check for common null cases.
|
2245
|
-
if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
|
2246
|
-
return 0;
|
2247
|
-
}
|
2248
|
-
// Binary search.
|
2249
|
-
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
2250
|
-
pointermin = 0;
|
2251
|
-
pointermax = Math.min(text1.length, text2.length);
|
2252
|
-
pointermid = pointermax;
|
2253
|
-
pointerend = 0;
|
2254
|
-
while ( pointermin < pointermid ) {
|
2255
|
-
if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
|
2256
|
-
text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
|
2257
|
-
pointermin = pointermid;
|
2258
|
-
pointerend = pointermin;
|
2259
|
-
} else {
|
2260
|
-
pointermax = pointermid;
|
2261
|
-
}
|
2262
|
-
pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
|
2263
|
-
}
|
2264
|
-
return pointermid;
|
2265
|
-
};
|
2266
|
-
|
2267
|
-
/**
|
2268
|
-
* Find the differences between two texts. Assumes that the texts do not
|
2269
|
-
* have any common prefix or suffix.
|
2270
|
-
* @param {string} text1 Old string to be diffed.
|
2271
|
-
* @param {string} text2 New string to be diffed.
|
2272
|
-
* @param {boolean} checklines Speedup flag. If false, then don't run a
|
2273
|
-
* line-level diff first to identify the changed areas.
|
2274
|
-
* If true, then run a faster, slightly less optimal diff.
|
2275
|
-
* @param {number} deadline Time when the diff should be complete by.
|
2276
|
-
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2277
|
-
* @private
|
2278
|
-
*/
|
2279
|
-
DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
|
2280
|
-
var diffs, longtext, shorttext, i, hm,
|
2281
|
-
text1A, text2A, text1B, text2B,
|
2282
|
-
midCommon, diffsA, diffsB;
|
2283
|
-
|
2284
|
-
if ( !text1 ) {
|
2285
|
-
// Just add some text (speedup).
|
2286
|
-
return [
|
2287
|
-
[ DIFF_INSERT, text2 ]
|
2288
|
-
];
|
2289
|
-
}
|
2290
|
-
|
2291
|
-
if (!text2) {
|
2292
|
-
// Just delete some text (speedup).
|
2293
|
-
return [
|
2294
|
-
[ DIFF_DELETE, text1 ]
|
2295
|
-
];
|
2296
|
-
}
|
2297
|
-
|
2298
|
-
longtext = text1.length > text2.length ? text1 : text2;
|
2299
|
-
shorttext = text1.length > text2.length ? text2 : text1;
|
2300
|
-
i = longtext.indexOf( shorttext );
|
2301
|
-
if ( i !== -1 ) {
|
2302
|
-
// Shorter text is inside the longer text (speedup).
|
2303
|
-
diffs = [
|
2304
|
-
[ DIFF_INSERT, longtext.substring( 0, i ) ],
|
2305
|
-
[ DIFF_EQUAL, shorttext ],
|
2306
|
-
[ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
|
2307
|
-
];
|
2308
|
-
// Swap insertions for deletions if diff is reversed.
|
2309
|
-
if ( text1.length > text2.length ) {
|
2310
|
-
diffs[0][0] = diffs[2][0] = DIFF_DELETE;
|
2311
|
-
}
|
2312
|
-
return diffs;
|
2313
|
-
}
|
2314
|
-
|
2315
|
-
if ( shorttext.length === 1 ) {
|
2316
|
-
// Single character string.
|
2317
|
-
// After the previous speedup, the character can't be an equality.
|
2318
|
-
return [
|
2319
|
-
[ DIFF_DELETE, text1 ],
|
2320
|
-
[ DIFF_INSERT, text2 ]
|
2321
|
-
];
|
2322
|
-
}
|
2323
|
-
|
2324
|
-
// Check to see if the problem can be split in two.
|
2325
|
-
hm = this.diffHalfMatch(text1, text2);
|
2326
|
-
if (hm) {
|
2327
|
-
// A half-match was found, sort out the return data.
|
2328
|
-
text1A = hm[0];
|
2329
|
-
text1B = hm[1];
|
2330
|
-
text2A = hm[2];
|
2331
|
-
text2B = hm[3];
|
2332
|
-
midCommon = hm[4];
|
2333
|
-
// Send both pairs off for separate processing.
|
2334
|
-
diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
|
2335
|
-
diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
|
2336
|
-
// Merge the results.
|
2337
|
-
return diffsA.concat([
|
2338
|
-
[ DIFF_EQUAL, midCommon ]
|
2339
|
-
], diffsB);
|
2340
|
-
}
|
2341
|
-
|
2342
|
-
if (checklines && text1.length > 100 && text2.length > 100) {
|
2343
|
-
return this.diffLineMode(text1, text2, deadline);
|
2344
|
-
}
|
2345
|
-
|
2346
|
-
return this.diffBisect(text1, text2, deadline);
|
2347
|
-
};
|
2348
|
-
|
2349
|
-
/**
|
2350
|
-
* Do the two texts share a substring which is at least half the length of the
|
2351
|
-
* longer text?
|
2352
|
-
* This speedup can produce non-minimal diffs.
|
2353
|
-
* @param {string} text1 First string.
|
2354
|
-
* @param {string} text2 Second string.
|
2355
|
-
* @return {Array.<string>} Five element Array, containing the prefix of
|
2356
|
-
* text1, the suffix of text1, the prefix of text2, the suffix of
|
2357
|
-
* text2 and the common middle. Or null if there was no match.
|
2358
|
-
* @private
|
2359
|
-
*/
|
2360
|
-
DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
|
2361
|
-
var longtext, shorttext, dmp,
|
2362
|
-
text1A, text2B, text2A, text1B, midCommon,
|
2363
|
-
hm1, hm2, hm;
|
2364
|
-
if (this.DiffTimeout <= 0) {
|
2365
|
-
// Don't risk returning a non-optimal diff if we have unlimited time.
|
2366
|
-
return null;
|
2367
|
-
}
|
2368
|
-
longtext = text1.length > text2.length ? text1 : text2;
|
2369
|
-
shorttext = text1.length > text2.length ? text2 : text1;
|
2370
|
-
if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
|
2371
|
-
return null; // Pointless.
|
2372
|
-
}
|
2373
|
-
dmp = this; // 'this' becomes 'window' in a closure.
|
2374
|
-
|
2375
|
-
/**
|
2376
|
-
* Does a substring of shorttext exist within longtext such that the substring
|
2377
|
-
* is at least half the length of longtext?
|
2378
|
-
* Closure, but does not reference any external variables.
|
2379
|
-
* @param {string} longtext Longer string.
|
2380
|
-
* @param {string} shorttext Shorter string.
|
2381
|
-
* @param {number} i Start index of quarter length substring within longtext.
|
2382
|
-
* @return {Array.<string>} Five element Array, containing the prefix of
|
2383
|
-
* longtext, the suffix of longtext, the prefix of shorttext, the suffix
|
2384
|
-
* of shorttext and the common middle. Or null if there was no match.
|
2385
|
-
* @private
|
2386
|
-
*/
|
2387
|
-
function diffHalfMatchI(longtext, shorttext, i) {
|
2388
|
-
var seed, j, bestCommon, prefixLength, suffixLength,
|
2389
|
-
bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
|
2390
|
-
// Start with a 1/4 length substring at position i as a seed.
|
2391
|
-
seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
|
2392
|
-
j = -1;
|
2393
|
-
bestCommon = "";
|
2394
|
-
while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
|
2395
|
-
prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
|
2396
|
-
shorttext.substring(j));
|
2397
|
-
suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
|
2398
|
-
shorttext.substring(0, j));
|
2399
|
-
if (bestCommon.length < suffixLength + prefixLength) {
|
2400
|
-
bestCommon = shorttext.substring(j - suffixLength, j) +
|
2401
|
-
shorttext.substring(j, j + prefixLength);
|
2402
|
-
bestLongtextA = longtext.substring(0, i - suffixLength);
|
2403
|
-
bestLongtextB = longtext.substring(i + prefixLength);
|
2404
|
-
bestShorttextA = shorttext.substring(0, j - suffixLength);
|
2405
|
-
bestShorttextB = shorttext.substring(j + prefixLength);
|
2406
|
-
}
|
2407
|
-
}
|
2408
|
-
if (bestCommon.length * 2 >= longtext.length) {
|
2409
|
-
return [ bestLongtextA, bestLongtextB,
|
2410
|
-
bestShorttextA, bestShorttextB, bestCommon
|
2411
|
-
];
|
2412
|
-
} else {
|
2413
|
-
return null;
|
2414
|
-
}
|
2415
|
-
}
|
2416
|
-
|
2417
|
-
// First check if the second quarter is the seed for a half-match.
|
2418
|
-
hm1 = diffHalfMatchI(longtext, shorttext,
|
2419
|
-
Math.ceil(longtext.length / 4));
|
2420
|
-
// Check again based on the third quarter.
|
2421
|
-
hm2 = diffHalfMatchI(longtext, shorttext,
|
2422
|
-
Math.ceil(longtext.length / 2));
|
2423
|
-
if (!hm1 && !hm2) {
|
2424
|
-
return null;
|
2425
|
-
} else if (!hm2) {
|
2426
|
-
hm = hm1;
|
2427
|
-
} else if (!hm1) {
|
2428
|
-
hm = hm2;
|
2429
|
-
} else {
|
2430
|
-
// Both matched. Select the longest.
|
2431
|
-
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
|
2432
|
-
}
|
2433
|
-
|
2434
|
-
// A half-match was found, sort out the return data.
|
2435
|
-
text1A, text1B, text2A, text2B;
|
2436
|
-
if (text1.length > text2.length) {
|
2437
|
-
text1A = hm[0];
|
2438
|
-
text1B = hm[1];
|
2439
|
-
text2A = hm[2];
|
2440
|
-
text2B = hm[3];
|
2441
|
-
} else {
|
2442
|
-
text2A = hm[0];
|
2443
|
-
text2B = hm[1];
|
2444
|
-
text1A = hm[2];
|
2445
|
-
text1B = hm[3];
|
2446
|
-
}
|
2447
|
-
midCommon = hm[4];
|
2448
|
-
return [ text1A, text1B, text2A, text2B, midCommon ];
|
2449
|
-
};
|
2450
|
-
|
2451
|
-
/**
|
2452
|
-
* Do a quick line-level diff on both strings, then rediff the parts for
|
2453
|
-
* greater accuracy.
|
2454
|
-
* This speedup can produce non-minimal diffs.
|
2455
|
-
* @param {string} text1 Old string to be diffed.
|
2456
|
-
* @param {string} text2 New string to be diffed.
|
2457
|
-
* @param {number} deadline Time when the diff should be complete by.
|
2458
|
-
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2459
|
-
* @private
|
2460
|
-
*/
|
2461
|
-
DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
|
2462
|
-
var a, diffs, linearray, pointer, countInsert,
|
2463
|
-
countDelete, textInsert, textDelete, j;
|
2464
|
-
// Scan the text on a line-by-line basis first.
|
2465
|
-
a = this.diffLinesToChars(text1, text2);
|
2466
|
-
text1 = a.chars1;
|
2467
|
-
text2 = a.chars2;
|
2468
|
-
linearray = a.lineArray;
|
2469
|
-
|
2470
|
-
diffs = this.DiffMain(text1, text2, false, deadline);
|
2471
|
-
|
2472
|
-
// Convert the diff back to original text.
|
2473
|
-
this.diffCharsToLines(diffs, linearray);
|
2474
|
-
// Eliminate freak matches (e.g. blank lines)
|
2475
|
-
this.diffCleanupSemantic(diffs);
|
2476
|
-
|
2477
|
-
// Rediff any replacement blocks, this time character-by-character.
|
2478
|
-
// Add a dummy entry at the end.
|
2479
|
-
diffs.push( [ DIFF_EQUAL, "" ] );
|
2480
|
-
pointer = 0;
|
2481
|
-
countDelete = 0;
|
2482
|
-
countInsert = 0;
|
2483
|
-
textDelete = "";
|
2484
|
-
textInsert = "";
|
2485
|
-
while (pointer < diffs.length) {
|
2486
|
-
switch ( diffs[pointer][0] ) {
|
2487
|
-
case DIFF_INSERT:
|
2488
|
-
countInsert++;
|
2489
|
-
textInsert += diffs[pointer][1];
|
2490
|
-
break;
|
2491
|
-
case DIFF_DELETE:
|
2492
|
-
countDelete++;
|
2493
|
-
textDelete += diffs[pointer][1];
|
2494
|
-
break;
|
2495
|
-
case DIFF_EQUAL:
|
2496
|
-
// Upon reaching an equality, check for prior redundancies.
|
2497
|
-
if (countDelete >= 1 && countInsert >= 1) {
|
2498
|
-
// Delete the offending records and add the merged ones.
|
2499
|
-
diffs.splice(pointer - countDelete - countInsert,
|
2500
|
-
countDelete + countInsert);
|
2501
|
-
pointer = pointer - countDelete - countInsert;
|
2502
|
-
a = this.DiffMain(textDelete, textInsert, false, deadline);
|
2503
|
-
for (j = a.length - 1; j >= 0; j--) {
|
2504
|
-
diffs.splice( pointer, 0, a[j] );
|
2505
|
-
}
|
2506
|
-
pointer = pointer + a.length;
|
2507
|
-
}
|
2508
|
-
countInsert = 0;
|
2509
|
-
countDelete = 0;
|
2510
|
-
textDelete = "";
|
2511
|
-
textInsert = "";
|
2512
|
-
break;
|
2513
|
-
}
|
2514
|
-
pointer++;
|
2515
|
-
}
|
2516
|
-
diffs.pop(); // Remove the dummy entry at the end.
|
2517
|
-
|
2518
|
-
return diffs;
|
2519
|
-
};
|
2520
|
-
|
2521
|
-
/**
|
2522
|
-
* Find the 'middle snake' of a diff, split the problem in two
|
2523
|
-
* and return the recursively constructed diff.
|
2524
|
-
* See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
|
2525
|
-
* @param {string} text1 Old string to be diffed.
|
2526
|
-
* @param {string} text2 New string to be diffed.
|
2527
|
-
* @param {number} deadline Time at which to bail if not yet complete.
|
2528
|
-
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2529
|
-
* @private
|
2530
|
-
*/
|
2531
|
-
DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
|
2532
|
-
var text1Length, text2Length, maxD, vOffset, vLength,
|
2533
|
-
v1, v2, x, delta, front, k1start, k1end, k2start,
|
2534
|
-
k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
|
2535
|
-
// Cache the text lengths to prevent multiple calls.
|
2536
|
-
text1Length = text1.length;
|
2537
|
-
text2Length = text2.length;
|
2538
|
-
maxD = Math.ceil((text1Length + text2Length) / 2);
|
2539
|
-
vOffset = maxD;
|
2540
|
-
vLength = 2 * maxD;
|
2541
|
-
v1 = new Array(vLength);
|
2542
|
-
v2 = new Array(vLength);
|
2543
|
-
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
|
2544
|
-
// integers and undefined.
|
2545
|
-
for (x = 0; x < vLength; x++) {
|
2546
|
-
v1[x] = -1;
|
2547
|
-
v2[x] = -1;
|
2548
|
-
}
|
2549
|
-
v1[vOffset + 1] = 0;
|
2550
|
-
v2[vOffset + 1] = 0;
|
2551
|
-
delta = text1Length - text2Length;
|
2552
|
-
// If the total number of characters is odd, then the front path will collide
|
2553
|
-
// with the reverse path.
|
2554
|
-
front = (delta % 2 !== 0);
|
2555
|
-
// Offsets for start and end of k loop.
|
2556
|
-
// Prevents mapping of space beyond the grid.
|
2557
|
-
k1start = 0;
|
2558
|
-
k1end = 0;
|
2559
|
-
k2start = 0;
|
2560
|
-
k2end = 0;
|
2561
|
-
for (d = 0; d < maxD; d++) {
|
2562
|
-
// Bail out if deadline is reached.
|
2563
|
-
if ((new Date()).getTime() > deadline) {
|
2564
|
-
break;
|
2565
|
-
}
|
2566
|
-
|
2567
|
-
// Walk the front path one step.
|
2568
|
-
for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
|
2569
|
-
k1Offset = vOffset + k1;
|
2570
|
-
if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
|
2571
|
-
x1 = v1[k1Offset + 1];
|
2572
|
-
} else {
|
2573
|
-
x1 = v1[k1Offset - 1] + 1;
|
2574
|
-
}
|
2575
|
-
y1 = x1 - k1;
|
2576
|
-
while (x1 < text1Length && y1 < text2Length &&
|
2577
|
-
text1.charAt(x1) === text2.charAt(y1)) {
|
2578
|
-
x1++;
|
2579
|
-
y1++;
|
2580
|
-
}
|
2581
|
-
v1[k1Offset] = x1;
|
2582
|
-
if (x1 > text1Length) {
|
2583
|
-
// Ran off the right of the graph.
|
2584
|
-
k1end += 2;
|
2585
|
-
} else if (y1 > text2Length) {
|
2586
|
-
// Ran off the bottom of the graph.
|
2587
|
-
k1start += 2;
|
2588
|
-
} else if (front) {
|
2589
|
-
k2Offset = vOffset + delta - k1;
|
2590
|
-
if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
|
2591
|
-
// Mirror x2 onto top-left coordinate system.
|
2592
|
-
x2 = text1Length - v2[k2Offset];
|
2593
|
-
if (x1 >= x2) {
|
2594
|
-
// Overlap detected.
|
2595
|
-
return this.diffBisectSplit(text1, text2, x1, y1, deadline);
|
2596
|
-
}
|
2597
|
-
}
|
2598
|
-
}
|
2599
|
-
}
|
2600
|
-
|
2601
|
-
// Walk the reverse path one step.
|
2602
|
-
for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
|
2603
|
-
k2Offset = vOffset + k2;
|
2604
|
-
if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
|
2605
|
-
x2 = v2[k2Offset + 1];
|
2606
|
-
} else {
|
2607
|
-
x2 = v2[k2Offset - 1] + 1;
|
2608
|
-
}
|
2609
|
-
y2 = x2 - k2;
|
2610
|
-
while (x2 < text1Length && y2 < text2Length &&
|
2611
|
-
text1.charAt(text1Length - x2 - 1) ===
|
2612
|
-
text2.charAt(text2Length - y2 - 1)) {
|
2613
|
-
x2++;
|
2614
|
-
y2++;
|
2615
|
-
}
|
2616
|
-
v2[k2Offset] = x2;
|
2617
|
-
if (x2 > text1Length) {
|
2618
|
-
// Ran off the left of the graph.
|
2619
|
-
k2end += 2;
|
2620
|
-
} else if (y2 > text2Length) {
|
2621
|
-
// Ran off the top of the graph.
|
2622
|
-
k2start += 2;
|
2623
|
-
} else if (!front) {
|
2624
|
-
k1Offset = vOffset + delta - k2;
|
2625
|
-
if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
|
2626
|
-
x1 = v1[k1Offset];
|
2627
|
-
y1 = vOffset + x1 - k1Offset;
|
2628
|
-
// Mirror x2 onto top-left coordinate system.
|
2629
|
-
x2 = text1Length - x2;
|
2630
|
-
if (x1 >= x2) {
|
2631
|
-
// Overlap detected.
|
2632
|
-
return this.diffBisectSplit(text1, text2, x1, y1, deadline);
|
2633
|
-
}
|
2634
|
-
}
|
2635
|
-
}
|
2636
|
-
}
|
2637
|
-
}
|
2638
|
-
// Diff took too long and hit the deadline or
|
2639
|
-
// number of diffs equals number of characters, no commonality at all.
|
2640
|
-
return [
|
2641
|
-
[ DIFF_DELETE, text1 ],
|
2642
|
-
[ DIFF_INSERT, text2 ]
|
2643
|
-
];
|
2644
|
-
};
|
2645
|
-
|
2646
|
-
/**
|
2647
|
-
* Given the location of the 'middle snake', split the diff in two parts
|
2648
|
-
* and recurse.
|
2649
|
-
* @param {string} text1 Old string to be diffed.
|
2650
|
-
* @param {string} text2 New string to be diffed.
|
2651
|
-
* @param {number} x Index of split point in text1.
|
2652
|
-
* @param {number} y Index of split point in text2.
|
2653
|
-
* @param {number} deadline Time at which to bail if not yet complete.
|
2654
|
-
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
|
2655
|
-
* @private
|
2656
|
-
*/
|
2657
|
-
DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
|
2658
|
-
var text1a, text1b, text2a, text2b, diffs, diffsb;
|
2659
|
-
text1a = text1.substring(0, x);
|
2660
|
-
text2a = text2.substring(0, y);
|
2661
|
-
text1b = text1.substring(x);
|
2662
|
-
text2b = text2.substring(y);
|
2663
|
-
|
2664
|
-
// Compute both diffs serially.
|
2665
|
-
diffs = this.DiffMain(text1a, text2a, false, deadline);
|
2666
|
-
diffsb = this.DiffMain(text1b, text2b, false, deadline);
|
2667
|
-
|
2668
|
-
return diffs.concat(diffsb);
|
2669
|
-
};
|
2670
|
-
|
2671
|
-
/**
|
2672
|
-
* Reduce the number of edits by eliminating semantically trivial equalities.
|
2673
|
-
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2674
|
-
*/
|
2675
|
-
DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
|
2676
|
-
var changes, equalities, equalitiesLength, lastequality,
|
2677
|
-
pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
|
2678
|
-
lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
|
2679
|
-
changes = false;
|
2680
|
-
equalities = []; // Stack of indices where equalities are found.
|
2681
|
-
equalitiesLength = 0; // Keeping our own length var is faster in JS.
|
2682
|
-
/** @type {?string} */
|
2683
|
-
lastequality = null;
|
2684
|
-
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
|
2685
|
-
pointer = 0; // Index of current position.
|
2686
|
-
// Number of characters that changed prior to the equality.
|
2687
|
-
lengthInsertions1 = 0;
|
2688
|
-
lengthDeletions1 = 0;
|
2689
|
-
// Number of characters that changed after the equality.
|
2690
|
-
lengthInsertions2 = 0;
|
2691
|
-
lengthDeletions2 = 0;
|
2692
|
-
while (pointer < diffs.length) {
|
2693
|
-
if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
|
2694
|
-
equalities[equalitiesLength++] = pointer;
|
2695
|
-
lengthInsertions1 = lengthInsertions2;
|
2696
|
-
lengthDeletions1 = lengthDeletions2;
|
2697
|
-
lengthInsertions2 = 0;
|
2698
|
-
lengthDeletions2 = 0;
|
2699
|
-
lastequality = diffs[pointer][1];
|
2700
|
-
} else { // An insertion or deletion.
|
2701
|
-
if (diffs[pointer][0] === DIFF_INSERT) {
|
2702
|
-
lengthInsertions2 += diffs[pointer][1].length;
|
2703
|
-
} else {
|
2704
|
-
lengthDeletions2 += diffs[pointer][1].length;
|
2705
|
-
}
|
2706
|
-
// Eliminate an equality that is smaller or equal to the edits on both
|
2707
|
-
// sides of it.
|
2708
|
-
if (lastequality && (lastequality.length <=
|
2709
|
-
Math.max(lengthInsertions1, lengthDeletions1)) &&
|
2710
|
-
(lastequality.length <= Math.max(lengthInsertions2,
|
2711
|
-
lengthDeletions2))) {
|
2712
|
-
// Duplicate record.
|
2713
|
-
diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
|
2714
|
-
// Change second copy to insert.
|
2715
|
-
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
|
2716
|
-
// Throw away the equality we just deleted.
|
2717
|
-
equalitiesLength--;
|
2718
|
-
// Throw away the previous equality (it needs to be reevaluated).
|
2719
|
-
equalitiesLength--;
|
2720
|
-
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
|
2721
|
-
lengthInsertions1 = 0; // Reset the counters.
|
2722
|
-
lengthDeletions1 = 0;
|
2723
|
-
lengthInsertions2 = 0;
|
2724
|
-
lengthDeletions2 = 0;
|
2725
|
-
lastequality = null;
|
2726
|
-
changes = true;
|
2727
|
-
}
|
2728
|
-
}
|
2729
|
-
pointer++;
|
2730
|
-
}
|
2731
|
-
|
2732
|
-
// Normalize the diff.
|
2733
|
-
if (changes) {
|
2734
|
-
this.diffCleanupMerge(diffs);
|
2735
|
-
}
|
2736
|
-
|
2737
|
-
// Find any overlaps between deletions and insertions.
|
2738
|
-
// e.g: <del>abcxxx</del><ins>xxxdef</ins>
|
2739
|
-
// -> <del>abc</del>xxx<ins>def</ins>
|
2740
|
-
// e.g: <del>xxxabc</del><ins>defxxx</ins>
|
2741
|
-
// -> <ins>def</ins>xxx<del>abc</del>
|
2742
|
-
// Only extract an overlap if it is as big as the edit ahead or behind it.
|
2743
|
-
pointer = 1;
|
2744
|
-
while (pointer < diffs.length) {
|
2745
|
-
if (diffs[pointer - 1][0] === DIFF_DELETE &&
|
2746
|
-
diffs[pointer][0] === DIFF_INSERT) {
|
2747
|
-
deletion = diffs[pointer - 1][1];
|
2748
|
-
insertion = diffs[pointer][1];
|
2749
|
-
overlapLength1 = this.diffCommonOverlap(deletion, insertion);
|
2750
|
-
overlapLength2 = this.diffCommonOverlap(insertion, deletion);
|
2751
|
-
if (overlapLength1 >= overlapLength2) {
|
2752
|
-
if (overlapLength1 >= deletion.length / 2 ||
|
2753
|
-
overlapLength1 >= insertion.length / 2) {
|
2754
|
-
// Overlap found. Insert an equality and trim the surrounding edits.
|
2755
|
-
diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
|
2756
|
-
diffs[pointer - 1][1] =
|
2757
|
-
deletion.substring(0, deletion.length - overlapLength1);
|
2758
|
-
diffs[pointer + 1][1] = insertion.substring(overlapLength1);
|
2759
|
-
pointer++;
|
2760
|
-
}
|
2761
|
-
} else {
|
2762
|
-
if (overlapLength2 >= deletion.length / 2 ||
|
2763
|
-
overlapLength2 >= insertion.length / 2) {
|
2764
|
-
// Reverse overlap found.
|
2765
|
-
// Insert an equality and swap and trim the surrounding edits.
|
2766
|
-
diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
|
2767
|
-
diffs[pointer - 1][0] = DIFF_INSERT;
|
2768
|
-
diffs[pointer - 1][1] =
|
2769
|
-
insertion.substring(0, insertion.length - overlapLength2);
|
2770
|
-
diffs[pointer + 1][0] = DIFF_DELETE;
|
2771
|
-
diffs[pointer + 1][1] =
|
2772
|
-
deletion.substring(overlapLength2);
|
2773
|
-
pointer++;
|
2774
|
-
}
|
2775
|
-
}
|
2776
|
-
pointer++;
|
2777
|
-
}
|
2778
|
-
pointer++;
|
2779
|
-
}
|
2780
|
-
};
|
2781
|
-
|
2782
|
-
/**
|
2783
|
-
* Determine if the suffix of one string is the prefix of another.
|
2784
|
-
* @param {string} text1 First string.
|
2785
|
-
* @param {string} text2 Second string.
|
2786
|
-
* @return {number} The number of characters common to the end of the first
|
2787
|
-
* string and the start of the second string.
|
2788
|
-
* @private
|
2789
|
-
*/
|
2790
|
-
DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
|
2791
|
-
var text1Length, text2Length, textLength,
|
2792
|
-
best, length, pattern, found;
|
2793
|
-
// Cache the text lengths to prevent multiple calls.
|
2794
|
-
text1Length = text1.length;
|
2795
|
-
text2Length = text2.length;
|
2796
|
-
// Eliminate the null case.
|
2797
|
-
if (text1Length === 0 || text2Length === 0) {
|
2798
|
-
return 0;
|
2799
|
-
}
|
2800
|
-
// Truncate the longer string.
|
2801
|
-
if (text1Length > text2Length) {
|
2802
|
-
text1 = text1.substring(text1Length - text2Length);
|
2803
|
-
} else if (text1Length < text2Length) {
|
2804
|
-
text2 = text2.substring(0, text1Length);
|
2805
|
-
}
|
2806
|
-
textLength = Math.min(text1Length, text2Length);
|
2807
|
-
// Quick check for the worst case.
|
2808
|
-
if (text1 === text2) {
|
2809
|
-
return textLength;
|
2810
|
-
}
|
2811
|
-
|
2812
|
-
// Start by looking for a single character match
|
2813
|
-
// and increase length until no match is found.
|
2814
|
-
// Performance analysis: http://neil.fraser.name/news/2010/11/04/
|
2815
|
-
best = 0;
|
2816
|
-
length = 1;
|
2817
|
-
while (true) {
|
2818
|
-
pattern = text1.substring(textLength - length);
|
2819
|
-
found = text2.indexOf(pattern);
|
2820
|
-
if (found === -1) {
|
2821
|
-
return best;
|
2822
|
-
}
|
2823
|
-
length += found;
|
2824
|
-
if (found === 0 || text1.substring(textLength - length) ===
|
2825
|
-
text2.substring(0, length)) {
|
2826
|
-
best = length;
|
2827
|
-
length++;
|
2828
|
-
}
|
2829
|
-
}
|
2830
|
-
};
|
2831
|
-
|
2832
|
-
/**
|
2833
|
-
* Split two texts into an array of strings. Reduce the texts to a string of
|
2834
|
-
* hashes where each Unicode character represents one line.
|
2835
|
-
* @param {string} text1 First string.
|
2836
|
-
* @param {string} text2 Second string.
|
2837
|
-
* @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
|
2838
|
-
* An object containing the encoded text1, the encoded text2 and
|
2839
|
-
* the array of unique strings.
|
2840
|
-
* The zeroth element of the array of unique strings is intentionally blank.
|
2841
|
-
* @private
|
2842
|
-
*/
|
2843
|
-
DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
|
2844
|
-
var lineArray, lineHash, chars1, chars2;
|
2845
|
-
lineArray = []; // e.g. lineArray[4] === 'Hello\n'
|
2846
|
-
lineHash = {}; // e.g. lineHash['Hello\n'] === 4
|
2847
|
-
|
2848
|
-
// '\x00' is a valid character, but various debuggers don't like it.
|
2849
|
-
// So we'll insert a junk entry to avoid generating a null character.
|
2850
|
-
lineArray[0] = "";
|
2851
|
-
|
2852
|
-
/**
|
2853
|
-
* Split a text into an array of strings. Reduce the texts to a string of
|
2854
|
-
* hashes where each Unicode character represents one line.
|
2855
|
-
* Modifies linearray and linehash through being a closure.
|
2856
|
-
* @param {string} text String to encode.
|
2857
|
-
* @return {string} Encoded string.
|
2858
|
-
* @private
|
2859
|
-
*/
|
2860
|
-
function diffLinesToCharsMunge(text) {
|
2861
|
-
var chars, lineStart, lineEnd, lineArrayLength, line;
|
2862
|
-
chars = "";
|
2863
|
-
// Walk the text, pulling out a substring for each line.
|
2864
|
-
// text.split('\n') would would temporarily double our memory footprint.
|
2865
|
-
// Modifying text would create many large strings to garbage collect.
|
2866
|
-
lineStart = 0;
|
2867
|
-
lineEnd = -1;
|
2868
|
-
// Keeping our own length variable is faster than looking it up.
|
2869
|
-
lineArrayLength = lineArray.length;
|
2870
|
-
while (lineEnd < text.length - 1) {
|
2871
|
-
lineEnd = text.indexOf("\n", lineStart);
|
2872
|
-
if (lineEnd === -1) {
|
2873
|
-
lineEnd = text.length - 1;
|
2874
|
-
}
|
2875
|
-
line = text.substring(lineStart, lineEnd + 1);
|
2876
|
-
lineStart = lineEnd + 1;
|
2877
|
-
|
2878
|
-
if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
|
2879
|
-
(lineHash[line] !== undefined)) {
|
2880
|
-
chars += String.fromCharCode( lineHash[ line ] );
|
2881
|
-
} else {
|
2882
|
-
chars += String.fromCharCode(lineArrayLength);
|
2883
|
-
lineHash[line] = lineArrayLength;
|
2884
|
-
lineArray[lineArrayLength++] = line;
|
2885
|
-
}
|
2886
|
-
}
|
2887
|
-
return chars;
|
2888
|
-
}
|
2889
|
-
|
2890
|
-
chars1 = diffLinesToCharsMunge(text1);
|
2891
|
-
chars2 = diffLinesToCharsMunge(text2);
|
2892
|
-
return {
|
2893
|
-
chars1: chars1,
|
2894
|
-
chars2: chars2,
|
2895
|
-
lineArray: lineArray
|
2896
|
-
};
|
2897
|
-
};
|
2898
|
-
|
2899
|
-
/**
|
2900
|
-
* Rehydrate the text in a diff from a string of line hashes to real lines of
|
2901
|
-
* text.
|
2902
|
-
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2903
|
-
* @param {!Array.<string>} lineArray Array of unique strings.
|
2904
|
-
* @private
|
2905
|
-
*/
|
2906
|
-
DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
|
2907
|
-
var x, chars, text, y;
|
2908
|
-
for ( x = 0; x < diffs.length; x++ ) {
|
2909
|
-
chars = diffs[x][1];
|
2910
|
-
text = [];
|
2911
|
-
for ( y = 0; y < chars.length; y++ ) {
|
2912
|
-
text[y] = lineArray[chars.charCodeAt(y)];
|
2913
|
-
}
|
2914
|
-
diffs[x][1] = text.join("");
|
2915
|
-
}
|
2916
|
-
};
|
2917
|
-
|
2918
|
-
/**
|
2919
|
-
* Reorder and merge like edit sections. Merge equalities.
|
2920
|
-
* Any edit section can move as long as it doesn't cross an equality.
|
2921
|
-
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
|
2922
|
-
*/
|
2923
|
-
DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
|
2924
|
-
var pointer, countDelete, countInsert, textInsert, textDelete,
|
2925
|
-
commonlength, changes;
|
2926
|
-
diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
|
2927
|
-
pointer = 0;
|
2928
|
-
countDelete = 0;
|
2929
|
-
countInsert = 0;
|
2930
|
-
textDelete = "";
|
2931
|
-
textInsert = "";
|
2932
|
-
commonlength;
|
2933
|
-
while (pointer < diffs.length) {
|
2934
|
-
switch ( diffs[ pointer ][ 0 ] ) {
|
2935
|
-
case DIFF_INSERT:
|
2936
|
-
countInsert++;
|
2937
|
-
textInsert += diffs[pointer][1];
|
2938
|
-
pointer++;
|
2939
|
-
break;
|
2940
|
-
case DIFF_DELETE:
|
2941
|
-
countDelete++;
|
2942
|
-
textDelete += diffs[pointer][1];
|
2943
|
-
pointer++;
|
2944
|
-
break;
|
2945
|
-
case DIFF_EQUAL:
|
2946
|
-
// Upon reaching an equality, check for prior redundancies.
|
2947
|
-
if (countDelete + countInsert > 1) {
|
2948
|
-
if (countDelete !== 0 && countInsert !== 0) {
|
2949
|
-
// Factor out any common prefixies.
|
2950
|
-
commonlength = this.diffCommonPrefix(textInsert, textDelete);
|
2951
|
-
if (commonlength !== 0) {
|
2952
|
-
if ((pointer - countDelete - countInsert) > 0 &&
|
2953
|
-
diffs[pointer - countDelete - countInsert - 1][0] ===
|
2954
|
-
DIFF_EQUAL) {
|
2955
|
-
diffs[pointer - countDelete - countInsert - 1][1] +=
|
2956
|
-
textInsert.substring(0, commonlength);
|
2957
|
-
} else {
|
2958
|
-
diffs.splice( 0, 0, [ DIFF_EQUAL,
|
2959
|
-
textInsert.substring( 0, commonlength )
|
2960
|
-
] );
|
2961
|
-
pointer++;
|
2962
|
-
}
|
2963
|
-
textInsert = textInsert.substring(commonlength);
|
2964
|
-
textDelete = textDelete.substring(commonlength);
|
2965
|
-
}
|
2966
|
-
// Factor out any common suffixies.
|
2967
|
-
commonlength = this.diffCommonSuffix(textInsert, textDelete);
|
2968
|
-
if (commonlength !== 0) {
|
2969
|
-
diffs[pointer][1] = textInsert.substring(textInsert.length -
|
2970
|
-
commonlength) + diffs[pointer][1];
|
2971
|
-
textInsert = textInsert.substring(0, textInsert.length -
|
2972
|
-
commonlength);
|
2973
|
-
textDelete = textDelete.substring(0, textDelete.length -
|
2974
|
-
commonlength);
|
2975
|
-
}
|
2976
|
-
}
|
2977
|
-
// Delete the offending records and add the merged ones.
|
2978
|
-
if (countDelete === 0) {
|
2979
|
-
diffs.splice( pointer - countInsert,
|
2980
|
-
countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
|
2981
|
-
} else if (countInsert === 0) {
|
2982
|
-
diffs.splice( pointer - countDelete,
|
2983
|
-
countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
|
2984
|
-
} else {
|
2985
|
-
diffs.splice( pointer - countDelete - countInsert,
|
2986
|
-
countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
|
2987
|
-
}
|
2988
|
-
pointer = pointer - countDelete - countInsert +
|
2989
|
-
(countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
|
2990
|
-
} else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
|
2991
|
-
// Merge this equality with the previous one.
|
2992
|
-
diffs[pointer - 1][1] += diffs[pointer][1];
|
2993
|
-
diffs.splice(pointer, 1);
|
2994
|
-
} else {
|
2995
|
-
pointer++;
|
2996
|
-
}
|
2997
|
-
countInsert = 0;
|
2998
|
-
countDelete = 0;
|
2999
|
-
textDelete = "";
|
3000
|
-
textInsert = "";
|
3001
|
-
break;
|
3002
|
-
}
|
3003
|
-
}
|
3004
|
-
if (diffs[diffs.length - 1][1] === "") {
|
3005
|
-
diffs.pop(); // Remove the dummy entry at the end.
|
3006
|
-
}
|
3007
|
-
|
3008
|
-
// Second pass: look for single edits surrounded on both sides by equalities
|
3009
|
-
// which can be shifted sideways to eliminate an equality.
|
3010
|
-
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
|
3011
|
-
changes = false;
|
3012
|
-
pointer = 1;
|
3013
|
-
// Intentionally ignore the first and last element (don't need checking).
|
3014
|
-
while (pointer < diffs.length - 1) {
|
3015
|
-
if (diffs[pointer - 1][0] === DIFF_EQUAL &&
|
3016
|
-
diffs[pointer + 1][0] === DIFF_EQUAL) {
|
3017
|
-
// This is a single edit surrounded by equalities.
|
3018
|
-
if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
|
3019
|
-
diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
|
3020
|
-
// Shift the edit over the previous equality.
|
3021
|
-
diffs[pointer][1] = diffs[pointer - 1][1] +
|
3022
|
-
diffs[pointer][1].substring(0, diffs[pointer][1].length -
|
3023
|
-
diffs[pointer - 1][1].length);
|
3024
|
-
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
|
3025
|
-
diffs.splice(pointer - 1, 1);
|
3026
|
-
changes = true;
|
3027
|
-
} else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
|
3028
|
-
diffs[ pointer + 1 ][ 1 ] ) {
|
3029
|
-
// Shift the edit over the next equality.
|
3030
|
-
diffs[pointer - 1][1] += diffs[pointer + 1][1];
|
3031
|
-
diffs[pointer][1] =
|
3032
|
-
diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
|
3033
|
-
diffs[pointer + 1][1];
|
3034
|
-
diffs.splice(pointer + 1, 1);
|
3035
|
-
changes = true;
|
3036
|
-
}
|
3037
|
-
}
|
3038
|
-
pointer++;
|
3039
|
-
}
|
3040
|
-
// If shifts were made, the diff needs reordering and another shift sweep.
|
3041
|
-
if (changes) {
|
3042
|
-
this.diffCleanupMerge(diffs);
|
3043
|
-
}
|
3044
|
-
};
|
3045
|
-
|
3046
|
-
return function(o, n) {
|
3047
|
-
var diff, output, text;
|
3048
|
-
diff = new DiffMatchPatch();
|
3049
|
-
output = diff.DiffMain(o, n);
|
3050
|
-
//console.log(output);
|
3051
|
-
diff.diffCleanupEfficiency(output);
|
3052
|
-
text = diff.diffPrettyHtml(output);
|
3053
|
-
|
3054
|
-
return text;
|
3055
|
-
};
|
3056
|
-
}());
|
3057
|
-
// jscs:enable
|
3058
|
-
|
3059
3322
|
(function() {
|
3060
3323
|
|
3324
|
+
// Don't load the HTML Reporter on non-Browser environments
|
3325
|
+
if ( typeof window === "undefined" || !window.document ) {
|
3326
|
+
return;
|
3327
|
+
}
|
3328
|
+
|
3061
3329
|
// Deprecated QUnit.init - Ref #530
|
3062
3330
|
// Re-initialize the configuration options
|
3063
3331
|
QUnit.init = function() {
|
@@ -3115,12 +3383,8 @@ QUnit.init = function() {
|
|
3115
3383
|
}
|
3116
3384
|
};
|
3117
3385
|
|
3118
|
-
// Don't load the HTML Reporter on non-Browser environments
|
3119
|
-
if ( typeof window === "undefined" ) {
|
3120
|
-
return;
|
3121
|
-
}
|
3122
|
-
|
3123
3386
|
var config = QUnit.config,
|
3387
|
+
collapseNext = false,
|
3124
3388
|
hasOwn = Object.prototype.hasOwnProperty,
|
3125
3389
|
defined = {
|
3126
3390
|
document: window.document !== undefined,
|
@@ -3517,6 +3781,17 @@ function storeFixture() {
|
|
3517
3781
|
}
|
3518
3782
|
}
|
3519
3783
|
|
3784
|
+
function appendFilteredTest() {
|
3785
|
+
var testId = QUnit.config.testId;
|
3786
|
+
if ( !testId || testId.length <= 0 ) {
|
3787
|
+
return "";
|
3788
|
+
}
|
3789
|
+
return "<div id='qunit-filteredTest'>Rerunning selected tests: " + testId.join(", ") +
|
3790
|
+
" <a id='qunit-clearFilter' href='" +
|
3791
|
+
setUrl({ filter: undefined, module: undefined, testId: undefined }) +
|
3792
|
+
"'>" + "Run all tests" + "</a></div>";
|
3793
|
+
}
|
3794
|
+
|
3520
3795
|
function appendUserAgent() {
|
3521
3796
|
var userAgent = id( "qunit-userAgent" );
|
3522
3797
|
|
@@ -3524,7 +3799,7 @@ function appendUserAgent() {
|
|
3524
3799
|
userAgent.innerHTML = "";
|
3525
3800
|
userAgent.appendChild(
|
3526
3801
|
document.createTextNode(
|
3527
|
-
"QUnit " + QUnit.version
|
3802
|
+
"QUnit " + QUnit.version + "; " + navigator.userAgent
|
3528
3803
|
)
|
3529
3804
|
);
|
3530
3805
|
}
|
@@ -3588,6 +3863,7 @@ QUnit.begin(function( details ) {
|
|
3588
3863
|
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
|
3589
3864
|
"<h2 id='qunit-banner'></h2>" +
|
3590
3865
|
"<div id='qunit-testrunner-toolbar'></div>" +
|
3866
|
+
appendFilteredTest() +
|
3591
3867
|
"<h2 id='qunit-userAgent'></h2>" +
|
3592
3868
|
"<ol id='qunit-tests'></ol>";
|
3593
3869
|
}
|
@@ -3632,7 +3908,7 @@ QUnit.done(function( details ) {
|
|
3632
3908
|
|
3633
3909
|
if ( config.altertitle && defined.document && document.title ) {
|
3634
3910
|
|
3635
|
-
// show
|
3911
|
+
// show ✖ for good, ✔ for bad suite result in title
|
3636
3912
|
// use escape sequences in case file gets loaded with non-utf-8-charset
|
3637
3913
|
document.title = [
|
3638
3914
|
( details.failed ? "\u2716" : "\u2714" ),
|
@@ -3693,9 +3969,15 @@ QUnit.testStart(function( details ) {
|
|
3693
3969
|
|
3694
3970
|
});
|
3695
3971
|
|
3972
|
+
function stripHtml( string ) {
|
3973
|
+
// strip tags, html entity and whitespaces
|
3974
|
+
return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, "");
|
3975
|
+
}
|
3976
|
+
|
3696
3977
|
QUnit.log(function( details ) {
|
3697
3978
|
var assertList, assertLi,
|
3698
|
-
message, expected, actual,
|
3979
|
+
message, expected, actual, diff,
|
3980
|
+
showDiff = false,
|
3699
3981
|
testItem = id( "qunit-test-output-" + details.testId );
|
3700
3982
|
|
3701
3983
|
if ( !testItem ) {
|
@@ -3710,26 +3992,43 @@ QUnit.log(function( details ) {
|
|
3710
3992
|
// when it calls, it's implicit to also not show expected and diff stuff
|
3711
3993
|
// Also, we need to check details.expected existence, as it can exist and be undefined
|
3712
3994
|
if ( !details.result && hasOwn.call( details, "expected" ) ) {
|
3713
|
-
|
3995
|
+
if ( details.negative ) {
|
3996
|
+
expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
|
3997
|
+
} else {
|
3998
|
+
expected = escapeText( QUnit.dump.parse( details.expected ) );
|
3999
|
+
}
|
4000
|
+
|
3714
4001
|
actual = escapeText( QUnit.dump.parse( details.actual ) );
|
3715
4002
|
message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
|
3716
4003
|
expected +
|
3717
4004
|
"</pre></td></tr>";
|
3718
4005
|
|
3719
4006
|
if ( actual !== expected ) {
|
4007
|
+
|
3720
4008
|
message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
|
3721
|
-
actual + "</pre></td></tr>"
|
3722
|
-
|
3723
|
-
|
3724
|
-
|
3725
|
-
|
3726
|
-
|
3727
|
-
|
3728
|
-
|
3729
|
-
|
3730
|
-
|
3731
|
-
|
4009
|
+
actual + "</pre></td></tr>";
|
4010
|
+
|
4011
|
+
// Don't show diff if actual or expected are booleans
|
4012
|
+
if ( !( /^(true|false)$/.test( actual ) ) &&
|
4013
|
+
!( /^(true|false)$/.test( expected ) ) ) {
|
4014
|
+
diff = QUnit.diff( expected, actual );
|
4015
|
+
showDiff = stripHtml( diff ).length !==
|
4016
|
+
stripHtml( expected ).length +
|
4017
|
+
stripHtml( actual ).length;
|
4018
|
+
}
|
4019
|
+
|
4020
|
+
// Don't show diff if expected and actual are totally different
|
4021
|
+
if ( showDiff ) {
|
4022
|
+
message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
|
4023
|
+
diff + "</pre></td></tr>";
|
3732
4024
|
}
|
4025
|
+
} else if ( expected.indexOf( "[object Array]" ) !== -1 ||
|
4026
|
+
expected.indexOf( "[object Object]" ) !== -1 ) {
|
4027
|
+
message += "<tr class='test-message'><th>Message: </th><td>" +
|
4028
|
+
"Diff suppressed as the depth of object is more than current max depth (" +
|
4029
|
+
QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
|
4030
|
+
" run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
|
4031
|
+
"Rerun</a> without max depth.</p></td></tr>";
|
3733
4032
|
}
|
3734
4033
|
|
3735
4034
|
if ( details.source ) {
|
@@ -3757,7 +4056,7 @@ QUnit.log(function( details ) {
|
|
3757
4056
|
|
3758
4057
|
QUnit.testDone(function( details ) {
|
3759
4058
|
var testTitle, time, testItem, assertList,
|
3760
|
-
good, bad, testCounts, skipped,
|
4059
|
+
good, bad, testCounts, skipped, sourceName,
|
3761
4060
|
tests = id( "qunit-tests" );
|
3762
4061
|
|
3763
4062
|
if ( !tests ) {
|
@@ -3781,6 +4080,16 @@ QUnit.testDone(function( details ) {
|
|
3781
4080
|
}
|
3782
4081
|
|
3783
4082
|
if ( bad === 0 ) {
|
4083
|
+
|
4084
|
+
// Collapse the passing tests
|
4085
|
+
addClass( assertList, "qunit-collapsed" );
|
4086
|
+
} else if ( bad && config.collapse && !collapseNext ) {
|
4087
|
+
|
4088
|
+
// Skip collapsing the first failing test
|
4089
|
+
collapseNext = true;
|
4090
|
+
} else {
|
4091
|
+
|
4092
|
+
// Collapse remaining tests
|
3784
4093
|
addClass( assertList, "qunit-collapsed" );
|
3785
4094
|
}
|
3786
4095
|
|
@@ -3812,10 +4121,31 @@ QUnit.testDone(function( details ) {
|
|
3812
4121
|
time.innerHTML = details.runtime + " ms";
|
3813
4122
|
testItem.insertBefore( time, assertList );
|
3814
4123
|
}
|
4124
|
+
|
4125
|
+
// Show the source of the test when showing assertions
|
4126
|
+
if ( details.source ) {
|
4127
|
+
sourceName = document.createElement( "p" );
|
4128
|
+
sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
|
4129
|
+
addClass( sourceName, "qunit-source" );
|
4130
|
+
if ( bad === 0 ) {
|
4131
|
+
addClass( sourceName, "qunit-collapsed" );
|
4132
|
+
}
|
4133
|
+
addEvent( testTitle, "click", function() {
|
4134
|
+
toggleClass( sourceName, "qunit-collapsed" );
|
4135
|
+
});
|
4136
|
+
testItem.appendChild( sourceName );
|
4137
|
+
}
|
3815
4138
|
});
|
3816
4139
|
|
3817
4140
|
if ( defined.document ) {
|
3818
|
-
|
4141
|
+
|
4142
|
+
// Avoid readyState issue with phantomjs
|
4143
|
+
// Ref: #818
|
4144
|
+
var notPhantom = ( function( p ) {
|
4145
|
+
return !( p && p.version && p.version.major > 0 );
|
4146
|
+
} )( window.phantom );
|
4147
|
+
|
4148
|
+
if ( notPhantom && document.readyState === "complete" ) {
|
3819
4149
|
QUnit.load();
|
3820
4150
|
} else {
|
3821
4151
|
addEvent( window, "load", QUnit.load );
|