1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3
4import {
5  patch, patchStringified
6} from '../../../src/patch';
7
8import {
9  IDiffEntry, IDiffAdd, IDiffRemove, IDiffReplace,
10  IDiffPatch, IDiffAddRange, IDiffRemoveRange
11} from '../../../src/diff/diffentries';
12
13import {
14  JSON_INDENT
15} from '../../../src/diff/util';
16import { JSONObject, JSONValue } from '@lumino/coreutils';
17
18
19function makeAddRange(key: number, values: string | any[]) : IDiffAddRange {
20  return {key: key, op: 'addrange', valuelist: values};
21}
22
23function makeRemoveRange(key: number, length: number) : IDiffRemoveRange {
24  return {key: key, op: 'removerange', length: length};
25}
26
27function makeAdd(key: string, value: any) : IDiffAdd {
28  return {key: key, op: 'add', value: value};
29}
30
31function makeRemove(key: string) : IDiffRemove {
32  return {key: key, op: 'remove'};
33}
34
35function makeReplace(key: string, value: any) : IDiffReplace {
36  return {key: key, op: 'replace', value: value};
37}
38
39function makePatch(key: number | string, diff: IDiffEntry[] | null) : IDiffPatch {
40  return {key: key, op: 'patch', diff: diff};
41}
42
43
44describe('patch', () => {
45
46  describe('patchStringified', () => {
47
48    it('should patch a simple string addition', () => {
49      let base = 'abcdef';
50      let diff = makePatch(0, [makeAddRange(3, 'ghi')]);
51      let value = patchStringified(base, [diff]);
52      expect(value.remote).toBe('abcghidef');
53      expect(value.additions).toEqual([{from: 3, to: 6, source: undefined}]);
54      expect(value.deletions).toHaveLength(0);
55    });
56
57    it('should patch a simple string deletion', () => {
58      let base = 'abcdef';
59      let diff = makePatch(0, [makeRemoveRange(2, 2)]);
60      let value = patchStringified(base, [diff]);
61      expect(value.remote).toBe('abef');
62      expect(value.additions).toHaveLength(0);
63      expect(value.deletions).toEqual([{from: 2, to: 4, source: undefined}]);
64    });
65
66    it('should patch a string with null diff', () => {
67      let base = 'abcdef';
68      let diff = null;
69      let value = patchStringified(base, diff);
70      expect(value.remote).toBe('abcdef');
71      expect(value.additions).toHaveLength(0);
72      expect(value.deletions).toHaveLength(0);
73    });
74
75    it('should patch a nested string with null diff', () => {
76      let base = {a: 'abcdef'};
77      let diff = [makePatch('a', null)];
78      let value = patchStringified(base, diff);
79      expect(value.remote).toBe(
80        '{\n' +
81        JSON_INDENT + '\"a\": \"abcdef\"\n' +
82        '}'
83      );
84      expect(value.additions).toHaveLength(0);
85      expect(value.deletions).toHaveLength(0);
86    });
87
88    it('should patch a simple string with simple escapes', () => {
89      let base = 'a \"string\" with\nsome\tescapable characters';
90      let diff = [
91        makePatch(1, [
92          makeAddRange('some'.length, '\n'),
93          makeRemoveRange('some'.length, '\tescapable characters'.length)
94        ]),
95        makeAddRange(2, ['\tadded escapable characters']),
96      ];
97      // Here, level is passed as > 0, which indicates that JSON stringification should be used
98      let value = patchStringified(base, diff, 1);
99      expect(value.remote).toBe(
100        JSON_INDENT + '\"a \\\"string\\\" with\\nsome\\n\\tadded escapable characters\"'
101      );
102    });
103
104    it('should patch a list addition', () => {
105      let base = [1, 2, 3];
106      let diff = makeAddRange(2, [-1, -2]);
107      let value = patchStringified(base, [diff]);
108      expect(value.remote).toBe(
109        '[\n' +
110        JSON_INDENT + '1,\n' +
111        JSON_INDENT + '2,\n' +
112        JSON_INDENT + '-1,\n' +
113        JSON_INDENT + '-2,\n' +
114        JSON_INDENT + '3\n' +
115        ']'
116      );
117      let f = '[\n1,\n2,\n'.length + JSON_INDENT.length * 2;
118      let t = f + '-1,\n-2,\n'.length + JSON_INDENT.length * 2;
119      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
120      expect(value.deletions).toHaveLength(0);
121    });
122
123    it('should patch a list addition at start', () => {
124      let base = [1, 2, 3];
125      let diff = makeAddRange(0, [-1, -2]);
126      let value = patchStringified(base, [diff]);
127      expect(value.remote).toBe(
128        '[\n' +
129        JSON_INDENT + '-1,\n' +
130        JSON_INDENT + '-2,\n' +
131        JSON_INDENT + '1,\n' +
132        JSON_INDENT + '2,\n' +
133        JSON_INDENT + '3\n' +
134        ']'
135      );
136      let f = '[\n'.length;
137      let t = f + '-1,\n-2,\n'.length + JSON_INDENT.length * 2;
138      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
139      expect(value.deletions).toHaveLength(0);
140    });
141
142    it('should patch a list addition at end', () => {
143      let base = [1, 2, 3];
144      let diff = makeAddRange(3, [-1, -2]);
145      let value = patchStringified(base, [diff]);
146      expect(value.remote).toBe(
147        '[\n' +
148        JSON_INDENT + '1,\n' +
149        JSON_INDENT + '2,\n' +
150        JSON_INDENT + '3,\n' +
151        JSON_INDENT + '-1,\n' +
152        JSON_INDENT + '-2\n' +
153        ']'
154      );
155      let f = '[\n1,\n2,\n3,\n'.length + JSON_INDENT.length * 3;
156      let t = f + '-1,\n-2\n'.length + JSON_INDENT.length * 2;
157      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
158      expect(value.deletions).toHaveLength(0);
159    });
160
161    it('should patch a list deletion', () => {
162      let base = [1, 2, 3, 4, 5];
163      let diff = makeRemoveRange(2, 2);
164      let value = patchStringified(base, [diff]);
165      expect(value.remote).toBe(
166        '[\n' +
167        JSON_INDENT + '1,\n' +
168        JSON_INDENT + '2,\n' +
169        JSON_INDENT + '5\n' +
170        ']'
171      );
172      let f = '[\n1,\n2,\n'.length + JSON_INDENT.length * 2;
173      let t = f + '3,\n4,\n'.length + JSON_INDENT.length * 2;
174      expect(value.additions).toHaveLength(0);
175      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
176    });
177
178    it('should patch a list deletion at start', () => {
179      let base = [1, 2, 3, 4, 5];
180      let diff = makeRemoveRange(0, 2);
181      let value = patchStringified(base, [diff]);
182      expect(value.remote).toBe(
183        '[\n' +
184        JSON_INDENT + '3,\n' +
185        JSON_INDENT + '4,\n' +
186        JSON_INDENT + '5\n' +
187        ']'
188      );
189      let f = '[\n'.length;
190      let t = f + '1,\n2,\n'.length + JSON_INDENT.length * 2;
191      expect(value.additions).toHaveLength(0);
192      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
193    });
194
195    it('should patch a list deletion at end', () => {
196      let base = [1, 2, 3, 4, 5];
197      let diff = makeRemoveRange(3, 2);
198      let value = patchStringified(base, [diff]);
199      expect(value.remote).toBe(
200        '[\n' +
201        JSON_INDENT + '1,\n' +
202        JSON_INDENT + '2,\n' +
203        JSON_INDENT + '3\n' +
204        ']'
205      );
206      let f = '[\n1,\n2,\n3\n,'.length + JSON_INDENT.length * 3;
207      let t = f + '4,\n5\n'.length + JSON_INDENT.length * 2;
208      expect(value.additions).toHaveLength(0);
209      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
210    });
211
212    it('should patch a list with null diff', () => {
213      let base = [1, 2, 3];
214      let diff = null;
215      let value = patchStringified(base, diff);
216      expect(value.remote).toBe(
217        '[\n' +
218        JSON_INDENT + '1,\n' +
219        JSON_INDENT + '2,\n' +
220        JSON_INDENT + '3\n' +
221        ']'
222      );
223      expect(value.additions).toHaveLength(0);
224      expect(value.deletions).toHaveLength(0);
225    });
226
227    it('should patch an object addition', () => {
228      let base = {a: 1, d: 'test', c: true};
229      let diff = makeAdd('b', 42);
230      let value = patchStringified(base, [diff]);
231      expect(value.remote).toBe(
232        '{\n' +
233        JSON_INDENT + '\"a\": 1,\n' +
234        JSON_INDENT + '\"b\": 42,\n' +
235        JSON_INDENT + '\"c\": true,\n' +
236        JSON_INDENT + '\"d\": \"test\"\n' +
237        '}'
238      );
239      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
240      let t = f + '\"b\": 42,\n'.length + JSON_INDENT.length;
241      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
242      expect(value.deletions).toHaveLength(0);
243    });
244
245    it('should patch an object addition at start', () => {
246      let base = {b: 1, d: 'test', c: true};
247      let diff = makeAdd('a', 42);
248      let value = patchStringified(base, [diff]);
249      expect(value.remote).toBe(
250        '{\n' +
251        JSON_INDENT + '\"a\": 42,\n' +
252        JSON_INDENT + '\"b\": 1,\n' +
253        JSON_INDENT + '\"c\": true,\n' +
254        JSON_INDENT + '\"d\": \"test\"\n' +
255        '}'
256      );
257      let f = '{\n'.length;
258      let t = f + '\"a\": 42,\n'.length + JSON_INDENT.length;
259      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
260      expect(value.deletions).toHaveLength(0);
261    });
262
263    it('should patch an object addition at end', () => {
264      let base = {a: 1, b: 'test', c: true};
265      let diff = makeAdd('d', 42);
266      let value = patchStringified(base, [diff]);
267      expect(value.remote).toBe(
268        '{\n' +
269        JSON_INDENT + '\"a\": 1,\n' +
270        JSON_INDENT + '\"b\": \"test\",\n' +
271        JSON_INDENT + '\"c\": true,\n' +
272        JSON_INDENT + '\"d\": 42\n' +
273        '}'
274      );
275      let f = '{\n\"a\": 1,\n\"b\": \"test\",\n\"c\": true,\n'.length +
276        JSON_INDENT.length * 3;
277      let t = f + '\"d\": 42\n'.length + JSON_INDENT.length;
278      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
279      expect(value.deletions).toHaveLength(0);
280    });
281
282    it('should patch an object removal', () => {
283      let base = {a: 1, d: 'test', c: true};
284      let diff = makeRemove('c');
285      let value = patchStringified(base, [diff]);
286      expect(value.remote).toBe(
287        '{\n' +
288        JSON_INDENT + '\"a\": 1,\n' +
289        JSON_INDENT + '\"d\": \"test\"\n' +
290        '}'
291      );
292      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
293      let t = f + '\"c\": true,\n'.length + JSON_INDENT.length;
294      expect(value.additions).toHaveLength(0);
295      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
296    });
297
298    it('should patch an object removal at start', () => {
299      let base = {a: 1, d: 'test', c: true};
300      let diff = makeRemove('a');
301      let value = patchStringified(base, [diff]);
302      expect(value.remote).toBe(
303        '{\n' +
304        JSON_INDENT + '\"c\": true,\n' +
305        JSON_INDENT + '\"d\": \"test\"\n' +
306        '}'
307      );
308      let f = '{\n'.length;
309      let t = f + '\"a\": 1,\n'.length + JSON_INDENT.length;
310      expect(value.additions).toHaveLength(0);
311      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
312    });
313
314    it('should patch an object removal at end', () => {
315      let base = {a: 1, d: 'test', c: true};
316      let diff = makeRemove('d');
317      let value = patchStringified(base, [diff]);
318      expect(value.remote).toBe(
319        '{\n' +
320        JSON_INDENT + '\"a\": 1,\n' +
321        JSON_INDENT + '\"c\": true\n' +
322        '}'
323      );
324      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
325      f += '\"c\": true,\n'.length + JSON_INDENT.length;
326      let t = f + '\"d\": \"test\"\n'.length + JSON_INDENT.length;
327      expect(value.additions).toHaveLength(0);
328      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
329    });
330
331    it('should patch an object replacement', () => {
332      let base = {a: 1, d: 'test', c: true};
333      let diff = makeReplace('c', false);
334      let value = patchStringified(base, [diff]);
335      expect(value.remote).toBe(
336        '{\n' +
337        JSON_INDENT + '\"a\": 1,\n' +
338        JSON_INDENT + '\"c\": false,\n' +
339        JSON_INDENT + '\"d\": \"test\"\n' +
340        '}'
341      );
342      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
343      f += '\"c\": '.length + JSON_INDENT.length;
344      let tAdd = f + 'false'.length;
345      let tDel = f + 'true'.length;
346      expect(value.additions).toEqual([{from: f, to: tAdd, source: undefined}]);
347      expect(value.deletions).toEqual([{from: f, to: tDel, source: undefined}]);
348    });
349
350    it('should patch an object replacement at start', () => {
351      let base = {a: 1, d: 'test', c: true};
352      let diff = makeReplace('a', 1234);
353      let value = patchStringified(base, [diff]);
354      expect(value.remote).toBe(
355        '{\n' +
356        JSON_INDENT + '\"a\": 1234,\n' +
357        JSON_INDENT + '\"c\": true,\n' +
358        JSON_INDENT + '\"d\": \"test\"\n' +
359        '}'
360      );
361      let f = '{\n\"a\": '.length + JSON_INDENT.length;
362      let tAdd = f + '1234'.length;
363      let tDel = f + '1'.length;
364      expect(value.additions).toEqual([{from: f, to: tAdd, source: undefined}]);
365      expect(value.deletions).toEqual([{from: f, to: tDel, source: undefined}]);
366    });
367
368    it('should patch an object replacement at end', () => {
369      let base = {a: 1, d: 'test', c: true};
370      let diff = makeReplace('d', 'foobar');
371      let value = patchStringified(base, [diff]);
372      expect(value.remote).toBe(
373        '{\n' +
374        JSON_INDENT + '\"a\": 1,\n' +
375        JSON_INDENT + '\"c\": true,\n' +
376        JSON_INDENT + '\"d\": \"foobar\"\n' +
377        '}'
378      );
379      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
380      f += '\"c\": true,\n'.length + JSON_INDENT.length;
381      f += '\"d\": '.length + JSON_INDENT.length;
382      let tAdd = f + '\"foobar\"'.length;
383      let tDel = f + '\"test\"'.length;
384      expect(value.additions).toEqual([{from: f, to: tAdd, source: undefined}]);
385      expect(value.deletions).toEqual([{from: f, to: tDel, source: undefined}]);
386    });
387
388    it('should patch an object replacement with changing type', () => {
389      let base = {a: 1, d: 'test', c: true};
390      let diff = makeReplace('c', ['foo', 'bar']);
391      let value = patchStringified(base, [diff]);
392      expect(value.remote).toBe(
393        '{\n' +
394        JSON_INDENT + '\"a\": 1,\n' +
395        JSON_INDENT + '\"c\": [\n' +
396        JSON_INDENT + JSON_INDENT + '\"foo\",\n' +
397        JSON_INDENT + JSON_INDENT + '\"bar\"\n' +
398        JSON_INDENT + '],\n' +
399        JSON_INDENT + '\"d\": \"test\"\n' +
400        '}'
401      );
402      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
403      f += '\"c\": '.length + JSON_INDENT.length;
404      let tAdd = f + '[\n\"foo\",\n\"bar\"\n]'.length + 5 * JSON_INDENT.length;
405      let tDel = f + 'true'.length;
406      expect(value.additions).toEqual([{from: f, to: tAdd, source: undefined}]);
407      expect(value.deletions).toEqual([{from: f, to: tDel, source: undefined}]);
408    });
409
410    it('should patch an object with combined addition and removal at end', () => {
411      let base = {a: 1, d: 'test', b: true};
412      // For this diff, a naive stringifier might get confused
413      // whether there should be a comma at the end of 'c' entry.
414      let diff = [makeAdd('c', 42), makeRemove('d')];
415      let value = patchStringified(base, diff);
416      expect(value.remote).toBe(
417        '{\n' +
418        JSON_INDENT + '\"a\": 1,\n' +
419        JSON_INDENT + '\"b\": true,\n' +
420        JSON_INDENT + '\"c\": 42\n' +
421        '}'
422      );
423      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
424      f += '\"b\": true,\n'.length + JSON_INDENT.length;
425      let tAdd = f + '\"c\": 42\n'.length + JSON_INDENT.length;
426      let tDel = f + '\"d\": \"test\"\n'.length + JSON_INDENT.length;
427      expect(value.additions).toEqual([{from: f, to: tAdd, source: undefined}]);
428      expect(value.deletions).toEqual([{from: f, to: tDel, source: undefined}]);
429    });
430
431    it('should patch an object with combined addition and patch at end', () => {
432      let base = {a: 1, d: 'test', b: true};
433      // For this diff, a naive stringifier might get confused
434      // whether there should be a comma at the end of 'c' entry.
435      let diff = [makeAdd('c', 42), makePatch('d', [makePatch(0, [makeAddRange(0, 'a ')])])];
436      let value = patchStringified(base, diff);
437      expect(value.remote).toBe(
438        '{\n' +
439        JSON_INDENT + '\"a\": 1,\n' +
440        JSON_INDENT + '\"b\": true,\n' +
441        JSON_INDENT + '\"c\": 42,\n' +
442        JSON_INDENT + '\"d\": \"a test\"\n' +
443        '}'
444      );
445      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
446      f += '\"b\": true,\n'.length + JSON_INDENT.length;
447      let tAdd = f + '\"c\": 42,\n'.length + JSON_INDENT.length;
448      expect(value.additions[0]).toEqual({from: f, to: tAdd, source: undefined});
449    });
450
451    it('should patch an object with combined removal and addition at end', () => {
452      let base = {a: 1, c: 'test', b: true};
453      // For this diff, a naive stringifier might get confused
454      // whether there should be a comma at the end of 'c' entry.
455      let diff = [makeRemove('b'), makePatch('c', [makePatch(0, [makeAddRange(0, 'a ')])])];
456      let value = patchStringified(base, diff);
457      expect(value.remote).toBe(
458        '{\n' +
459        JSON_INDENT + '\"a\": 1,\n' +
460        JSON_INDENT + '\"c\": \"a test\"\n' +
461        '}'
462      );
463      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
464      let tDel = f + '\"b\": true,\n'.length + JSON_INDENT.length;
465      expect(value.deletions[0]).toEqual({from: f, to: tDel, source: undefined});
466    });
467
468    it('should patch an object with combined removal and patch at end', () => {
469      let base = {a: 1, c: 'test', b: true};
470      // For this diff, a naive stringifier might get confused
471      // whether there should be a comma at the end of 'c' entry.
472      let diff = [makeAdd('d', 42), makeRemove('c')];
473      let value = patchStringified(base, diff);
474      expect(value.remote).toBe(
475        '{\n' +
476        JSON_INDENT + '\"a\": 1,\n' +
477        JSON_INDENT + '\"b\": true,\n' +
478        JSON_INDENT + '\"d\": 42\n' +
479        '}'
480      );
481      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
482      f += '\"b\": true,\n'.length + JSON_INDENT.length;
483      let tAdd = f + '\"d\": 42\n'.length + JSON_INDENT.length;
484      let tDel = f + '\"c\": \"test\"\n'.length + JSON_INDENT.length;
485      expect(value.additions).toEqual([{from: f, to: tAdd, source: undefined}]);
486      expect(value.deletions).toEqual([{from: f, to: tDel, source: undefined}]);
487    });
488
489    it('should patch an object with double addition at end', () => {
490      let base = {a: 1, b: true};
491      // For this diff, a naive stringifier might get confused
492      // whether there should be a comma at the end of 'c' entry.
493      let diff = [makeAdd('c', 42), makeAdd('d', 'test')];
494      let value = patchStringified(base, diff);
495      expect(value.remote).toBe(
496        '{\n' +
497        JSON_INDENT + '\"a\": 1,\n' +
498        JSON_INDENT + '\"b\": true,\n' +
499        JSON_INDENT + '\"c\": 42,\n' +
500        JSON_INDENT + '\"d\": \"test\"\n' +
501        '}'
502      );
503      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
504      f += '\"b\": true,\n'.length + JSON_INDENT.length;
505      let tAdd1 = f + '\"c\": 42,\n'.length + JSON_INDENT.length;
506      let tAdd2 = tAdd1 + '\"d\": \"test\"\n'.length + JSON_INDENT.length;
507      expect(value.additions[0]).toEqual({from: f, to: tAdd1, source: undefined});
508      expect(value.additions[1]).toEqual({from: tAdd1, to: tAdd2, source: undefined});
509    });
510
511    it('should patch an object with double removal at end', () => {
512      let base = {a: 1, b: true, c: 42, d: 'test'};
513      // For this diff, a naive stringifier might get confused
514      // whether there should be a comma at the end of 'c' entry.
515      let diff = [makeRemove('c'), makeRemove('d')];
516      let value = patchStringified(base, diff);
517      expect(value.remote).toBe(
518        '{\n' +
519        JSON_INDENT + '\"a\": 1,\n' +
520        JSON_INDENT + '\"b\": true\n' +
521        '}'
522      );
523      let f = '{\n\"a\": 1,\n'.length + JSON_INDENT.length;
524      f += '\"b\": true,\n'.length + JSON_INDENT.length;
525      let tDel1 = f + '\"c\": 42,\n'.length + JSON_INDENT.length;
526      let tDel2 = tDel1 + '\"d\": \"test\"\n'.length + JSON_INDENT.length;
527      expect(value.deletions[0]).toEqual({from: f, to: tDel1, source: undefined});
528      expect(value.deletions[1]).toEqual({from: tDel1, to: tDel2, source: undefined});
529    });
530
531    it('should patch an object with null diff', () => {
532      let base = {a: 1, d: 'test', c: true};
533      let diff = null;
534      let value = patchStringified(base, diff);
535      expect(value.remote).toBe(
536        '{\n' +
537        JSON_INDENT + '\"a\": 1,\n' +
538        JSON_INDENT + '\"c\": true,\n' +
539        JSON_INDENT + '\"d\": \"test\"\n' +
540        '}'
541      );
542      expect(value.additions).toHaveLength(0);
543      expect(value.deletions).toHaveLength(0);
544    });
545
546    it('should patch a nested patch', () => {
547      let base: JSONValue = [
548        {a: 1, c: true},
549        {b: 42, c: 'this\nis\na\nvalid\nstring'}
550      ];
551      let diff = makePatch(1, [
552        makePatch('c', [
553          makePatch(3, [
554            makeAddRange(0, 'patched'),
555            makeRemoveRange(0, 'valid'.length)
556      ])])]);
557      let value = patchStringified(base, [diff]);
558      expect(value.remote).toBe(
559        '[\n' +
560        JSON_INDENT + '{\n' +
561        JSON_INDENT + JSON_INDENT + '\"a\": 1,\n' +
562        JSON_INDENT + JSON_INDENT + '\"c\": true\n' +
563        JSON_INDENT + '},\n' +
564        JSON_INDENT + '{\n' +
565        JSON_INDENT + JSON_INDENT + '\"b\": 42,\n' +
566        JSON_INDENT + JSON_INDENT + '\"c\": ' +
567          '\"this\\nis\\na\\npatched\\nstring\"\n' +
568        JSON_INDENT + '}\n' +
569        ']'
570      );
571      let f = (
572          '[\n{\n\"a\": 1,\n' +
573          '\"c\": true\n},\n' +
574          '{\n\"b\": 42,\n' +
575          '\"c\": \"this\\nis\\na\\n'
576      ).length + JSON_INDENT.length * 11;
577      let t = f + 'patched'.length;
578      console.log(value.remote.slice(value.additions[0].from, value.additions[0].to));
579      expect(value.additions).toEqual([{from: f, to: t, source: undefined}]);
580      t = f + 'valid'.length;
581      expect(value.deletions).toEqual([{from: f, to: t, source: undefined}]);
582    });
583
584    it('should fail to patch atomic types', () => {
585      let diff = makeReplace('toString', () => {/* */});
586
587      // Number
588      let base: any = 5;
589      expect(() => {patchStringified(base, diff as any)}).toThrow(
590        /Cannot patch an atomic type: /
591      );
592
593      // Boolean
594      base = true;
595      expect(() => {patchStringified(base, diff as any)}).toThrow(
596        /Cannot patch an atomic type: /
597      );
598    });
599
600  });
601
602  describe('patch', () => {
603
604    describe('patch object', () => {
605
606      it('should patch an object addition', () => {
607        let base = {a: 55, b: 43};
608        let diff = [makeAdd('c', 22)];
609        let expected = {a: 55, b: 43, c: 22};
610        let value = patch(base, diff);
611        expect(value).toEqual(expected);
612      });
613
614      it('should patch an object removal', () => {
615        let base = {a: 55, b: 43};
616        let diff = [makeRemove('b')];
617        let expected = {a: 55};
618        let value = patch(base, diff);
619        expect(value).toEqual(expected);
620      });
621
622      it('should patch an object replace', () => {
623        let base = {a: 55, b: 43};
624        let diff = [makeReplace('b', 22)];
625        let expected = {a: 55, b: 22};
626        let value = patch(base, diff);
627        expect(value).toEqual(expected);
628      });
629
630      it('should patch an object list-patch', () => {
631        let base = {a: 55, b: [43]};
632        let diff = [makePatch('b', [makeAddRange(0, [22])])];
633        let expected = {a: 55, b: [22, 43]};
634        let value = patch(base, diff);
635        expect(value).toEqual(expected);
636      });
637
638      it('should patch an object object-patch', () => {
639        let base = {a: 55, b: {c : 43}};
640        let diff = [makePatch('b', [makeReplace('c', 22)])];
641        let expected = {a: 55, b: {c: 22}};
642        let value = patch(base, diff);
643        expect(value).toEqual(expected);
644      });
645
646      it('should patch an object with a null diff', () => {
647        let base = {a: 55, b: 43};
648        let diff = null;
649        let patched = patch(base, diff);
650        expect(patched).toEqual(base);
651        // Should return a copy
652        expect(patched).not.toBe(base);
653      });
654
655      it('should fail to patch an object with a non-string key', () => {
656        let base = {a: 55, b: 43};
657        let diff = [makeRemove(32 as any)];
658        expect(() => {patch(base, diff)}).toThrow(
659          /Invalid patch object op: Key is not a string: 32/);
660      });
661
662      it('should fail to patch an object with an add on existing key', () => {
663        let base = {a: 55, b: 43};
664        let diff = [makeAdd('a', 22)];
665        expect(() => {patch(base, diff)}).toThrow(
666          /Invalid add key diff op: Key already present: a/);
667      });
668
669      it('should fail to patch an object with a remove on invalid key', () => {
670        let base = {a: 55, b: 43};
671        let diff = [makeRemove('c')];
672        expect(() => {patch(base, diff)}).toThrow(
673          /Invalid remove key diff op: Missing key: c/);
674      });
675
676      it('should fail to patch an object with a replace on invalid key', () => {
677        let base = {a: 55, b: 43};
678        let diff = [makeReplace('c', 22)];
679        expect(() => {patch(base, diff)}).toThrow(
680          /Invalid replace key diff op: Missing key: c/);
681      });
682
683      it('should fail to patch an object with a patch of an atomic value', () => {
684        let base = {a: 55, b: 43};
685        let diff = [makePatch('b', [makeAdd('b', 22)])];
686        expect(() => {patch(base, diff)}).toThrow(
687          /Cannot patch an atomic type: number/);
688      });
689
690      it('should fail to patch an object with a patch of an invalid key', () => {
691        let base = {a: 55, b: 43};
692        let diff = [makePatch('c', [makeReplace('b', 22)])];
693        expect(() => {patch(base, diff)}).toThrow(
694          /Invalid patch key diff op: Missing key: c/);
695      });
696
697      it('should fail to patch an object with an invalid diff op', () => {
698        let base = {a: 55, b: 43};
699        let diff = [{op: 'typo', value: 22, key: 'c'}];
700        expect(() => {patch(base, diff as any)}).toThrow(/Invalid op: typo/);
701      });
702
703    });
704
705    describe('patch sequence', () => {
706
707      describe('patch sequence addition', () => {
708
709        it('should patch a sequence addition at start', () => {
710          let base = [55, 43];
711          let diff = [makeAddRange(0, [22, 33])];
712          let expected = [22, 33, 55, 43];
713          let value = patch(base, diff);
714          expect(value).toEqual(expected);
715        });
716
717        it('should patch a sequence addition in middle', () => {
718          let base = [55, 43];
719          let diff = [makeAddRange(1, [22, 33])];
720          let expected = [55, 22, 33, 43];
721          let value = patch(base, diff);
722          expect(value).toEqual(expected);
723        });
724
725        it('should patch a sequence addition at end', () => {
726          let base = [55, 43];
727          let diff = [makeAddRange(2, [22, 33])];
728          let expected = [55, 43, 22, 33];
729          let value = patch(base, diff);
730          expect(value).toEqual(expected);
731        });
732      });
733
734      describe('patch sequence removal', () => {
735
736        it('should patch a sequence removal at start', () => {
737          let base = [55, 43, 22];
738          let diff = [makeRemoveRange(0, 2)];
739          let expected = [22];
740          let value = patch(base, diff);
741          expect(value).toEqual(expected);
742        });
743
744        it('should patch a sequence removal in middle', () => {
745          let base = [55, 43, 22, 32];
746          let diff = [makeRemoveRange(1, 2)];
747          let expected = [55, 32];
748          let value = patch(base, diff);
749          expect(value).toEqual(expected);
750        });
751
752        it('should patch a sequence removal at end', () => {
753          let base = [55, 43, 22, 32];
754          let diff = [makeRemoveRange(2, 2)];
755          let expected = [55, 43];
756          let value = patch(base, diff);
757          expect(value).toEqual(expected);
758
759          diff = [makeRemoveRange(3, 1)];
760          expected = [55, 43, 22];
761          value = patch(base, diff);
762          expect(value).toEqual(expected);
763        });
764
765      });
766
767      describe('patch sequence list-patch', () => {
768
769        it('should patch a sequence list-patch at start', () => {
770          let base = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
771          let diff = [makePatch(0, [makeAddRange(0, [22])])];
772          let expected = [[22, 1, 2, 3], [4, 5, 6], [7, 8, 9]];
773          let value = patch(base, diff);
774          expect(value).toEqual(expected);
775        });
776
777        it('should patch a sequence list-patch in middle', () => {
778          let base = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
779          let diff = [makePatch(1, [makeAddRange(0, [22])])];
780          let expected = [[1, 2, 3], [22, 4, 5, 6], [7, 8, 9]];
781          let value = patch(base, diff);
782          expect(value).toEqual(expected);
783        });
784
785        it('should patch a sequence list-patch at end', () => {
786          let base = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
787          let diff = [makePatch(2, [makeAddRange(0, [22])])];
788          let expected = [[1, 2, 3], [4, 5, 6], [22, 7, 8, 9]];
789          let value = patch(base, diff);
790          expect(value).toEqual(expected);
791        });
792
793      });
794
795      describe('patch sequence object-patch', () => {
796
797        it('should patch a sequence object-patch at start', () => {
798          let base = [{a: 32, '15': 33}, {a: 32, '15': 33}, {a: 32, '15': 33}];
799          let diff = [makePatch(0, [makeReplace('15', 22)])];
800          let expected = [{a: 32, '15': 22}, {a: 32, '15': 33}, {a: 32, '15': 33}];
801          let value = patch(base, diff);
802          expect(value).toEqual(expected);
803        });
804
805        it('should patch a sequence object-patch in middle', () => {
806          let base = [{a: 32, '15': 33}, {a: 32, '15': 33}, {a: 32, '15': 33}];
807          let diff = [makePatch(1, [makeReplace('15', 22)])];
808          let expected = [{a: 32, '15': 33}, {a: 32, '15': 22}, {a: 32, '15': 33}];
809          let value = patch(base, diff);
810          expect(value).toEqual(expected);
811        });
812
813        it('should patch a sequence object-patch at end', () => {
814          let base = [{a: 32, '15': 33}, {a: 32, '15': 33}, {a: 32, '15': 33}];
815          let diff = [makePatch(2, [makeReplace('15', 22)])];
816          let expected = [{a: 32, '15': 33}, {a: 32, '15': 33}, {a: 32, '15': 22}];
817          let value = patch(base, diff);
818          expect(value).toEqual(expected);
819        });
820
821      });
822
823      it('should patch a sequence with a null diff', () => {
824        let base = [55, 43];
825        let diff = null;
826        let patched = patch(base, diff);
827        expect(patched).toEqual(base);
828        // Should return a copy
829        expect(patched).not.toBe(base);
830      });
831
832      it('should fail to patch a sequence with a non-number key', () => {
833        let base = [55, 43];
834        // Obvious case
835        let diff = [makeRemove('text')];
836        expect(() => {patch(base, diff)}).toThrow(
837          /Invalid patch sequence op: Key is not a number: text/);
838
839        // Not so obvious case (pure type error, which could be cast correctly)
840        diff = [makeRemove('32')];
841        expect(() => {patch(base, diff)}).toThrow(
842          /Invalid patch sequence op: Key is not a number: 32/);
843      });
844
845      it('should fail to patch a sequence with an add on invalid key', () => {
846        let base = [55, 43];
847        let diff = [makeAddRange(-1, [22])];
848        expect(() => {patch(base, diff)}).toThrow(
849          /Invalid add range diff op: Key out of range: -1/);
850
851        diff = [makeAddRange(3, [22])];
852        expect(() => {patch(base, diff)}).toThrow(
853          /Invalid add range diff op: Key out of range: 3/);
854
855        diff = [makeAddRange(Infinity, [22])];
856        expect(() => {patch(base, diff)}).toThrow(
857          /Invalid add range diff op: Key out of range: Infinity/);
858
859        diff = [makeAddRange(NaN, [22])];
860        expect(() => {patch(base, diff)}).toThrow(
861          /Invalid add range diff op: Key out of range: NaN/);
862      });
863
864      it('should fail to patch a sequence with a remove on invalid key', () => {
865        let base = [55, 43];
866        let diff = [makeRemoveRange(-1, 1)];
867        expect(() => {patch(base, diff)}).toThrow(
868          /Invalid remove range diff op: Key out of range: -1/);
869
870        diff = [makeRemoveRange(2, 1)];
871        expect(() => {patch(base, diff)}).toThrow(
872          /Invalid remove range diff op: Key out of range: 2/);
873
874        diff = [makeRemoveRange(Infinity, 1)];
875        expect(() => {patch(base, diff)}).toThrow(
876          /Invalid remove range diff op: Key out of range: Infinity/);
877
878        diff = [makeRemoveRange(NaN, 1)];
879        expect(() => {patch(base, diff)}).toThrow(
880          /Invalid remove range diff op: Key out of range: NaN/);
881      });
882
883      it('should fail to patch a sequence with a too long remove', () => {
884        let base = [55, 43];
885        let diff = [makeRemoveRange(0, 3)];
886        expect(() => {patch(base, diff)}).toThrow(
887          /Invalid remove range diff op: Range too long!/);
888
889        diff = [makeRemoveRange(1, 2)];
890        expect(() => {patch(base, diff)}).toThrow(
891          /Invalid remove range diff op: Range too long!/);
892      });
893
894      it('should fail to patch a sequence with a patch of an atomic value', () => {
895        let base = [55, 43];
896        let diff = [makePatch(0, [makeAdd('b', 22)])];
897        expect(() => {patch(base, diff)}).toThrow(
898          /Cannot patch an atomic type: number/);
899      });
900
901      it('should fail to patch a sequence with a patch of an invalid key', () => {
902        let base = [55, 43];
903        let diff = [makePatch(-1, [makeReplace('b', 22)])];
904        expect(() => {patch(base, diff)}).toThrow(
905          /Invalid patch diff op: Key out of range: -1/);
906
907        diff = [makePatch(2, [makeReplace('b', 22)])];
908        expect(() => {patch(base, diff)}).toThrow(
909          /Invalid patch diff op: Key out of range: 2/);
910
911        diff = [makePatch(Infinity, [makeReplace('b', 22)])];
912        expect(() => {patch(base, diff)}).toThrow(
913          /Invalid patch diff op: Key out of range: Infinity/);
914
915        diff = [makePatch(NaN, [makeReplace('b', 22)])];
916        expect(() => {patch(base, diff)}).toThrow(
917          /Invalid patch diff op: Key out of range: NaN/);
918      });
919
920      it('should fail to patch a sequence with an invalid diff op', () => {
921        let base = [55, 43];
922        let diff = [{op: 'typo', value: 22, key: 0}];
923        expect(() => {patch(base, diff as any)}).toThrow(/Invalid op: typo/);
924      });
925
926    });
927
928    describe('patch string', () => {
929
930
931      describe('patch string line addition', () => {
932
933        it('should patch a string line addition at start', () => {
934          let base = 'abc\ndef\ngh\n';
935          let diff = [makeAddRange(0, ['ij\n'])];
936          let expected = 'ij\nabc\ndef\ngh\n';
937          let value = patch(base, diff);
938          expect(value).toEqual(expected);
939        });
940
941        it('should patch a string line addition in middle', () => {
942          let base = 'abc\ndef\ngh\n';
943          let diff = [makeAddRange(1, ['ij\n'])];
944          let expected = 'abc\nij\ndef\ngh\n';
945          let value = patch(base, diff);
946          expect(value).toEqual(expected);
947        });
948
949        it('should patch a string addition at end', () => {
950          let base = 'abc\ndef\ngh\n';
951          let diff = [makeAddRange(3, ['ij\n'])];
952          let expected = 'abc\ndef\ngh\nij\n';
953          let value = patch(base, diff);
954          expect(value).toEqual(expected);
955
956          diff = [makeAddRange(3, ['ij'])];
957          expected = 'abc\ndef\ngh\nij';
958          value = patch(base, diff);
959          expect(value).toEqual(expected);
960        });
961      });
962
963      describe('patch string character addition', () => {
964
965        it('should patch a string character addition at start of first line', () => {
966          let base = 'abc\ndef\ngh\n';
967          let diff = [makePatch(0, [makeAddRange(0, ['ij'])])];
968          let expected = 'ijabc\ndef\ngh\n';
969          let value = patch(base, diff);
970          expect(value).toEqual(expected);
971        });
972
973        it('should patch a string character addition at end of first line', () => {
974          let base = 'abc\ndef\ngh\n';
975          let diff = [makePatch(0, [makeAddRange('abc'.length, ['ij'])])];
976          let expected = 'abcij\ndef\ngh\n';
977          let value = patch(base, diff);
978          expect(value).toEqual(expected);
979        });
980
981        it('should patch a string character addition at start of middle line', () => {
982          let base = 'abc\ndef\ngh\n';
983          let diff = [makePatch(1, [makeAddRange(0, ['ij'])])];
984          let expected = 'abc\nijdef\ngh\n';
985          let value = patch(base, diff);
986          expect(value).toEqual(expected);
987        });
988
989        it('should patch a string character addition at end of middle line', () => {
990          let base = 'abc\ndef\ngh\n';
991          let diff = [makePatch(1, [makeAddRange('def'.length, ['ij'])])];
992          let expected = 'abc\ndefij\ngh\n';
993          let value = patch(base, diff);
994          expect(value).toEqual(expected);
995        });
996
997        it('should patch a string character addition in last line', () => {
998          let base = 'abc\ndef\ngh\n';
999          let diff = [makePatch(3, [makeAddRange(0, ['ij'])])];
1000          let expected = 'abc\ndef\ngh\nij';
1001          let value = patch(base, diff);
1002          expect(value).toEqual(expected);
1003        });
1004
1005      });
1006
1007      describe('patch string line removal', () => {
1008
1009        it('should patch a string line removal at start', () => {
1010          let base = 'abc\ndef\ngh\n';
1011          let diff = [makeRemoveRange(0, 1)];
1012          let expected = 'def\ngh\n';
1013          let value = patch(base, diff);
1014          expect(value).toEqual(expected);
1015        });
1016
1017        it('should patch a string line removal in middle', () => {
1018          let base = 'abc\ndef\ngh\n';
1019          let diff = [makeRemoveRange(1, 1)];
1020          let expected = 'abc\ngh\n';
1021          let value = patch(base, diff);
1022          expect(value).toEqual(expected);
1023        });
1024
1025        it('should patch a string removal at end', () => {
1026          let base = 'abc\ndef\ngh\n';
1027          let diff = [makeRemoveRange(2, 1)];
1028          let expected = 'abc\ndef\n';
1029          let value = patch(base, diff);
1030          expect(value).toEqual(expected);
1031
1032          // Removing line 3 is for now a valid no-op
1033          // Instead, the diff should say that the newline at end of line 2
1034          // should be removed! Possibly we could make this explicit by an
1035          // exception / warning?
1036        });
1037      });
1038
1039      describe('patch string character removal', () => {
1040
1041        it('should patch a string character removal at start of first line', () => {
1042          let base = 'abc\ndef\ngh\n';
1043          let diff = [makePatch(0, [makeRemoveRange(1, 2)])];
1044          let expected = 'a\ndef\ngh\n';
1045          let value = patch(base, diff);
1046          expect(value).toEqual(expected);
1047        });
1048
1049        it('should patch a string character removal at end of first line', () => {
1050          let base = 'abc\ndef\ngh\n';
1051          let diff = [makePatch(0, [makeRemoveRange(1, 2)])];
1052          let expected = 'a\ndef\ngh\n';
1053          let value = patch(base, diff);
1054          expect(value).toEqual(expected);
1055
1056          // TODO: Is this really wanted behavior?
1057          diff = [makePatch(0, [makeRemoveRange(2, 2)])];
1058          expected = 'abdef\ngh\n';
1059          value = patch(base, diff);
1060          expect(value).toEqual(expected);
1061        });
1062
1063        it('should patch a string character removal at start of middle line', () => {
1064          let base = 'abc\ndef\ngh\n';
1065          let diff = [makePatch(1, [makeRemoveRange(0, 2)])];
1066          let expected = 'abc\nf\ngh\n';
1067          let value = patch(base, diff);
1068          expect(value).toEqual(expected);
1069        });
1070
1071        it('should patch a string character removal at end of middle line', () => {
1072          let base = 'abc\ndef\ngh\n';
1073          let diff = [makePatch(1, [makeRemoveRange(1, 2)])];
1074          let expected = 'abc\nd\ngh\n';
1075          let value = patch(base, diff);
1076          expect(value).toEqual(expected);
1077        });
1078
1079        it('should patch a string character removal in last line', () => {
1080          let base = 'abc\ndef\ngh\nij';
1081          let diff = [makePatch(3, [makeRemoveRange(0, 2)])];
1082          let expected = 'abc\ndef\ngh\n';
1083          let value = patch(base, diff);
1084          expect(value).toEqual(expected);
1085        });
1086
1087      });
1088
1089      it('should fail to patch a string with an add on invalid line key', () => {
1090        let base = 'abc\ndef\ngh\n';
1091        let diff = [makeAddRange(-1, ['ij\n'])];
1092        expect(() => {patch(base, diff)}).toThrow(
1093          /Invalid add range diff op: Key out of range: -1/);
1094
1095        diff = [makeAddRange(5, ['ij\n'])];
1096        expect(() => {patch(base, diff)}).toThrow(
1097          /Invalid add range diff op: Key out of range: 5/);
1098
1099        diff = [makeAddRange(Infinity, ['ij\n'])];
1100        expect(() => {patch(base, diff)}).toThrow(
1101          /Invalid add range diff op: Key out of range: Infinity/);
1102
1103        diff = [makeAddRange(NaN, ['ij\n'])];
1104        expect(() => {patch(base, diff)}).toThrow(
1105          /Invalid add range diff op: Key out of range: NaN/);
1106      });
1107
1108      it('should fail to patch a string with an add on invalid character key', () => {
1109        let base = 'abc\ndef\ngh\n';
1110        let diff = [makePatch(0, [makeAddRange(-1, ['ij'])])];
1111        expect(() => {patch(base, diff)}).toThrow(
1112          /Invalid add range diff op: Key out of range: -1/);
1113
1114        diff = [makePatch(0, [makeAddRange(5, ['ij'])])];
1115        expect(() => {patch(base, diff)}).toThrow(
1116          /Invalid add range diff op: Key out of range: 5/);
1117
1118        diff = [makePatch(0, [makeAddRange(Infinity, ['ij'])])];
1119        expect(() => {patch(base, diff)}).toThrow(
1120          /Invalid add range diff op: Key out of range: Infinity/);
1121
1122        diff = [makePatch(0, [makeAddRange(NaN, ['ij'])])];
1123        expect(() => {patch(base, diff)}).toThrow(
1124          /Invalid add range diff op: Key out of range: NaN/);
1125      });
1126
1127      it('should fail to patch a string with a remove on invalid line key', () => {
1128        let base = 'abc\ndef\ngh\n';
1129        let diff = [makeRemoveRange(-1, 1)];
1130        expect(() => {patch(base, diff)}).toThrow(
1131          /Invalid remove range diff op: Key out of range: -1/);
1132
1133        diff = [makeRemoveRange(4, 1)];
1134        expect(() => {patch(base, diff)}).toThrow(
1135          /Invalid remove range diff op: Key out of range: 4/);
1136
1137        diff = [makeRemoveRange(Infinity, 1)];
1138        expect(() => {patch(base, diff)}).toThrow(
1139          /Invalid remove range diff op: Key out of range: Infinity/);
1140
1141        diff = [makeRemoveRange(NaN, 1)];
1142        expect(() => {patch(base, diff)}).toThrow(
1143          /Invalid remove range diff op: Key out of range: NaN/);
1144      });
1145
1146      it('should fail to patch a string with a remove on invalid character key', () => {
1147        let base = 'abc\ndef\ngh\n';
1148        let diff = [makePatch(0, [makeRemoveRange(-1, 1)])];
1149        expect(() => {patch(base, diff)}).toThrow(
1150          /Invalid remove range diff op: Key out of range: -1/);
1151
1152        diff = [makePatch(0, [makeRemoveRange(4, 1)])];
1153        expect(() => {patch(base, diff)}).toThrow(
1154          /Invalid remove range diff op: Key out of range: 4/);
1155
1156        diff = [makePatch(0, [makeRemoveRange(Infinity, 1)])];
1157        expect(() => {patch(base, diff)}).toThrow(
1158          /Invalid remove range diff op: Key out of range: Infinity/);
1159
1160        diff = [makePatch(0, [makeRemoveRange(NaN, 1)])];
1161        expect(() => {patch(base, diff)}).toThrow(
1162          /Invalid remove range diff op: Key out of range: NaN/);
1163      });
1164
1165      it('should fail to patch a string with a too long line remove', () => {
1166        let base = 'abc\ndef\ngh\n';
1167        let diff = [makeRemoveRange(0, 5)];
1168        expect(() => {patch(base, diff)}).toThrow(
1169          /Invalid remove range diff op: Range too long!/);
1170
1171        diff = [makeRemoveRange(3, 2)];
1172        expect(() => {patch(base, diff)}).toThrow(
1173          /Invalid remove range diff op: Range too long!/);
1174      });
1175
1176      it('should fail to patch a string with a too long character remove', () => {
1177        let base = 'abc\ndef\ngh\n';
1178        let diff = [makePatch(0, [makeRemoveRange(0, 5)])];
1179        expect(() => {patch(base, diff)}).toThrow(
1180          /Invalid remove range diff op: Range too long!/);
1181
1182        // TODO: Should this exclude new-line?
1183        diff = [makePatch(0, [makeRemoveRange(3, 2)])];
1184        expect(() => {patch(base, diff)}).toThrow(
1185          /Invalid remove range diff op: Range too long!/);
1186      });
1187
1188      it('should fail to patch a string with a patch of an invalid line key', () => {
1189        let base = 'abc\ndef\ngh\n';
1190        let diff = [makePatch(-1, [makeAddRange(0, ['ij\n'])])];
1191        expect(() => {patch(base, diff)}).toThrow(
1192          /Invalid patch diff op: Key out of range: -1/);
1193
1194        diff = [makePatch(4, [makeAddRange(0, ['ij\n'])])];
1195        expect(() => {patch(base, diff)}).toThrow(
1196          /Invalid patch diff op: Key out of range: 4/);
1197
1198        diff = [makePatch(Infinity, [makeAddRange(0, ['ij\n'])])];
1199        expect(() => {patch(base, diff)}).toThrow(
1200          /Invalid patch diff op: Key out of range: Infinity/);
1201
1202        diff = [makePatch(NaN, [makeAddRange(0, ['ij\n'])])];
1203        expect(() => {patch(base, diff)}).toThrow(
1204          /Invalid patch diff op: Key out of range: NaN/);
1205      });
1206
1207      it('should fail to patch a string with an invalid line diff op', () => {
1208        let base = 'abc\ndef\ngh\n';
1209        let diff = [{op: 'typo', value: 22, key: 0}];
1210        expect(() => {patch(base, diff as any)}).toThrow(/Invalid op: typo/);
1211      });
1212
1213      it('should fail to patch a string with an invalid character diff op', () => {
1214        let base = 'abc\ndef\ngh\n';
1215        let diff = [makePatch(0, [{op: 'typo', value: 22, key: 0}] as any)];
1216        expect(() => {patch(base, diff)}).toThrow(/Invalid op: typo/);
1217      });
1218
1219    });
1220
1221  });
1222
1223});
1224