";
3055 |
3056 | // This occurs when pushFailure is set and we have an extracted stack trace
3057 | } else if ( !details.result && details.source ) {
3058 | message += "
" +
3059 | "
Source:
" +
3060 | escapeText( details.source ) + "
" +
3061 | "
";
3062 | }
3063 |
3064 | assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3065 |
3066 | assertLi = document.createElement( "li" );
3067 | assertLi.className = details.result ? "pass" : "fail";
3068 | assertLi.innerHTML = message;
3069 | assertList.appendChild( assertLi );
3070 | } );
3071 |
3072 | QUnit.testDone( function( details ) {
3073 | var testTitle, time, testItem, assertList,
3074 | good, bad, testCounts, skipped, sourceName,
3075 | tests = id( "qunit-tests" );
3076 |
3077 | if ( !tests ) {
3078 | return;
3079 | }
3080 |
3081 | testItem = id( "qunit-test-output-" + details.testId );
3082 |
3083 | assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3084 |
3085 | good = details.passed;
3086 | bad = details.failed;
3087 |
3088 | // Store result when possible
3089 | if ( config.reorder && defined.sessionStorage ) {
3090 | if ( bad ) {
3091 | sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
3092 | } else {
3093 | sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
3094 | }
3095 | }
3096 |
3097 | if ( bad === 0 ) {
3098 |
3099 | // Collapse the passing tests
3100 | addClass( assertList, "qunit-collapsed" );
3101 | } else if ( bad && config.collapse && !collapseNext ) {
3102 |
3103 | // Skip collapsing the first failing test
3104 | collapseNext = true;
3105 | } else {
3106 |
3107 | // Collapse remaining tests
3108 | addClass( assertList, "qunit-collapsed" );
3109 | }
3110 |
3111 | // The testItem.firstChild is the test name
3112 | testTitle = testItem.firstChild;
3113 |
3114 | testCounts = bad ?
3115 | "" + bad + ", " + "" + good + ", " :
3116 | "";
3117 |
3118 | testTitle.innerHTML += " (" + testCounts +
3119 | details.assertions.length + ")";
3120 |
3121 | if ( details.skipped ) {
3122 | testItem.className = "skipped";
3123 | skipped = document.createElement( "em" );
3124 | skipped.className = "qunit-skipped-label";
3125 | skipped.innerHTML = "skipped";
3126 | testItem.insertBefore( skipped, testTitle );
3127 | } else {
3128 | addEvent( testTitle, "click", function() {
3129 | toggleClass( assertList, "qunit-collapsed" );
3130 | } );
3131 |
3132 | testItem.className = bad ? "fail" : "pass";
3133 |
3134 | time = document.createElement( "span" );
3135 | time.className = "runtime";
3136 | time.innerHTML = details.runtime + " ms";
3137 | testItem.insertBefore( time, assertList );
3138 | }
3139 |
3140 | // Show the source of the test when showing assertions
3141 | if ( details.source ) {
3142 | sourceName = document.createElement( "p" );
3143 | sourceName.innerHTML = "Source: " + details.source;
3144 | addClass( sourceName, "qunit-source" );
3145 | if ( bad === 0 ) {
3146 | addClass( sourceName, "qunit-collapsed" );
3147 | }
3148 | addEvent( testTitle, "click", function() {
3149 | toggleClass( sourceName, "qunit-collapsed" );
3150 | } );
3151 | testItem.appendChild( sourceName );
3152 | }
3153 | } );
3154 |
3155 | // Avoid readyState issue with phantomjs
3156 | // Ref: #818
3157 | var notPhantom = ( function( p ) {
3158 | return !( p && p.version && p.version.major > 0 );
3159 | } )( window.phantom );
3160 |
3161 | if ( notPhantom && document.readyState === "complete" ) {
3162 | QUnit.load();
3163 | } else {
3164 | addEvent( window, "load", QUnit.load );
3165 | }
3166 |
3167 | /*
3168 | * This file is a modified version of google-diff-match-patch's JavaScript implementation
3169 | * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
3170 | * modifications are licensed as more fully set forth in LICENSE.txt.
3171 | *
3172 | * The original source of google-diff-match-patch is attributable and licensed as follows:
3173 | *
3174 | * Copyright 2006 Google Inc.
3175 | * https://code.google.com/p/google-diff-match-patch/
3176 | *
3177 | * Licensed under the Apache License, Version 2.0 (the "License");
3178 | * you may not use this file except in compliance with the License.
3179 | * You may obtain a copy of the License at
3180 | *
3181 | * https://www.apache.org/licenses/LICENSE-2.0
3182 | *
3183 | * Unless required by applicable law or agreed to in writing, software
3184 | * distributed under the License is distributed on an "AS IS" BASIS,
3185 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3186 | * See the License for the specific language governing permissions and
3187 | * limitations under the License.
3188 | *
3189 | * More Info:
3190 | * https://code.google.com/p/google-diff-match-patch/
3191 | *
3192 | * Usage: QUnit.diff(expected, actual)
3193 | *
3194 | */
3195 | QUnit.diff = ( function() {
3196 | function DiffMatchPatch() {
3197 | }
3198 |
3199 | // DIFF FUNCTIONS
3200 |
3201 | /**
3202 | * The data structure representing a diff is an array of tuples:
3203 | * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
3204 | * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
3205 | */
3206 | var DIFF_DELETE = -1,
3207 | DIFF_INSERT = 1,
3208 | DIFF_EQUAL = 0;
3209 |
3210 | /**
3211 | * Find the differences between two texts. Simplifies the problem by stripping
3212 | * any common prefix or suffix off the texts before diffing.
3213 | * @param {string} text1 Old string to be diffed.
3214 | * @param {string} text2 New string to be diffed.
3215 | * @param {boolean=} optChecklines Optional speedup flag. If present and false,
3216 | * then don't run a line-level diff first to identify the changed areas.
3217 | * Defaults to true, which does a faster, slightly less optimal diff.
3218 | * @return {!Array.} Array of diff tuples.
3219 | */
3220 | DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
3221 | var deadline, checklines, commonlength,
3222 | commonprefix, commonsuffix, diffs;
3223 |
3224 | // The diff must be complete in up to 1 second.
3225 | deadline = ( new Date() ).getTime() + 1000;
3226 |
3227 | // Check for null inputs.
3228 | if ( text1 === null || text2 === null ) {
3229 | throw new Error( "Null input. (DiffMain)" );
3230 | }
3231 |
3232 | // Check for equality (speedup).
3233 | if ( text1 === text2 ) {
3234 | if ( text1 ) {
3235 | return [
3236 | [ DIFF_EQUAL, text1 ]
3237 | ];
3238 | }
3239 | return [];
3240 | }
3241 |
3242 | if ( typeof optChecklines === "undefined" ) {
3243 | optChecklines = true;
3244 | }
3245 |
3246 | checklines = optChecklines;
3247 |
3248 | // Trim off common prefix (speedup).
3249 | commonlength = this.diffCommonPrefix( text1, text2 );
3250 | commonprefix = text1.substring( 0, commonlength );
3251 | text1 = text1.substring( commonlength );
3252 | text2 = text2.substring( commonlength );
3253 |
3254 | // Trim off common suffix (speedup).
3255 | commonlength = this.diffCommonSuffix( text1, text2 );
3256 | commonsuffix = text1.substring( text1.length - commonlength );
3257 | text1 = text1.substring( 0, text1.length - commonlength );
3258 | text2 = text2.substring( 0, text2.length - commonlength );
3259 |
3260 | // Compute the diff on the middle block.
3261 | diffs = this.diffCompute( text1, text2, checklines, deadline );
3262 |
3263 | // Restore the prefix and suffix.
3264 | if ( commonprefix ) {
3265 | diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
3266 | }
3267 | if ( commonsuffix ) {
3268 | diffs.push( [ DIFF_EQUAL, commonsuffix ] );
3269 | }
3270 | this.diffCleanupMerge( diffs );
3271 | return diffs;
3272 | };
3273 |
3274 | /**
3275 | * Reduce the number of edits by eliminating operationally trivial equalities.
3276 | * @param {!Array.} diffs Array of diff tuples.
3277 | */
3278 | DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
3279 | var changes, equalities, equalitiesLength, lastequality,
3280 | pointer, preIns, preDel, postIns, postDel;
3281 | changes = false;
3282 | equalities = []; // Stack of indices where equalities are found.
3283 | equalitiesLength = 0; // Keeping our own length var is faster in JS.
3284 | /** @type {?string} */
3285 | lastequality = null;
3286 |
3287 | // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3288 | pointer = 0; // Index of current position.
3289 |
3290 | // Is there an insertion operation before the last equality.
3291 | preIns = false;
3292 |
3293 | // Is there a deletion operation before the last equality.
3294 | preDel = false;
3295 |
3296 | // Is there an insertion operation after the last equality.
3297 | postIns = false;
3298 |
3299 | // Is there a deletion operation after the last equality.
3300 | postDel = false;
3301 | while ( pointer < diffs.length ) {
3302 |
3303 | // Equality found.
3304 | if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
3305 | if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
3306 |
3307 | // Candidate found.
3308 | equalities[ equalitiesLength++ ] = pointer;
3309 | preIns = postIns;
3310 | preDel = postDel;
3311 | lastequality = diffs[ pointer ][ 1 ];
3312 | } else {
3313 |
3314 | // Not a candidate, and can never become one.
3315 | equalitiesLength = 0;
3316 | lastequality = null;
3317 | }
3318 | postIns = postDel = false;
3319 |
3320 | // An insertion or deletion.
3321 | } else {
3322 |
3323 | if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
3324 | postDel = true;
3325 | } else {
3326 | postIns = true;
3327 | }
3328 |
3329 | /*
3330 | * Five types to be split:
3331 | * ABXYCD
3332 | * AXCD
3333 | * ABXC
3334 | * AXCD
3335 | * ABXC
3336 | */
3337 | if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
3338 | ( ( lastequality.length < 2 ) &&
3339 | ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
3340 |
3341 | // Duplicate record.
3342 | diffs.splice(
3343 | equalities[ equalitiesLength - 1 ],
3344 | 0,
3345 | [ DIFF_DELETE, lastequality ]
3346 | );
3347 |
3348 | // Change second copy to insert.
3349 | diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
3350 | equalitiesLength--; // Throw away the equality we just deleted;
3351 | lastequality = null;
3352 | if ( preIns && preDel ) {
3353 |
3354 | // No changes made which could affect previous entry, keep going.
3355 | postIns = postDel = true;
3356 | equalitiesLength = 0;
3357 | } else {
3358 | equalitiesLength--; // Throw away the previous equality.
3359 | pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3360 | postIns = postDel = false;
3361 | }
3362 | changes = true;
3363 | }
3364 | }
3365 | pointer++;
3366 | }
3367 |
3368 | if ( changes ) {
3369 | this.diffCleanupMerge( diffs );
3370 | }
3371 | };
3372 |
3373 | /**
3374 | * Convert a diff array into a pretty HTML report.
3375 | * @param {!Array.} diffs Array of diff tuples.
3376 | * @param {integer} string to be beautified.
3377 | * @return {string} HTML representation.
3378 | */
3379 | DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
3380 | var op, data, x,
3381 | html = [];
3382 | for ( x = 0; x < diffs.length; x++ ) {
3383 | op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
3384 | data = diffs[ x ][ 1 ]; // Text of change.
3385 | switch ( op ) {
3386 | case DIFF_INSERT:
3387 | html[ x ] = "" + escapeText( data ) + "";
3388 | break;
3389 | case DIFF_DELETE:
3390 | html[ x ] = "" + escapeText( data ) + "";
3391 | break;
3392 | case DIFF_EQUAL:
3393 | html[ x ] = "" + escapeText( data ) + "";
3394 | break;
3395 | }
3396 | }
3397 | return html.join( "" );
3398 | };
3399 |
3400 | /**
3401 | * Determine the common prefix of two strings.
3402 | * @param {string} text1 First string.
3403 | * @param {string} text2 Second string.
3404 | * @return {number} The number of characters common to the start of each
3405 | * string.
3406 | */
3407 | DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
3408 | var pointermid, pointermax, pointermin, pointerstart;
3409 |
3410 | // Quick check for common null cases.
3411 | if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
3412 | return 0;
3413 | }
3414 |
3415 | // Binary search.
3416 | // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3417 | pointermin = 0;
3418 | pointermax = Math.min( text1.length, text2.length );
3419 | pointermid = pointermax;
3420 | pointerstart = 0;
3421 | while ( pointermin < pointermid ) {
3422 | if ( text1.substring( pointerstart, pointermid ) ===
3423 | text2.substring( pointerstart, pointermid ) ) {
3424 | pointermin = pointermid;
3425 | pointerstart = pointermin;
3426 | } else {
3427 | pointermax = pointermid;
3428 | }
3429 | pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
3430 | }
3431 | return pointermid;
3432 | };
3433 |
3434 | /**
3435 | * Determine the common suffix of two strings.
3436 | * @param {string} text1 First string.
3437 | * @param {string} text2 Second string.
3438 | * @return {number} The number of characters common to the end of each string.
3439 | */
3440 | DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
3441 | var pointermid, pointermax, pointermin, pointerend;
3442 |
3443 | // Quick check for common null cases.
3444 | if ( !text1 ||
3445 | !text2 ||
3446 | text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
3447 | return 0;
3448 | }
3449 |
3450 | // Binary search.
3451 | // Performance analysis: https://neil.fraser.name/news/2007/10/09/
3452 | pointermin = 0;
3453 | pointermax = Math.min( text1.length, text2.length );
3454 | pointermid = pointermax;
3455 | pointerend = 0;
3456 | while ( pointermin < pointermid ) {
3457 | if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
3458 | text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
3459 | pointermin = pointermid;
3460 | pointerend = pointermin;
3461 | } else {
3462 | pointermax = pointermid;
3463 | }
3464 | pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
3465 | }
3466 | return pointermid;
3467 | };
3468 |
3469 | /**
3470 | * Find the differences between two texts. Assumes that the texts do not
3471 | * have any common prefix or suffix.
3472 | * @param {string} text1 Old string to be diffed.
3473 | * @param {string} text2 New string to be diffed.
3474 | * @param {boolean} checklines Speedup flag. If false, then don't run a
3475 | * line-level diff first to identify the changed areas.
3476 | * If true, then run a faster, slightly less optimal diff.
3477 | * @param {number} deadline Time when the diff should be complete by.
3478 | * @return {!Array.} Array of diff tuples.
3479 | * @private
3480 | */
3481 | DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
3482 | var diffs, longtext, shorttext, i, hm,
3483 | text1A, text2A, text1B, text2B,
3484 | midCommon, diffsA, diffsB;
3485 |
3486 | if ( !text1 ) {
3487 |
3488 | // Just add some text (speedup).
3489 | return [
3490 | [ DIFF_INSERT, text2 ]
3491 | ];
3492 | }
3493 |
3494 | if ( !text2 ) {
3495 |
3496 | // Just delete some text (speedup).
3497 | return [
3498 | [ DIFF_DELETE, text1 ]
3499 | ];
3500 | }
3501 |
3502 | longtext = text1.length > text2.length ? text1 : text2;
3503 | shorttext = text1.length > text2.length ? text2 : text1;
3504 | i = longtext.indexOf( shorttext );
3505 | if ( i !== -1 ) {
3506 |
3507 | // Shorter text is inside the longer text (speedup).
3508 | diffs = [
3509 | [ DIFF_INSERT, longtext.substring( 0, i ) ],
3510 | [ DIFF_EQUAL, shorttext ],
3511 | [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
3512 | ];
3513 |
3514 | // Swap insertions for deletions if diff is reversed.
3515 | if ( text1.length > text2.length ) {
3516 | diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
3517 | }
3518 | return diffs;
3519 | }
3520 |
3521 | if ( shorttext.length === 1 ) {
3522 |
3523 | // Single character string.
3524 | // After the previous speedup, the character can't be an equality.
3525 | return [
3526 | [ DIFF_DELETE, text1 ],
3527 | [ DIFF_INSERT, text2 ]
3528 | ];
3529 | }
3530 |
3531 | // Check to see if the problem can be split in two.
3532 | hm = this.diffHalfMatch( text1, text2 );
3533 | if ( hm ) {
3534 |
3535 | // A half-match was found, sort out the return data.
3536 | text1A = hm[ 0 ];
3537 | text1B = hm[ 1 ];
3538 | text2A = hm[ 2 ];
3539 | text2B = hm[ 3 ];
3540 | midCommon = hm[ 4 ];
3541 |
3542 | // Send both pairs off for separate processing.
3543 | diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
3544 | diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
3545 |
3546 | // Merge the results.
3547 | return diffsA.concat( [
3548 | [ DIFF_EQUAL, midCommon ]
3549 | ], diffsB );
3550 | }
3551 |
3552 | if ( checklines && text1.length > 100 && text2.length > 100 ) {
3553 | return this.diffLineMode( text1, text2, deadline );
3554 | }
3555 |
3556 | return this.diffBisect( text1, text2, deadline );
3557 | };
3558 |
3559 | /**
3560 | * Do the two texts share a substring which is at least half the length of the
3561 | * longer text?
3562 | * This speedup can produce non-minimal diffs.
3563 | * @param {string} text1 First string.
3564 | * @param {string} text2 Second string.
3565 | * @return {Array.} Five element Array, containing the prefix of
3566 | * text1, the suffix of text1, the prefix of text2, the suffix of
3567 | * text2 and the common middle. Or null if there was no match.
3568 | * @private
3569 | */
3570 | DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
3571 | var longtext, shorttext, dmp,
3572 | text1A, text2B, text2A, text1B, midCommon,
3573 | hm1, hm2, hm;
3574 |
3575 | longtext = text1.length > text2.length ? text1 : text2;
3576 | shorttext = text1.length > text2.length ? text2 : text1;
3577 | if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
3578 | return null; // Pointless.
3579 | }
3580 | dmp = this; // 'this' becomes 'window' in a closure.
3581 |
3582 | /**
3583 | * Does a substring of shorttext exist within longtext such that the substring
3584 | * is at least half the length of longtext?
3585 | * Closure, but does not reference any external variables.
3586 | * @param {string} longtext Longer string.
3587 | * @param {string} shorttext Shorter string.
3588 | * @param {number} i Start index of quarter length substring within longtext.
3589 | * @return {Array.} Five element Array, containing the prefix of
3590 | * longtext, the suffix of longtext, the prefix of shorttext, the suffix
3591 | * of shorttext and the common middle. Or null if there was no match.
3592 | * @private
3593 | */
3594 | function diffHalfMatchI( longtext, shorttext, i ) {
3595 | var seed, j, bestCommon, prefixLength, suffixLength,
3596 | bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
3597 |
3598 | // Start with a 1/4 length substring at position i as a seed.
3599 | seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
3600 | j = -1;
3601 | bestCommon = "";
3602 | while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
3603 | prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
3604 | shorttext.substring( j ) );
3605 | suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
3606 | shorttext.substring( 0, j ) );
3607 | if ( bestCommon.length < suffixLength + prefixLength ) {
3608 | bestCommon = shorttext.substring( j - suffixLength, j ) +
3609 | shorttext.substring( j, j + prefixLength );
3610 | bestLongtextA = longtext.substring( 0, i - suffixLength );
3611 | bestLongtextB = longtext.substring( i + prefixLength );
3612 | bestShorttextA = shorttext.substring( 0, j - suffixLength );
3613 | bestShorttextB = shorttext.substring( j + prefixLength );
3614 | }
3615 | }
3616 | if ( bestCommon.length * 2 >= longtext.length ) {
3617 | return [ bestLongtextA, bestLongtextB,
3618 | bestShorttextA, bestShorttextB, bestCommon
3619 | ];
3620 | } else {
3621 | return null;
3622 | }
3623 | }
3624 |
3625 | // First check if the second quarter is the seed for a half-match.
3626 | hm1 = diffHalfMatchI( longtext, shorttext,
3627 | Math.ceil( longtext.length / 4 ) );
3628 |
3629 | // Check again based on the third quarter.
3630 | hm2 = diffHalfMatchI( longtext, shorttext,
3631 | Math.ceil( longtext.length / 2 ) );
3632 | if ( !hm1 && !hm2 ) {
3633 | return null;
3634 | } else if ( !hm2 ) {
3635 | hm = hm1;
3636 | } else if ( !hm1 ) {
3637 | hm = hm2;
3638 | } else {
3639 |
3640 | // Both matched. Select the longest.
3641 | hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
3642 | }
3643 |
3644 | // A half-match was found, sort out the return data.
3645 | text1A, text1B, text2A, text2B;
3646 | if ( text1.length > text2.length ) {
3647 | text1A = hm[ 0 ];
3648 | text1B = hm[ 1 ];
3649 | text2A = hm[ 2 ];
3650 | text2B = hm[ 3 ];
3651 | } else {
3652 | text2A = hm[ 0 ];
3653 | text2B = hm[ 1 ];
3654 | text1A = hm[ 2 ];
3655 | text1B = hm[ 3 ];
3656 | }
3657 | midCommon = hm[ 4 ];
3658 | return [ text1A, text1B, text2A, text2B, midCommon ];
3659 | };
3660 |
3661 | /**
3662 | * Do a quick line-level diff on both strings, then rediff the parts for
3663 | * greater accuracy.
3664 | * This speedup can produce non-minimal diffs.
3665 | * @param {string} text1 Old string to be diffed.
3666 | * @param {string} text2 New string to be diffed.
3667 | * @param {number} deadline Time when the diff should be complete by.
3668 | * @return {!Array.} Array of diff tuples.
3669 | * @private
3670 | */
3671 | DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
3672 | var a, diffs, linearray, pointer, countInsert,
3673 | countDelete, textInsert, textDelete, j;
3674 |
3675 | // Scan the text on a line-by-line basis first.
3676 | a = this.diffLinesToChars( text1, text2 );
3677 | text1 = a.chars1;
3678 | text2 = a.chars2;
3679 | linearray = a.lineArray;
3680 |
3681 | diffs = this.DiffMain( text1, text2, false, deadline );
3682 |
3683 | // Convert the diff back to original text.
3684 | this.diffCharsToLines( diffs, linearray );
3685 |
3686 | // Eliminate freak matches (e.g. blank lines)
3687 | this.diffCleanupSemantic( diffs );
3688 |
3689 | // Rediff any replacement blocks, this time character-by-character.
3690 | // Add a dummy entry at the end.
3691 | diffs.push( [ DIFF_EQUAL, "" ] );
3692 | pointer = 0;
3693 | countDelete = 0;
3694 | countInsert = 0;
3695 | textDelete = "";
3696 | textInsert = "";
3697 | while ( pointer < diffs.length ) {
3698 | switch ( diffs[ pointer ][ 0 ] ) {
3699 | case DIFF_INSERT:
3700 | countInsert++;
3701 | textInsert += diffs[ pointer ][ 1 ];
3702 | break;
3703 | case DIFF_DELETE:
3704 | countDelete++;
3705 | textDelete += diffs[ pointer ][ 1 ];
3706 | break;
3707 | case DIFF_EQUAL:
3708 |
3709 | // Upon reaching an equality, check for prior redundancies.
3710 | if ( countDelete >= 1 && countInsert >= 1 ) {
3711 |
3712 | // Delete the offending records and add the merged ones.
3713 | diffs.splice( pointer - countDelete - countInsert,
3714 | countDelete + countInsert );
3715 | pointer = pointer - countDelete - countInsert;
3716 | a = this.DiffMain( textDelete, textInsert, false, deadline );
3717 | for ( j = a.length - 1; j >= 0; j-- ) {
3718 | diffs.splice( pointer, 0, a[ j ] );
3719 | }
3720 | pointer = pointer + a.length;
3721 | }
3722 | countInsert = 0;
3723 | countDelete = 0;
3724 | textDelete = "";
3725 | textInsert = "";
3726 | break;
3727 | }
3728 | pointer++;
3729 | }
3730 | diffs.pop(); // Remove the dummy entry at the end.
3731 |
3732 | return diffs;
3733 | };
3734 |
3735 | /**
3736 | * Find the 'middle snake' of a diff, split the problem in two
3737 | * and return the recursively constructed diff.
3738 | * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
3739 | * @param {string} text1 Old string to be diffed.
3740 | * @param {string} text2 New string to be diffed.
3741 | * @param {number} deadline Time at which to bail if not yet complete.
3742 | * @return {!Array.} Array of diff tuples.
3743 | * @private
3744 | */
3745 | DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
3746 | var text1Length, text2Length, maxD, vOffset, vLength,
3747 | v1, v2, x, delta, front, k1start, k1end, k2start,
3748 | k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
3749 |
3750 | // Cache the text lengths to prevent multiple calls.
3751 | text1Length = text1.length;
3752 | text2Length = text2.length;
3753 | maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
3754 | vOffset = maxD;
3755 | vLength = 2 * maxD;
3756 | v1 = new Array( vLength );
3757 | v2 = new Array( vLength );
3758 |
3759 | // Setting all elements to -1 is faster in Chrome & Firefox than mixing
3760 | // integers and undefined.
3761 | for ( x = 0; x < vLength; x++ ) {
3762 | v1[ x ] = -1;
3763 | v2[ x ] = -1;
3764 | }
3765 | v1[ vOffset + 1 ] = 0;
3766 | v2[ vOffset + 1 ] = 0;
3767 | delta = text1Length - text2Length;
3768 |
3769 | // If the total number of characters is odd, then the front path will collide
3770 | // with the reverse path.
3771 | front = ( delta % 2 !== 0 );
3772 |
3773 | // Offsets for start and end of k loop.
3774 | // Prevents mapping of space beyond the grid.
3775 | k1start = 0;
3776 | k1end = 0;
3777 | k2start = 0;
3778 | k2end = 0;
3779 | for ( d = 0; d < maxD; d++ ) {
3780 |
3781 | // Bail out if deadline is reached.
3782 | if ( ( new Date() ).getTime() > deadline ) {
3783 | break;
3784 | }
3785 |
3786 | // Walk the front path one step.
3787 | for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
3788 | k1Offset = vOffset + k1;
3789 | if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
3790 | x1 = v1[ k1Offset + 1 ];
3791 | } else {
3792 | x1 = v1[ k1Offset - 1 ] + 1;
3793 | }
3794 | y1 = x1 - k1;
3795 | while ( x1 < text1Length && y1 < text2Length &&
3796 | text1.charAt( x1 ) === text2.charAt( y1 ) ) {
3797 | x1++;
3798 | y1++;
3799 | }
3800 | v1[ k1Offset ] = x1;
3801 | if ( x1 > text1Length ) {
3802 |
3803 | // Ran off the right of the graph.
3804 | k1end += 2;
3805 | } else if ( y1 > text2Length ) {
3806 |
3807 | // Ran off the bottom of the graph.
3808 | k1start += 2;
3809 | } else if ( front ) {
3810 | k2Offset = vOffset + delta - k1;
3811 | if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
3812 |
3813 | // Mirror x2 onto top-left coordinate system.
3814 | x2 = text1Length - v2[ k2Offset ];
3815 | if ( x1 >= x2 ) {
3816 |
3817 | // Overlap detected.
3818 | return this.diffBisectSplit( text1, text2, x1, y1, deadline );
3819 | }
3820 | }
3821 | }
3822 | }
3823 |
3824 | // Walk the reverse path one step.
3825 | for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
3826 | k2Offset = vOffset + k2;
3827 | if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
3828 | x2 = v2[ k2Offset + 1 ];
3829 | } else {
3830 | x2 = v2[ k2Offset - 1 ] + 1;
3831 | }
3832 | y2 = x2 - k2;
3833 | while ( x2 < text1Length && y2 < text2Length &&
3834 | text1.charAt( text1Length - x2 - 1 ) ===
3835 | text2.charAt( text2Length - y2 - 1 ) ) {
3836 | x2++;
3837 | y2++;
3838 | }
3839 | v2[ k2Offset ] = x2;
3840 | if ( x2 > text1Length ) {
3841 |
3842 | // Ran off the left of the graph.
3843 | k2end += 2;
3844 | } else if ( y2 > text2Length ) {
3845 |
3846 | // Ran off the top of the graph.
3847 | k2start += 2;
3848 | } else if ( !front ) {
3849 | k1Offset = vOffset + delta - k2;
3850 | if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
3851 | x1 = v1[ k1Offset ];
3852 | y1 = vOffset + x1 - k1Offset;
3853 |
3854 | // Mirror x2 onto top-left coordinate system.
3855 | x2 = text1Length - x2;
3856 | if ( x1 >= x2 ) {
3857 |
3858 | // Overlap detected.
3859 | return this.diffBisectSplit( text1, text2, x1, y1, deadline );
3860 | }
3861 | }
3862 | }
3863 | }
3864 | }
3865 |
3866 | // Diff took too long and hit the deadline or
3867 | // number of diffs equals number of characters, no commonality at all.
3868 | return [
3869 | [ DIFF_DELETE, text1 ],
3870 | [ DIFF_INSERT, text2 ]
3871 | ];
3872 | };
3873 |
3874 | /**
3875 | * Given the location of the 'middle snake', split the diff in two parts
3876 | * and recurse.
3877 | * @param {string} text1 Old string to be diffed.
3878 | * @param {string} text2 New string to be diffed.
3879 | * @param {number} x Index of split point in text1.
3880 | * @param {number} y Index of split point in text2.
3881 | * @param {number} deadline Time at which to bail if not yet complete.
3882 | * @return {!Array.} Array of diff tuples.
3883 | * @private
3884 | */
3885 | DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
3886 | var text1a, text1b, text2a, text2b, diffs, diffsb;
3887 | text1a = text1.substring( 0, x );
3888 | text2a = text2.substring( 0, y );
3889 | text1b = text1.substring( x );
3890 | text2b = text2.substring( y );
3891 |
3892 | // Compute both diffs serially.
3893 | diffs = this.DiffMain( text1a, text2a, false, deadline );
3894 | diffsb = this.DiffMain( text1b, text2b, false, deadline );
3895 |
3896 | return diffs.concat( diffsb );
3897 | };
3898 |
3899 | /**
3900 | * Reduce the number of edits by eliminating semantically trivial equalities.
3901 | * @param {!Array.} diffs Array of diff tuples.
3902 | */
3903 | DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
3904 | var changes, equalities, equalitiesLength, lastequality,
3905 | pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
3906 | lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
3907 | changes = false;
3908 | equalities = []; // Stack of indices where equalities are found.
3909 | equalitiesLength = 0; // Keeping our own length var is faster in JS.
3910 | /** @type {?string} */
3911 | lastequality = null;
3912 |
3913 | // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3914 | pointer = 0; // Index of current position.
3915 |
3916 | // Number of characters that changed prior to the equality.
3917 | lengthInsertions1 = 0;
3918 | lengthDeletions1 = 0;
3919 |
3920 | // Number of characters that changed after the equality.
3921 | lengthInsertions2 = 0;
3922 | lengthDeletions2 = 0;
3923 | while ( pointer < diffs.length ) {
3924 | if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
3925 | equalities[ equalitiesLength++ ] = pointer;
3926 | lengthInsertions1 = lengthInsertions2;
3927 | lengthDeletions1 = lengthDeletions2;
3928 | lengthInsertions2 = 0;
3929 | lengthDeletions2 = 0;
3930 | lastequality = diffs[ pointer ][ 1 ];
3931 | } else { // An insertion or deletion.
3932 | if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3933 | lengthInsertions2 += diffs[ pointer ][ 1 ].length;
3934 | } else {
3935 | lengthDeletions2 += diffs[ pointer ][ 1 ].length;
3936 | }
3937 |
3938 | // Eliminate an equality that is smaller or equal to the edits on both
3939 | // sides of it.
3940 | if ( lastequality && ( lastequality.length <=
3941 | Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
3942 | ( lastequality.length <= Math.max( lengthInsertions2,
3943 | lengthDeletions2 ) ) ) {
3944 |
3945 | // Duplicate record.
3946 | diffs.splice(
3947 | equalities[ equalitiesLength - 1 ],
3948 | 0,
3949 | [ DIFF_DELETE, lastequality ]
3950 | );
3951 |
3952 | // Change second copy to insert.
3953 | diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
3954 |
3955 | // Throw away the equality we just deleted.
3956 | equalitiesLength--;
3957 |
3958 | // Throw away the previous equality (it needs to be reevaluated).
3959 | equalitiesLength--;
3960 | pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3961 |
3962 | // Reset the counters.
3963 | lengthInsertions1 = 0;
3964 | lengthDeletions1 = 0;
3965 | lengthInsertions2 = 0;
3966 | lengthDeletions2 = 0;
3967 | lastequality = null;
3968 | changes = true;
3969 | }
3970 | }
3971 | pointer++;
3972 | }
3973 |
3974 | // Normalize the diff.
3975 | if ( changes ) {
3976 | this.diffCleanupMerge( diffs );
3977 | }
3978 |
3979 | // Find any overlaps between deletions and insertions.
3980 | // e.g: abcxxxxxxdef
3981 | // -> abcxxxdef
3982 | // e.g: xxxabcdefxxx
3983 | // -> defxxxabc
3984 | // Only extract an overlap if it is as big as the edit ahead or behind it.
3985 | pointer = 1;
3986 | while ( pointer < diffs.length ) {
3987 | if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
3988 | diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3989 | deletion = diffs[ pointer - 1 ][ 1 ];
3990 | insertion = diffs[ pointer ][ 1 ];
3991 | overlapLength1 = this.diffCommonOverlap( deletion, insertion );
3992 | overlapLength2 = this.diffCommonOverlap( insertion, deletion );
3993 | if ( overlapLength1 >= overlapLength2 ) {
3994 | if ( overlapLength1 >= deletion.length / 2 ||
3995 | overlapLength1 >= insertion.length / 2 ) {
3996 |
3997 | // Overlap found. Insert an equality and trim the surrounding edits.
3998 | diffs.splice(
3999 | pointer,
4000 | 0,
4001 | [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
4002 | );
4003 | diffs[ pointer - 1 ][ 1 ] =
4004 | deletion.substring( 0, deletion.length - overlapLength1 );
4005 | diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
4006 | pointer++;
4007 | }
4008 | } else {
4009 | if ( overlapLength2 >= deletion.length / 2 ||
4010 | overlapLength2 >= insertion.length / 2 ) {
4011 |
4012 | // Reverse overlap found.
4013 | // Insert an equality and swap and trim the surrounding edits.
4014 | diffs.splice(
4015 | pointer,
4016 | 0,
4017 | [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
4018 | );
4019 |
4020 | diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
4021 | diffs[ pointer - 1 ][ 1 ] =
4022 | insertion.substring( 0, insertion.length - overlapLength2 );
4023 | diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
4024 | diffs[ pointer + 1 ][ 1 ] =
4025 | deletion.substring( overlapLength2 );
4026 | pointer++;
4027 | }
4028 | }
4029 | pointer++;
4030 | }
4031 | pointer++;
4032 | }
4033 | };
4034 |
4035 | /**
4036 | * Determine if the suffix of one string is the prefix of another.
4037 | * @param {string} text1 First string.
4038 | * @param {string} text2 Second string.
4039 | * @return {number} The number of characters common to the end of the first
4040 | * string and the start of the second string.
4041 | * @private
4042 | */
4043 | DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
4044 | var text1Length, text2Length, textLength,
4045 | best, length, pattern, found;
4046 |
4047 | // Cache the text lengths to prevent multiple calls.
4048 | text1Length = text1.length;
4049 | text2Length = text2.length;
4050 |
4051 | // Eliminate the null case.
4052 | if ( text1Length === 0 || text2Length === 0 ) {
4053 | return 0;
4054 | }
4055 |
4056 | // Truncate the longer string.
4057 | if ( text1Length > text2Length ) {
4058 | text1 = text1.substring( text1Length - text2Length );
4059 | } else if ( text1Length < text2Length ) {
4060 | text2 = text2.substring( 0, text1Length );
4061 | }
4062 | textLength = Math.min( text1Length, text2Length );
4063 |
4064 | // Quick check for the worst case.
4065 | if ( text1 === text2 ) {
4066 | return textLength;
4067 | }
4068 |
4069 | // Start by looking for a single character match
4070 | // and increase length until no match is found.
4071 | // Performance analysis: https://neil.fraser.name/news/2010/11/04/
4072 | best = 0;
4073 | length = 1;
4074 | while ( true ) {
4075 | pattern = text1.substring( textLength - length );
4076 | found = text2.indexOf( pattern );
4077 | if ( found === -1 ) {
4078 | return best;
4079 | }
4080 | length += found;
4081 | if ( found === 0 || text1.substring( textLength - length ) ===
4082 | text2.substring( 0, length ) ) {
4083 | best = length;
4084 | length++;
4085 | }
4086 | }
4087 | };
4088 |
4089 | /**
4090 | * Split two texts into an array of strings. Reduce the texts to a string of
4091 | * hashes where each Unicode character represents one line.
4092 | * @param {string} text1 First string.
4093 | * @param {string} text2 Second string.
4094 | * @return {{chars1: string, chars2: string, lineArray: !Array.}}
4095 | * An object containing the encoded text1, the encoded text2 and
4096 | * the array of unique strings.
4097 | * The zeroth element of the array of unique strings is intentionally blank.
4098 | * @private
4099 | */
4100 | DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
4101 | var lineArray, lineHash, chars1, chars2;
4102 | lineArray = []; // E.g. lineArray[4] === 'Hello\n'
4103 | lineHash = {}; // E.g. lineHash['Hello\n'] === 4
4104 |
4105 | // '\x00' is a valid character, but various debuggers don't like it.
4106 | // So we'll insert a junk entry to avoid generating a null character.
4107 | lineArray[ 0 ] = "";
4108 |
4109 | /**
4110 | * Split a text into an array of strings. Reduce the texts to a string of
4111 | * hashes where each Unicode character represents one line.
4112 | * Modifies linearray and linehash through being a closure.
4113 | * @param {string} text String to encode.
4114 | * @return {string} Encoded string.
4115 | * @private
4116 | */
4117 | function diffLinesToCharsMunge( text ) {
4118 | var chars, lineStart, lineEnd, lineArrayLength, line;
4119 | chars = "";
4120 |
4121 | // Walk the text, pulling out a substring for each line.
4122 | // text.split('\n') would would temporarily double our memory footprint.
4123 | // Modifying text would create many large strings to garbage collect.
4124 | lineStart = 0;
4125 | lineEnd = -1;
4126 |
4127 | // Keeping our own length variable is faster than looking it up.
4128 | lineArrayLength = lineArray.length;
4129 | while ( lineEnd < text.length - 1 ) {
4130 | lineEnd = text.indexOf( "\n", lineStart );
4131 | if ( lineEnd === -1 ) {
4132 | lineEnd = text.length - 1;
4133 | }
4134 | line = text.substring( lineStart, lineEnd + 1 );
4135 | lineStart = lineEnd + 1;
4136 |
4137 | if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
4138 | ( lineHash[ line ] !== undefined ) ) {
4139 | chars += String.fromCharCode( lineHash[ line ] );
4140 | } else {
4141 | chars += String.fromCharCode( lineArrayLength );
4142 | lineHash[ line ] = lineArrayLength;
4143 | lineArray[ lineArrayLength++ ] = line;
4144 | }
4145 | }
4146 | return chars;
4147 | }
4148 |
4149 | chars1 = diffLinesToCharsMunge( text1 );
4150 | chars2 = diffLinesToCharsMunge( text2 );
4151 | return {
4152 | chars1: chars1,
4153 | chars2: chars2,
4154 | lineArray: lineArray
4155 | };
4156 | };
4157 |
4158 | /**
4159 | * Rehydrate the text in a diff from a string of line hashes to real lines of
4160 | * text.
4161 | * @param {!Array.} diffs Array of diff tuples.
4162 | * @param {!Array.} lineArray Array of unique strings.
4163 | * @private
4164 | */
4165 | DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
4166 | var x, chars, text, y;
4167 | for ( x = 0; x < diffs.length; x++ ) {
4168 | chars = diffs[ x ][ 1 ];
4169 | text = [];
4170 | for ( y = 0; y < chars.length; y++ ) {
4171 | text[ y ] = lineArray[ chars.charCodeAt( y ) ];
4172 | }
4173 | diffs[ x ][ 1 ] = text.join( "" );
4174 | }
4175 | };
4176 |
4177 | /**
4178 | * Reorder and merge like edit sections. Merge equalities.
4179 | * Any edit section can move as long as it doesn't cross an equality.
4180 | * @param {!Array.} diffs Array of diff tuples.
4181 | */
4182 | DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
4183 | var pointer, countDelete, countInsert, textInsert, textDelete,
4184 | commonlength, changes, diffPointer, position;
4185 | diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
4186 | pointer = 0;
4187 | countDelete = 0;
4188 | countInsert = 0;
4189 | textDelete = "";
4190 | textInsert = "";
4191 | commonlength;
4192 | while ( pointer < diffs.length ) {
4193 | switch ( diffs[ pointer ][ 0 ] ) {
4194 | case DIFF_INSERT:
4195 | countInsert++;
4196 | textInsert += diffs[ pointer ][ 1 ];
4197 | pointer++;
4198 | break;
4199 | case DIFF_DELETE:
4200 | countDelete++;
4201 | textDelete += diffs[ pointer ][ 1 ];
4202 | pointer++;
4203 | break;
4204 | case DIFF_EQUAL:
4205 |
4206 | // Upon reaching an equality, check for prior redundancies.
4207 | if ( countDelete + countInsert > 1 ) {
4208 | if ( countDelete !== 0 && countInsert !== 0 ) {
4209 |
4210 | // Factor out any common prefixes.
4211 | commonlength = this.diffCommonPrefix( textInsert, textDelete );
4212 | if ( commonlength !== 0 ) {
4213 | if ( ( pointer - countDelete - countInsert ) > 0 &&
4214 | diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
4215 | DIFF_EQUAL ) {
4216 | diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
4217 | textInsert.substring( 0, commonlength );
4218 | } else {
4219 | diffs.splice( 0, 0, [ DIFF_EQUAL,
4220 | textInsert.substring( 0, commonlength )
4221 | ] );
4222 | pointer++;
4223 | }
4224 | textInsert = textInsert.substring( commonlength );
4225 | textDelete = textDelete.substring( commonlength );
4226 | }
4227 |
4228 | // Factor out any common suffixies.
4229 | commonlength = this.diffCommonSuffix( textInsert, textDelete );
4230 | if ( commonlength !== 0 ) {
4231 | diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
4232 | commonlength ) + diffs[ pointer ][ 1 ];
4233 | textInsert = textInsert.substring( 0, textInsert.length -
4234 | commonlength );
4235 | textDelete = textDelete.substring( 0, textDelete.length -
4236 | commonlength );
4237 | }
4238 | }
4239 |
4240 | // Delete the offending records and add the merged ones.
4241 | if ( countDelete === 0 ) {
4242 | diffs.splice( pointer - countInsert,
4243 | countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
4244 | } else if ( countInsert === 0 ) {
4245 | diffs.splice( pointer - countDelete,
4246 | countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
4247 | } else {
4248 | diffs.splice(
4249 | pointer - countDelete - countInsert,
4250 | countDelete + countInsert,
4251 | [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
4252 | );
4253 | }
4254 | pointer = pointer - countDelete - countInsert +
4255 | ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
4256 | } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
4257 |
4258 | // Merge this equality with the previous one.
4259 | diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
4260 | diffs.splice( pointer, 1 );
4261 | } else {
4262 | pointer++;
4263 | }
4264 | countInsert = 0;
4265 | countDelete = 0;
4266 | textDelete = "";
4267 | textInsert = "";
4268 | break;
4269 | }
4270 | }
4271 | if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
4272 | diffs.pop(); // Remove the dummy entry at the end.
4273 | }
4274 |
4275 | // Second pass: look for single edits surrounded on both sides by equalities
4276 | // which can be shifted sideways to eliminate an equality.
4277 | // e.g: ABAC -> ABAC
4278 | changes = false;
4279 | pointer = 1;
4280 |
4281 | // Intentionally ignore the first and last element (don't need checking).
4282 | while ( pointer < diffs.length - 1 ) {
4283 | if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
4284 | diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
4285 |
4286 | diffPointer = diffs[ pointer ][ 1 ];
4287 | position = diffPointer.substring(
4288 | diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
4289 | );
4290 |
4291 | // This is a single edit surrounded by equalities.
4292 | if ( position === diffs[ pointer - 1 ][ 1 ] ) {
4293 |
4294 | // Shift the edit over the previous equality.
4295 | diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
4296 | diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
4297 | diffs[ pointer - 1 ][ 1 ].length );
4298 | diffs[ pointer + 1 ][ 1 ] =
4299 | diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
4300 | diffs.splice( pointer - 1, 1 );
4301 | changes = true;
4302 | } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
4303 | diffs[ pointer + 1 ][ 1 ] ) {
4304 |
4305 | // Shift the edit over the next equality.
4306 | diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
4307 | diffs[ pointer ][ 1 ] =
4308 | diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
4309 | diffs[ pointer + 1 ][ 1 ];
4310 | diffs.splice( pointer + 1, 1 );
4311 | changes = true;
4312 | }
4313 | }
4314 | pointer++;
4315 | }
4316 |
4317 | // If shifts were made, the diff needs reordering and another shift sweep.
4318 | if ( changes ) {
4319 | this.diffCleanupMerge( diffs );
4320 | }
4321 | };
4322 |
4323 | return function( o, n ) {
4324 | var diff, output, text;
4325 | diff = new DiffMatchPatch();
4326 | output = diff.DiffMain( o, n );
4327 | diff.diffCleanupEfficiency( output );
4328 | text = diff.diffPrettyHtml( output );
4329 |
4330 | return text;
4331 | };
4332 | }() );
4333 |
4334 | }() );
4335 |
--------------------------------------------------------------------------------
/unit_test/test/testData.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var Test = {
3 | add: function (a, b) {
4 | return a + b;
5 | },
6 |
7 | al: function () {
8 | alert('hello');
9 | return true;
10 | },
11 |
12 | compareValue: function(a, b) {
13 | for (var key in a) {
14 | if (a[key] !== b[key]) {
15 | return;
16 | }
17 | }
18 | return true;
19 | },
20 |
21 | obj: {
22 | a: 1,
23 | b: 2,
24 | c: 3
25 | },
26 |
27 | arr: [1, 2, 3]
28 |
29 | };
30 |
31 | window.Test = Test;
32 | })();
--------------------------------------------------------------------------------
/unit_test/test/van.each.test.js:
--------------------------------------------------------------------------------
1 | module('Van.each');
2 | test('Object遍历', function () {
3 | var obj = {
4 | name: 'jiavan',
5 | age: 20
6 | },
7 | res = {
8 | name: 'jiavan1',
9 | age: 21
10 | };
11 |
12 | Van.each(obj, function (key, value) {
13 | obj[key] = value + 1;
14 | });
15 | ok(Test.compareValue(obj, res), 'object passing');
16 | });
17 |
18 | test('Array遍历', function () {
19 | var dest = [1, 2, 3],
20 | src = [2, 3, 4];
21 | Van.each(src, function (key, value) {
22 | src[key] = --value;
23 | });
24 | ok(Test.compareValue(src, dest), 'array passing');
25 | });
--------------------------------------------------------------------------------
/unit_test/test/van.extend.test.js:
--------------------------------------------------------------------------------
1 | module('Van.extend');
2 | asyncTest('Van.extend直接挂载', function () {
3 | Van.extend({
4 | add: Test.add,
5 | al: Test.al
6 | });
7 |
8 | var src = {uid: "0x0001"},
9 | result = Van.extend({}, {name: "Tom", age: 21},
10 | {name: "Jerry", sex: "Boy"}),
11 | result2 = Van.extend(src, {name: "Tom", age: 21},
12 | {name: "Jerry", sex: "Boy"});
13 |
14 | equal(Van.add(1, 2), 3, '1+2等于3 passing');
15 | equal(Test.compareValue(result, {name:"Jerry",age:21,sex:"Boy"}),
16 | true, "将两个对象合并为一个对象,不更改元对象 passing");
17 | equal(Test.compareValue(result2, {uid: "0x0001",name:"Jerry",age:21,sex:"Boy"}),
18 | true, "将两个对象合并为一个对象,在原对象基础上扩展 passing");
19 | start();
20 | });
21 |
22 | module('Van.fn.extend');
23 | test('Van.prototype.extend原型挂载', function () {
24 | Van.prototype.extend({
25 | add: Test.add,
26 | al: Test.al
27 | });
28 | var src = {uid: "0x0001"},
29 | result = Van.fn.extend({}, {name: "Tom", age: 21},
30 | {name: "Jerry", sex: "Boy"}),
31 | result2 = Van.fn.extend(src, {name: "Tom", age: 21},
32 | {name: "Jerry", sex: "Boy"});
33 |
34 | equal(Van.add(1, 2), 3, '1+2等于3 passing');
35 | equal(Test.compareValue(result, {name:"Jerry",age:21,sex:"Boy"}),
36 | true, "将两个对象合并为一个对象,不更改元对象 passing");
37 | equal(Test.compareValue(result2, {uid: "0x0001",name:"Jerry",age:21,sex:"Boy"}),
38 | true, "将两个对象合并为一个对象,在原对象基础上扩展 passing");
39 | });
40 |
--------------------------------------------------------------------------------
/unit_test/test/van.map.test.js:
--------------------------------------------------------------------------------
1 | module('Van.map');
2 | test('object', function () {
3 | var src = Test.obj;
4 | var dest = {
5 | a: 2,
6 | b: 4,
7 | c: 6
8 | };
9 | src = Van.map(function(item) {
10 | return item * 2;
11 | });
12 |
13 | ok(Test.compareValue(src, dest), 'object passing');
14 | });
15 |
16 | test('array', function () {
17 | var src = Test.arr;
18 | var dest = [2, 4, 6];
19 | src = Van.map(function(item) {
20 | return item * 2;
21 | });
22 |
23 | ok(Test.compareValue(src, dest), 'array passing');
24 | });
--------------------------------------------------------------------------------
/unit_test/test/van.test.js:
--------------------------------------------------------------------------------
1 | var Test = {
2 | add: function (a, b) {
3 | return a + b;
4 | },
5 |
6 | al: function () {
7 | alert('hello');
8 | return true;
9 | }
10 |
11 | };
12 |
13 | module('扩展测试');
14 | asyncTest('Van.extend直接挂载', function () {
15 | Van.extend({
16 | add: Test.add,
17 | al: Test.al
18 | });
19 |
20 | equal(Van.add(1, 2), 3, '1+2等于3');
21 | //equal(Van.al(), true, 'alert hello');
22 | // 注意异步测试时调用start以继续测试
23 | start();
24 | });
25 |
26 | test('Van.prototype.extend原型挂载', function () {
27 | Van.prototype.extend({
28 | add: Test.add,
29 | al: Test.al
30 | });
31 | equal(Van.prototype.add(1, 2), 3, '1+2等于3');
32 | //equal(Van.prototype.al(), true, 'alert hello');
33 | });
34 |
35 | module('attribute相关测试');
36 | test('获得attribute', function () {
37 | document.getElementById('qunit-fixture').setAttribute('title', 'jiavan');
38 | var title = Van('#qunit-fixture').attr('title');
39 | equal(title, 'jiavan', 'title的属性应该是jiavan');
40 | });
41 |
42 | test('设置attribute', function () {
43 | Van('#qunit-fixture').attr('title', 'jiavan');
44 | var title = document.getElementById('qunit-fixture').getAttribute('title');
45 | equal(title, 'jiavan', 'title属性被设置为jiavan');
46 | });
--------------------------------------------------------------------------------