1// Copyright (c) Jupyter Development Team. 2// Distributed under the terms of the Modified BSD License. 3 4import * as decisions from '../../../src/merge/decisions'; 5 6import { 7 opAdd, opRemove, opAddRange, opRemoveRange, opPatch, 8 IDiffEntry, IDiffPatch, DiffCollection 9} from '../../../src/diff/diffentries'; 10 11import { 12 arraysEqual 13} from '../../../src/common/util'; 14 15 16function isDiffEmpty(diff: IDiffEntry[] | null): boolean { 17 if (diff === null || diff.length === 0) { 18 return true; 19 } 20 for (let e of diff) { 21 if (e.op !== 'patch') { 22 return false; 23 } 24 if (!isDiffEmpty(e.diff)) { 25 return false; 26 } 27 } 28 return true; 29} 30 31 32describe('merge', () => { 33 34 describe('decisions', () => { 35 36 describe('MergeDecision class', () => { 37 38 let jsonStructure: decisions.IMergeDecision = { 39 action: 'custom', 40 local_diff: [opAdd('two', 22)], 41 remote_diff: [opAdd('two', 33)], 42 custom_diff: [opAdd('two', 55)], 43 conflict: true, 44 common_path: ['a', 0, '32', 'foo', 'bar'] 45 }; 46 47 it('should initialize by full JSON structure', () => { 48 let value = new decisions.MergeDecision(jsonStructure); 49 expect(value.action).toEqual(jsonStructure.action); 50 expect(value.localDiff).toEqual(jsonStructure.local_diff); 51 expect(value.remoteDiff).toEqual(jsonStructure.remote_diff); 52 expect(value.customDiff).toEqual(jsonStructure.custom_diff); 53 expect(value.conflict).toEqual(jsonStructure.conflict); 54 expect(value.absolutePath).toEqual(jsonStructure.common_path); 55 }); 56 57 it('should serialize out a JSON structure', () => { 58 let d = new decisions.MergeDecision( 59 jsonStructure.common_path!, 60 jsonStructure.local_diff, 61 jsonStructure.remote_diff, 62 jsonStructure.action as decisions.Action, 63 jsonStructure.conflict, 64 jsonStructure.custom_diff 65 ); 66 let value = d.serialize(); 67 expect(value).toEqual(jsonStructure); 68 }); 69 70 it('should recreate a JSON structure from constructor/serialize', () => { 71 let value = new decisions.MergeDecision(jsonStructure).serialize(); 72 expect(value).toEqual(jsonStructure); 73 }); 74 75 it('should initialize to defaults by partial JSON structure', () => { 76 // Check everything in one go with empty structure: 77 let s: decisions.IMergeDecision = { }; 78 let value = new decisions.MergeDecision(s); 79 expect(value.action).toEqual('base'); 80 expect(value.localDiff).toEqual(null); 81 expect(value.remoteDiff).toEqual(null); 82 expect(value.customDiff).toEqual(null); 83 expect(value.conflict).toEqual(false); 84 expect(value.absolutePath).toEqual([]); 85 }); 86 87 it('should initialize by copy constructor', () => { 88 // Check everything in one go with empty structure: 89 let initial = new decisions.MergeDecision(jsonStructure); 90 let value = new decisions.MergeDecision(initial); 91 expect(value.action).toEqual(jsonStructure.action); 92 expect(value.localDiff).toEqual(jsonStructure.local_diff); 93 expect(value.remoteDiff).toEqual(jsonStructure.remote_diff); 94 expect(value.customDiff).toEqual(jsonStructure.custom_diff); 95 expect(value.conflict).toEqual(jsonStructure.conflict); 96 expect(value.absolutePath).toEqual(jsonStructure.common_path); 97 }); 98 99 it('should slice localPath to level', () => { 100 let value = new decisions.MergeDecision(jsonStructure); 101 expect(value.localPath).toEqual(jsonStructure.common_path); 102 103 for (let i = 0; i <= jsonStructure.common_path!.length; ++i) { 104 value.level = i; 105 expect(value.localPath).toEqual(jsonStructure.common_path!.slice(i)); 106 } 107 }); 108 109 it('should be able to push a path', () => { 110 let value = new decisions.MergeDecision(jsonStructure); 111 112 value.pushPath('test'); 113 expect(value.absolutePath).toEqual(['a', 0, '32', 'foo', 'bar', 'test']); 114 }); 115 116 it('should be able to set absolute path', () => { 117 let value = new decisions.MergeDecision(jsonStructure); 118 let path = ['a', 5]; 119 value.absolutePath = path; 120 expect(value.absolutePath).toEqual(path); 121 expect(value.localPath).toEqual(path); 122 }); 123 124 it('should be able to get diffs', () => { 125 let value = new decisions.MergeDecision(jsonStructure); 126 127 value.customDiff = null; 128 129 expect(value.diffs).toEqual([ 130 jsonStructure.local_diff, 131 jsonStructure.remote_diff 132 ]); 133 }); 134 135 it('should be able to set diffs', () => { 136 let value = new decisions.MergeDecision(jsonStructure); 137 138 value.diffs = [ 139 jsonStructure.remote_diff!, 140 jsonStructure.local_diff!, 141 ]; 142 143 expect(value.localDiff).toEqual(jsonStructure.remote_diff); 144 expect(value.remoteDiff).toEqual(jsonStructure.local_diff); 145 146 value.diffs = [ 147 jsonStructure.local_diff!, 148 jsonStructure.remote_diff!, 149 null 150 ]; 151 152 expect(value.localDiff).toEqual(jsonStructure.local_diff); 153 expect(value.remoteDiff).toEqual(jsonStructure.remote_diff); 154 expect(value.customDiff).toEqual(null); 155 }); 156 157 it('should have diffs include custom diff if set', () => { 158 let value = new decisions.MergeDecision(jsonStructure); 159 160 expect(value.diffs).toEqual([ 161 jsonStructure.local_diff, 162 jsonStructure.remote_diff, 163 jsonStructure.custom_diff 164 ]); 165 }); 166 167 }); 168 169 describe('popPath', () => { 170 171 it('should always pop patch paths if only passed one diff', () => { 172 let diffs: IDiffEntry[][] = [[opPatch('a', [opPatch(0, [opPatch('foo', 173 [opAdd('two', 'bar')])])])]]; 174 let value = decisions.popPath(diffs)!; 175 expect(value.key).toBe('a'); 176 expect(value.diffs.length).toBe(1); 177 expect(value.diffs[0]).toEqual((diffs[0][0] as IDiffPatch).diff); 178 179 diffs = [(diffs[0][0] as IDiffPatch).diff!]; 180 value = decisions.popPath(diffs)!; 181 expect(value.key).toBe(0); 182 expect(value.diffs.length).toBe(1); 183 expect(value.diffs[0]).toEqual((diffs[0][0] as IDiffPatch).diff); 184 185 diffs = [(diffs[0][0] as IDiffPatch).diff!]; 186 value = decisions.popPath(diffs)!; 187 expect(value.key).toBe('foo'); 188 expect(value.diffs.length).toBe(1); 189 expect(value.diffs[0]).toEqual((diffs[0][0] as IDiffPatch).diff); 190 }); 191 192 it('should pop shared patch paths', () => { 193 let diffs: IDiffEntry[][] = [ 194 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])], 195 [opPatch('a', [opPatch(0, [opAdd('two', 'whizz')])])] 196 ]; 197 let value = decisions.popPath(diffs)!; 198 expect(value.key).toBe('a'); 199 expect(value.diffs.length).toBe(2); 200 expect(value.diffs[0]).toEqual((diffs[0][0] as IDiffPatch).diff); 201 expect(value.diffs[1]).toEqual((diffs[1][0] as IDiffPatch).diff); 202 203 diffs = [(diffs[0][0] as IDiffPatch).diff!, 204 (diffs[1][0] as IDiffPatch).diff!]; 205 value = decisions.popPath(diffs)!; 206 expect(value.key).toBe(0); 207 expect(value.diffs.length).toBe(2); 208 expect(value.diffs[0]).toEqual((diffs[0][0] as IDiffPatch).diff); 209 expect(value.diffs[1]).toEqual((diffs[1][0] as IDiffPatch).diff); 210 }); 211 212 it('should pop patch path if one entry is null', () => { 213 let diffs: DiffCollection = [ 214 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])], 215 null 216 ]; 217 let value = decisions.popPath(diffs)!; 218 expect(value.key).toBe('a'); 219 expect(value.diffs.length).toBe(2); 220 expect(value.diffs[0]).toEqual((diffs[0]![0] as IDiffPatch).diff); 221 expect(value.diffs[1]).toEqual(null); 222 223 // Check there is no preference for order: 224 diffs = [ 225 null, 226 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])] 227 ]; 228 value = decisions.popPath(diffs)!; 229 expect(value.key).toBe('a'); 230 expect(value.diffs.length).toBe(2); 231 expect(value.diffs[0]).toEqual(null); 232 expect(value.diffs[1]).toEqual((diffs[1]![0] as IDiffPatch).diff); 233 }); 234 235 it('should NOT pop patch path if only one side has patch', () => { 236 let diffs: IDiffEntry[][] = [ 237 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])], 238 [opAdd('b', 'bar')] 239 ]; 240 let value = decisions.popPath(diffs); 241 expect(value).toBe(null); 242 }); 243 244 it('should NOT pop patch path if only one side has multiple entries', () => { 245 let diffs: IDiffEntry[][] = [ 246 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])], 247 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])]), opAdd('b', 'bar')] 248 ]; 249 let value = decisions.popPath(diffs); 250 expect(value).toBe(null); 251 252 diffs = [ 253 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])], 254 [opAdd('b', 'bar'), opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])] 255 ]; 256 value = decisions.popPath(diffs); 257 expect(value).toBe(null); 258 }); 259 260 it('should NOT pop path if both sides has multiple entries', () => { 261 let diffs: IDiffEntry[][] = [ 262 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])]), opAdd('b', 'bar')], 263 [opPatch('a', [opPatch(0, [opAdd('three', 'bar')])]), opAdd('b', 'bar')] 264 ]; 265 let value = decisions.popPath(diffs); 266 expect(value).toBe(null); 267 268 diffs = [ 269 [opAdd('b', 'bar'), opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])], 270 [opAdd('b', 'bar'), opPatch('a', [opPatch(0, [opAdd('three', 'bar')])])] 271 ]; 272 value = decisions.popPath(diffs); 273 expect(value).toBe(null); 274 }); 275 276 it('should return null on empty input', () => { 277 expect(decisions.popPath([])).toBe(null); 278 expect(decisions.popPath([[], []])).toBe(null); 279 expect(decisions.popPath([null, null])).toBe(null); 280 }); 281 282 it('should only pop patch path if inner diffs have a length of 1, or if popInner is true', () => { 283 let diffs: IDiffEntry[][] = [ 284 [opPatch(0, [opAdd('three', 'bar'), opAdd('two', 'bar')])], 285 [opPatch(0, [opAdd('three', 'bar'), opAdd('one', 'bar')])] 286 ]; 287 let value = decisions.popPath(diffs); 288 expect(value).toBe(null); 289 290 value = decisions.popPath(diffs, true)!; 291 expect(value).not.toBe(null); 292 expect(value.key).toBe(0); 293 expect(value.diffs[0]!.length).toBe(2); 294 expect(value.diffs[1]!.length).toBe(2); 295 296 diffs = [ 297 [opPatch(0, [opAdd('three', 'bar')])], 298 [opPatch(0, [opAdd('three', 'bar'), opAdd('one', 'bar')])] 299 ]; 300 value = decisions.popPath(diffs); 301 expect(value).toBe(null); 302 303 value = decisions.popPath(diffs, true)!; 304 expect(value).not.toBe(null); 305 expect(value.key).toBe(0); 306 expect(value.diffs[0]!.length).toBe(1); 307 expect(value.diffs[1]!.length).toBe(2); 308 309 diffs = [ 310 [opPatch(0, [opAdd('three', 'bar'), opAdd('two', 'bar')])], 311 [opPatch(0, [opAdd('three', 'bar')])] 312 ]; 313 value = decisions.popPath(diffs); 314 expect(value).toBe(null); 315 316 value = decisions.popPath(diffs, true)!; 317 expect(value).not.toBe(null); 318 expect(value.key).toBe(0); 319 expect(value.diffs[0]!.length).toBe(2); 320 expect(value.diffs[1]!.length).toBe(1); 321 }); 322 323 }); 324 325 describe('resolveCommonPaths', () => { 326 327 it('should move patch ops to common path', () => { 328 let decs = [ 329 new decisions.MergeDecision([], 330 [opPatch('a', [opPatch(0, [opPatch('foo', null)])])], 331 [opPatch('a', [opPatch(0, [opRemove('foo')])])] 332 ), 333 new decisions.MergeDecision([], 334 [opPatch(33, [opPatch(0, [opPatch('foo', null)])])], 335 [opPatch(33, [opPatch(0, null)])] 336 ) 337 ]; 338 339 decisions.resolveCommonPaths(decs); 340 expect(decs.length).toBe(2); 341 expect(decs[0].absolutePath).toEqual(['a', 0]); 342 expect(decs[1].absolutePath).toEqual([33, 0, 'foo']); 343 }); 344 345 }); 346 347 describe('pushPatchDecision', () => { 348 349 let simpleDecision : decisions.IMergeDecision = { 350 local_diff: [opAddRange(3, ['line 4\n'])], 351 remote_diff: [opAddRange(3, ['alternative line 4\n'])], 352 common_path: ['cells', 3, 'source'] 353 }; 354 355 it('should push a single level prefix', () => { 356 let dec = new decisions.MergeDecision(simpleDecision); 357 let value = decisions.pushPatchDecision(dec, ['source']); 358 expect(value.absolutePath).toEqual( 359 ['cells', 3] 360 ); 361 expect(value.localDiff).toEqual( 362 [opPatch('source', dec.localDiff)] 363 ); 364 expect(value.remoteDiff).toEqual( 365 [opPatch('source', dec.remoteDiff)] 366 ); 367 }); 368 369 it('should push a multi-level prefix', () => { 370 let dec = new decisions.MergeDecision(simpleDecision); 371 let value = decisions.pushPatchDecision(dec, ['cells', 3, 'source']); 372 expect(value.absolutePath).toEqual([]); 373 expect(value.localDiff).toEqual( 374 [opPatch('cells', [opPatch(3, 375 [opPatch('source', dec.localDiff)])])] 376 ); 377 expect(value.remoteDiff).toEqual( 378 [opPatch('cells', [opPatch(3, 379 [opPatch('source', dec.remoteDiff)])])] 380 ); 381 }); 382 383 it('should only change path if diffs are missing', () => { 384 let dec = new decisions.MergeDecision(simpleDecision); 385 dec.localDiff = dec.remoteDiff = null; 386 let value = decisions.pushPatchDecision(dec, ['cells', 3, 'source']); 387 expect(value.absolutePath).toEqual([]); 388 // Check that everything else is unchanged: 389 dec.absolutePath = []; 390 expect(value.serialize()).toEqual(dec.serialize()); 391 }); 392 393 it('should push a custom diff as well', () => { 394 let dec = new decisions.MergeDecision(simpleDecision); 395 dec.customDiff = dec.localDiff; 396 dec.localDiff = dec.remoteDiff = null; 397 let value = decisions.pushPatchDecision(dec, ['source']); 398 expect(value.absolutePath).toEqual( 399 ['cells', 3] 400 ); 401 expect(value.customDiff).toEqual( 402 [opPatch('source', dec.customDiff)] 403 ); 404 }); 405 406 it('should fail to push an invalid prefix', () => { 407 let dec = new decisions.MergeDecision(simpleDecision); 408 expect(() => {decisions.pushPatchDecision( 409 dec, ['cells'])}).toThrow( 410 /Cannot push a patch that doesn\'t correspond to a key in the decision path!/ 411 ); 412 }); 413 414 it('should fail to push a prefix longer than path', () => { 415 let dec = new decisions.MergeDecision(simpleDecision); 416 expect(() => {decisions.pushPatchDecision( 417 dec, ['/', 'cells', 3, 'source'])}).toThrow( 418 /Cannot remove key from empty decision path: / 419 ); 420 }); 421 422 }); 423 424 describe('buildDiffs', () => { 425 let base = { 426 source: 'line 1\nline 2\nline 3 is longer\n', 427 metadata: { 428 secret: 'foo!' 429 } 430 }; 431 432 let simpleDecision : decisions.IMergeDecision = { 433 local_diff: [opAddRange(3, ['line 4\n'])], 434 remote_diff: [opAddRange(3, ['alternative line 4\n'])], 435 common_path: ['source'] 436 }; 437 438 it('should build a simple local diff irregardless of action', () => { 439 let dec = new decisions.MergeDecision(simpleDecision); 440 for (let a of ['base', 'local', 'remote', 'clear', 'local_then_remote']) { 441 dec.action = a as any; 442 let value = decisions.buildDiffs(base, [dec], 'local'); 443 expect(value).toEqual( 444 [opPatch('source', dec.localDiff)] 445 ); 446 } 447 }); 448 449 it('should build a simple remote diff irregardless of action', () => { 450 let dec = new decisions.MergeDecision(simpleDecision); 451 for (let a of ['base', 'local', 'remote', 'clear', 'local_then_remote']) { 452 dec.action = a as any; 453 let value = decisions.buildDiffs(base, [dec], 'remote'); 454 expect(value).toEqual( 455 [opPatch('source', dec.remoteDiff)] 456 ); 457 } 458 }); 459 460 it('should build a simple merged diff for local decision', () => { 461 let dec = new decisions.MergeDecision(simpleDecision); 462 dec.action = 'local'; 463 let value = decisions.buildDiffs(base, [dec], 'merged'); 464 expect(value).toEqual( 465 [opPatch('source', dec.localDiff)] 466 ); 467 }); 468 469 it('should build a simple merged diff for remote decision', () => { 470 let dec = new decisions.MergeDecision(simpleDecision); 471 dec.action = 'remote'; 472 let value = decisions.buildDiffs(base, [dec], 'merged'); 473 expect(value).toEqual( 474 [opPatch('source', dec.remoteDiff)] 475 ); 476 }); 477 478 it('should build a simple merged diff for custom decision', () => { 479 let dec = new decisions.MergeDecision(simpleDecision); 480 dec.customDiff = dec.localDiff; 481 dec.action = 'custom'; 482 let value = decisions.buildDiffs(base, [dec], 'merged'); 483 expect(value).toEqual( 484 [opPatch('source', dec.customDiff)] 485 ); 486 }); 487 488 it('should build an empty merged diff for base decision', () => { 489 let dec = new decisions.MergeDecision(simpleDecision); 490 let value = decisions.buildDiffs(base, [dec], 'merged'); 491 expect(isDiffEmpty(value)).toBe(true); 492 }); 493 494 it('should build an interleaved merged diff for local_then_remote decision', () => { 495 let dec = new decisions.MergeDecision(simpleDecision); 496 dec.action = 'local_then_remote'; 497 let value = decisions.buildDiffs(base, [dec], 'merged'); 498 expect(value).toEqual( 499 [opPatch('source', dec.localDiff!.concat(dec.remoteDiff!))] 500 ); 501 }); 502 503 it('should build an interleaved merged diff for remote_then_local decision', () => { 504 let dec = new decisions.MergeDecision(simpleDecision); 505 dec.action = 'remote_then_local'; 506 let value = decisions.buildDiffs(base, [dec], 'merged'); 507 expect(value).toEqual( 508 [opPatch('source', dec.remoteDiff!.concat(dec.localDiff!))] 509 ); 510 }); 511 512 it('should build a diff for a clear_parent decision on a string', () => { 513 let dec = new decisions.MergeDecision(simpleDecision); 514 dec.action = 'clear_parent'; 515 let value = decisions.buildDiffs(base, [dec], 'merged'); 516 let expectedInner = opRemoveRange(0, 4); 517 expectedInner.source = { decision: dec, action: 'custom' }; 518 expect(value).toEqual( 519 [opPatch('source', [expectedInner])] 520 ); 521 }); 522 523 }); 524 525 describe('filterDecisions', () => { 526 527 let paths = [ 528 ['cells', 0, 'outputs', 0], 529 ['cells', 0, 'outputs', 1], 530 ['cells', 2, 'outputs', 1], 531 ['cells', 12, 'outputs', 0, 'data'] 532 ]; 533 534 let decs: decisions.MergeDecision[] = []; 535 for (let p of paths) { 536 decs.push(new decisions.MergeDecision(p)); 537 } 538 539 it('should pass all on shared prefix', () => { 540 let value = decisions.filterDecisions(decs, ['cells']); 541 expect(value).toEqual(decs); 542 for (let d of value) { 543 expect(d.level).toBe(1); 544 } 545 }); 546 547 it('should return same instances', () => { 548 let value = decisions.filterDecisions(decs, ['cells']); 549 expect(arraysEqual(value, decs)).toBe(true); 550 }); 551 552 it('should filter on shared prefix', () => { 553 let value = decisions.filterDecisions(decs, ['cells', 0]); 554 expect(value).toEqual(decs.slice(0, 2)); 555 for (let d of value) { 556 expect(d.level).toBe(2); 557 } 558 }); 559 560 it('should filter on common segment with skipLevels', () => { 561 let value = decisions.filterDecisions(decs, ['outputs'], 2); 562 expect(value).toEqual(decs); 563 for (let d of value) { 564 expect(d.level).toBe(3); 565 } 566 }); 567 568 it('should filter on shared prefix', () => { 569 let value = decisions.filterDecisions(decs, ['outputs', 0], 2); 570 expect(value).toEqual([decs[0], decs[3]]); 571 for (let d of value) { 572 expect(d.level).toBe(4); 573 } 574 }); 575 576 }); 577 578 describe('applyDecisions', () => { 579 let baseObject = { 580 source: 'line 1\nline 2\nline 3 is longer\n', 581 metadata: { 582 secret: 'foo!' 583 } 584 }; 585 586 let simpleObjectDecision : decisions.IMergeDecision = { 587 local_diff: [opAddRange(3, ['line 4\n'])], 588 remote_diff: [opAddRange(3, ['alternative line 4\n'])], 589 common_path: ['source'] 590 }; 591 592 it('should apply \'base\' action on object', () => { 593 let decs = [new decisions.MergeDecision( 594 simpleObjectDecision 595 )]; 596 let value = decisions.applyDecisions(baseObject, decs); 597 expect(value).toEqual(baseObject); 598 }); 599 600 it('should apply \'local\' action on object', () => { 601 let decs = [new decisions.MergeDecision( 602 simpleObjectDecision 603 )]; 604 decs[0].action = 'local'; 605 let value = decisions.applyDecisions(baseObject, decs); 606 expect(value.source).toEqual( 607 baseObject.source + 'line 4\n' 608 ); 609 }); 610 611 it('should apply \'remote\' action on object', () => { 612 let decs = [new decisions.MergeDecision( 613 simpleObjectDecision 614 )]; 615 decs[0].action = 'remote'; 616 let value = decisions.applyDecisions(baseObject, decs); 617 expect(value.source).toEqual( 618 baseObject.source + 'alternative line 4\n' 619 ); 620 }); 621 622 it('should apply \'either\' action on object', () => { 623 let decs = [new decisions.MergeDecision( 624 simpleObjectDecision 625 )]; 626 decs[0].remoteDiff = decs[0].localDiff; 627 decs[0].action = 'either'; 628 let value = decisions.applyDecisions(baseObject, decs); 629 expect(value.source).toEqual( 630 baseObject.source + 'line 4\n'); 631 }); 632 633 it('should handle multiple decisions with shared path', () => { 634 let decs = [ 635 new decisions.MergeDecision({ 636 local_diff: [opAddRange(0, ['top '])], 637 remote_diff: [opAddRange(0, ['not '])], 638 common_path: ['metadata', 'secret'] 639 }), 640 new decisions.MergeDecision({ 641 local_diff: [opAdd('foo', true)], 642 remote_diff: [opAdd('foo', true)], 643 common_path: ['metadata'] 644 }), 645 new decisions.MergeDecision({ 646 local_diff: [opRemoveRange(0, 1)], 647 common_path: ['seq'] 648 }), 649 new decisions.MergeDecision({ 650 local_diff: [opRemoveRange(1, 1)], 651 remote_diff: [opRemoveRange(1, 1)], 652 common_path: ['seq'] 653 }), 654 new decisions.MergeDecision({ 655 local_diff: [opAdd('bar', 43)], 656 remote_diff: [opAdd('bar', 12)], 657 common_path: ['metadata'] 658 }), 659 ]; 660 decs[0].action = 'local'; 661 decs[1].action = 'either'; 662 decs[2].action = 'local'; 663 decs[3].action = 'either'; 664 decs[4].action = 'local'; 665 let value = decisions.applyDecisions({...baseObject, seq: ['foo', 'bar']}, decs); 666 expect(value.metadata).toEqual({ 667 foo: true, 668 bar: 43, 669 secret: 'top foo!', 670 }); 671 expect(value.seq).toEqual([]); 672 }); 673 674 }); 675 676 }); 677 678}); 679