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