Move merge utility functions from utils to vendor/core
Paul Shen committed
Nov 5, 2013 at 16:56 UTC
0fed861424147a6f79f3d6431ed46b89779a0afe
6 files changed
-983
src/utils/__tests__/mergeFuncs-test.js
deleted
-983
@@ -1,983 +0,0 @@
1
-/**
2
- * Copyright 2013 Facebook, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * @emails react-core
17
- */
18
-
19
-"use strict";
20
-
21
-require('mock-modules')
22
- .dontMock('mergeHelpers')
23
- .dontMock('merge')
24
- .dontMock('mergeDeep')
25
- .dontMock('mergeInto')
26
- .dontMock('mergeDeepInto');
27
-
28
-var mergeHelpers;
29
-var merge;
30
-var mergeInto;
31
-var mergeDeep;
32
-var mergeDeepInto;
33
-
34
-var isTerminal;
35
-var MAX_MERGE_DEPTH;
36
-
37
-/**
38
- * Simple helper to traverse an object, calling the callback (cb) on every
39
- * nonTerminal node it finds. It stops once it reaches the maximum depth,
40
- * because we know that the merge functions shouldn't merge past that depth
41
- * anyways. If you don't "stop" once reaching the MAX_MERGE_DEPTH, there won't
42
- * be a practical way to traverse circular structures.
43
- */
44
-var traverseUntilLeaves = function(obj, cb, depth) {
45
- if ((depth || 0) >= MAX_MERGE_DEPTH) {
46
- return;
47
- }
48
- var key;
49
- if (!isTerminal(obj) && !Array.isArray(obj)) {
50
- cb(obj);
51
- for (key in obj) {
52
- if (!obj.hasOwnProperty(key)) {
53
- continue;
54
- }
55
- traverseUntilLeaves(obj[key], cb, (depth || 0) + 1);
56
- }
57
- }
58
-};
59
-
60
-/*
61
- * Checks that the structure of two merged objects are identical. Checks
62
- * MAX_LEVEL because we don't want to block other unit tests if I have an
63
- * infinite loop.
64
- */
65
-var MAX_LEVEL = 50;
66
-var structureEquals = function(one, two, levelParam) {
67
- var level = levelParam || 0;
68
- var oneKey;
69
- var twoKey;
70
- if (level > MAX_LEVEL) {
71
- throw new Error('structure equals computation explored too many levels');
72
- }
73
- if (isTerminal(one) && isTerminal(two)) {
74
- return one === two;
75
- } else if (!isTerminal(one) && !isTerminal(two)) {
76
- var allInOneAreInTwo = true;
77
- for (oneKey in one) {
78
- if (!one.hasOwnProperty(oneKey)) {
79
- continue;
80
- }
81
- if (!structureEquals(one[oneKey], two[oneKey], level + 1)) {
82
- allInOneAreInTwo = false;
83
- }
84
- }
85
- var allInTwoAreInOne = true;
86
- for (twoKey in two) {
87
- if (!two.hasOwnProperty(twoKey)) {
88
- continue;
89
- }
90
- if (!structureEquals(two[twoKey], one[twoKey], level + 1)) {
91
- allInTwoAreInOne = false;
92
- }
93
- }
94
- // Sure, the computations overlap a bit.
95
- return allInOneAreInTwo && allInTwoAreInOne;
96
- } else {
97
- return false;
98
- }
99
-};
100
-
101
-
102
-var makeCircular = function() {
103
- var obj = {};
104
- obj.self = obj;
105
- return obj;
106
-};
107
-
108
-
109
-
110
-/**
111
- * Not a symmetric function. Checks if there are any node references
112
- * (nonTerminal [non-string, non-null, etc]) that exist in inThis. See
113
- * sharesOnlyTerminalMemory.
114
- */
115
-var hasNoNodeReferencesIn = function(obj, inThis) {
116
- traverseUntilLeaves(obj, function(nonTerm) {
117
- traverseUntilLeaves(inThis, function(inThisNonTerm) {
118
- if (inThisNonTerm === nonTerm) {
119
- return false;
120
- }
121
- });
122
- });
123
- return true;
124
-};
125
-
126
-/**
127
- * Checks that two objects share no memory references with each other (except
128
- * possibly at the terminal nodes (such as strings etc)). This is used to test
129
- * isolation of memory. When certain merge operations occur we need to ensure
130
- * that we did not leak references across objects.
131
- */
132
-var sharesOnlyTerminalMemory = function(a, b) {
133
- return hasNoNodeReferencesIn(a, b) && hasNoNodeReferencesIn(b, a);
134
-};
135
-
136
-/**
137
- * Tests all of the merging functions. You provide it data that describes what
138
- * a deep merge looks like and what a shallow merge looks like. It will make
139
- * sure that various input/output contracts are adhered to, in addition to
140
- * ensuring that various guarantees about side-effects are upheld.
141
- * The test data must provide functions createOne/createTwo as a means to
142
- * create test data for objects one/two respsectively - each time these
143
- * functions must return pristine copies so that we can test for side effects
144
- * correctly.
145
- */
146
-var testAllMerges = function(testData) {
147
- var createOne = testData.createOne;
148
- var createTwo = testData.createTwo;
149
- var one;
150
- var two;
151
-
152
- /*
153
- * Test merge()
154
- */
155
- one = createOne();
156
- two = createTwo();
157
- if (testData.mergeShouldThrow) {
158
- expect(function() {
159
- merge(one, two);
160
- }).toThrow();
161
- } else {
162
- var mergeRes = merge(one, two);
163
- if (testData.shallowResultShouldBe) {
164
- expect(structureEquals(
165
- mergeRes,
166
- testData.shallowResultShouldBe)
167
- ).toBe(true);
168
- }
169
- // Ensure the result is not the exact same object memory reference.
170
- expect(mergeRes === one).toBe(false);
171
- }
172
-
173
- /*
174
- * Test mergeInto()
175
- */
176
- one = createOne();
177
- two = createTwo();
178
- if (testData.mergeIntoShouldThrow) {
179
- expect(function() {
180
- mergeInto(one, two);
181
- }).toThrow();
182
- } else {
183
- var mergeIntoRes = mergeInto(one, two);
184
- if (testData.shallowResultShouldBe) {
185
- expect(structureEquals(
186
- one,
187
- testData.shallowResultShouldBe)
188
- ).toBe(true);
189
- }
190
- /*
191
- * Ensure it's the exact same object reference
192
- * TODO: We should ensure that for every object path p in one points to
193
- * the original object reference in one before the mutation.
194
- */
195
- expect(mergeIntoRes === undefined).toBe(true);
196
- }
197
-
198
- /*
199
- * Test mergeDeep()
200
- */
201
- one = createOne();
202
- two = createTwo();
203
- if (testData.mergeDeepShouldThrow) {
204
- expect(function() {
205
- mergeDeep(one, two, testData.arrayStrategy);
206
- }).toThrow();
207
- } else {
208
- var mergeDeepRes = mergeDeep(one, two, testData.arrayStrategy);
209
- if (testData.deepResultShouldBe) {
210
- expect(structureEquals(
211
- mergeDeepRes,
212
- testData.deepResultShouldBe)
213
- ).toBe(true);
214
- }
215
- expect(sharesOnlyTerminalMemory(mergeDeepRes, one)).toBe(true);
216
- expect(sharesOnlyTerminalMemory(mergeDeepRes, two)).toBe(true);
217
- }
218
-
219
- /*
220
- * Test mergeDeepInto()
221
- */
222
- one = createOne();
223
- two = createTwo();
224
- if (testData.mergeDeepIntoShouldThrow) {
225
- expect(function() {
226
- mergeDeepInto(one, two, testData.arrayStrategy);
227
- }).toThrow();
228
- } else {
229
- var mergeDeepIntoRes = mergeDeepInto(one, two, testData.arrayStrategy);
230
- expect(mergeDeepIntoRes).toBe(undefined);
231
- if (testData.deepResultShouldBe) {
232
- expect(structureEquals(
233
- one,
234
- testData.deepResultShouldBe)
235
- ).toBe(true);
236
- }
237
- expect(mergeDeepIntoRes === undefined).toBe(true);
238
- expect(sharesOnlyTerminalMemory(one, two)).toBe(true);
239
- }
240
-
241
-};
242
-
243
-/**
244
- * ReactTextWithEntities is not a React component, but rather a helper function
245
- * that returns an array. You can use that array as a helper function when
246
- * constructing a structure.
247
- */
248
-describe('mergeFuncs', function() {
249
- beforeEach(function() {
250
- require('mock-modules').dumpCache();
251
- mergeHelpers = require('mergeHelpers');
252
- merge = require('merge');
253
- mergeInto = require('mergeInto');
254
- mergeDeep = require('mergeDeep');
255
- mergeDeepInto = require('mergeDeepInto');
256
- isTerminal = mergeHelpers.isTerminal;
257
- MAX_MERGE_DEPTH = mergeHelpers.MAX_MERGE_DEPTH;
258
- });
259
-
260
- var twoLevelsDeep = function() {
261
- return {
262
- shallowObject: {
263
- deepObject: {
264
- a: 'a',
265
- b: 'b'
266
- },
267
- deepObject2: {
268
- one: 'one',
269
- two: 'two'
270
- }
271
- }
272
- };
273
- };
274
-
275
- it('should not throw if first argument is circular', function() {
276
- testAllMerges({
277
- createOne: makeCircular,
278
- createTwo: function() { return {thisOneIsNotCircular: true}; },
279
- mergeShouldThrow: false,
280
- mergeIntoShouldThrow: false,
281
- mergeDeepShouldThrow: true,
282
- mergeDeepIntoShouldThrow: false
283
- });
284
- });
285
-
286
- it('should throw if second argument is circular', function() {
287
- testAllMerges({
288
- createOne: function() { return {thisOneIsNotCircular: true}; },
289
- createTwo: makeCircular,
290
- mergeShouldThrow: false,
291
- mergeIntoShouldThrow: false,
292
- mergeDeepShouldThrow: true,
293
- mergeDeepIntoShouldThrow: true
294
- });
295
- });
296
-
297
- it('should throw if something deep is circular in second param', function() {
298
- testAllMerges({
299
- createOne: function() { return {thisOneIsNotCircular: true}; },
300
- createTwo: function() {
301
- return {
302
- deeper: {
303
- deeper: makeCircular()
304
- }
305
- };
306
- },
307
- mergeShouldThrow: false,
308
- mergeIntoShouldThrow: false,
309
- mergeDeepShouldThrow: true,
310
- mergeDeepIntoShouldThrow: true
311
- });
312
- });
313
-
314
- it('should throw if something deep is circular in first param', function() {
315
- testAllMerges({
316
- createOne: function() {
317
- return {
318
- deeper: {
319
- deeper: makeCircular()
320
- }
321
- };
322
- },
323
- createTwo: function() { return {thisOneIsNotCircular: true}; },
324
- mergeShouldThrow: false,
325
- mergeIntoShouldThrow: false,
326
- mergeDeepShouldThrow: true,
327
- mergeDeepIntoShouldThrow: false
328
- });
329
- });
330
-
331
-
332
- it('should throw exceptions on invalid data/params', function() {
333
- testAllMerges({
334
- createOne: function() { return []; },
335
- createTwo: function() { return {}; },
336
- mergeShouldThrow: true,
337
- mergeIntoShouldThrow: true,
338
- mergeDeepShouldThrow: true,
339
- mergeDeepIntoShouldThrow: true
340
- });
341
- testAllMerges({
342
- createOne: function() { return {}; },
343
- createTwo: function() { return []; },
344
- mergeShouldThrow: true,
345
- mergeIntoShouldThrow: true,
346
- mergeDeepShouldThrow: true,
347
- mergeDeepIntoShouldThrow: true
348
- });
349
- testAllMerges({
350
- createOne: function() { return []; },
351
- createTwo: function() { return {hasDeepStructure: 'yes'}; },
352
- mergeShouldThrow: true,
353
- mergeIntoShouldThrow: true,
354
- mergeDeepShouldThrow: true,
355
- mergeDeepIntoShouldThrow: true
356
- });
357
- testAllMerges({
358
- createOne: function() { return {hasDeepStructure: 'yes'}; },
359
- createTwo: function() { return []; },
360
- mergeShouldThrow: true,
361
- mergeIntoShouldThrow: true,
362
- mergeDeepShouldThrow: true,
363
- mergeDeepIntoShouldThrow: true
364
- });
365
- testAllMerges({
366
- createOne: function() { return {hasDeepStructure: 'yes'}; },
367
- createTwo: function() { return []; },
368
- mergeShouldThrow: true,
369
- mergeIntoShouldThrow: true,
370
- mergeDeepShouldThrow: true,
371
- mergeDeepIntoShouldThrow: true
372
- });
373
- /*
374
- * mergeInto functions do not accept an empty first parameter. There would
375
- * be nothing to mutate!
376
- */
377
- testAllMerges({
378
- createOne: function() { return null; },
379
- createTwo: function() { return {field: 'yes'}; },
380
- mergeIntoShouldThrow: true,
381
- mergeDeepIntoShouldThrow: true,
382
- shallowResultShouldBe: {field: 'yes'},
383
- deepResultShouldBe: {field: 'yes'}
384
- });
385
- testAllMerges({
386
- createOne: function() { return undefined; },
387
- createTwo: function() { return {field: 'yes'}; },
388
- mergeIntoShouldThrow: true,
389
- mergeDeepIntoShouldThrow: true,
390
- shallowResultShouldBe: {field: 'yes'},
391
- deepResultShouldBe: {field: 'yes'}
392
- });
393
- // Every merge function accepts an empty second parameter.
394
- testAllMerges({
395
- createOne: function() { return {field: 'yes'}; },
396
- createTwo: function() { return null; },
397
- shallowResultShouldBe: {field: 'yes'},
398
- deepResultShouldBe: {field: 'yes'}
399
- });
400
- testAllMerges({
401
- createOne: function() { return null; },
402
- createTwo: function() { return null; },
403
- deepResultShouldBe: {},
404
- shallowResultShouldBe: {},
405
- mergeIntoShouldThrow: true,
406
- mergeDeepIntoShouldThrow: true
407
- });
408
- testAllMerges({
409
- createOne: function() { return {field: 'yes'}; },
410
- createTwo: function() { return undefined; },
411
- shallowResultShouldBe: {field: 'yes'},
412
- deepResultShouldBe: {field: 'yes'}
413
- });
414
- testAllMerges({
415
- createOne: function() { return 0; },
416
- createTwo: function() { return {field: 'yes'}; },
417
- mergeShouldThrow: true,
418
- mergeIntoShouldThrow: true,
419
- mergeDeepShouldThrow: true,
420
- mergeDeepIntoShouldThrow: true
421
- });
422
- /*
423
- * The fact that the second param is empty shouldn't change that we still
424
- * error on arrays.
425
- */
426
- testAllMerges({
427
- createOne: function() { return []; },
428
- createTwo: function() { return undefined; },
429
- mergeShouldThrow: true,
430
- mergeIntoShouldThrow: true,
431
- mergeDeepShouldThrow: true,
432
- mergeDeepIntoShouldThrow: true
433
- });
434
- testAllMerges({
435
- createOne: function() { return {field: 'yes'}; },
436
- createTwo: function() { return null; },
437
- shallowResultShouldBe: {field: 'yes'},
438
- deepResultShouldBe: {field: 'yes'}
439
- });
440
- });
441
-
442
-
443
- it('should merge very deep objects correctly.',
444
- function() {
445
- testAllMerges({
446
- createOne: function() {
447
- return {
448
- shouldBeClobberedByNull: {
449
- color: 'blue'
450
- },
451
- shouldGetTwoFieldsWhenDeepMerge: {
452
- firstField: { color: 'red' }
453
- },
454
- shouldRemainUndefined: undefined,
455
- shouldRemainFour: 4,
456
- shouldRemainZero: 0,
457
- shouldBeClobberedByObj: 0
458
- };
459
- },
460
- createTwo: function() {
461
- return {
462
- shouldBeClobberedByNull: null,
463
- shouldGetTwoFieldsWhenDeepMerge: {
464
- secondField: { color: 'black' }
465
- },
466
- shouldBeClobberedByObj: { thisObj: true }
467
- };
468
- },
469
- shallowResultShouldBe: {
470
- shouldBeClobberedByNull: null,
471
- // only gets one field when merged shallowly
472
- shouldGetTwoFieldsWhenDeepMerge: {
473
- secondField: { color: 'black' }
474
- },
475
- shouldRemainUndefined: undefined,
476
- shouldRemainFour: 4,
477
- shouldRemainZero: 0,
478
- shouldBeClobberedByObj: { thisObj: true }
479
- },
480
- deepResultShouldBe: {
481
- shouldRemainUndefined: undefined,
482
- shouldRemainFour: 4,
483
- shouldRemainZero: 0,
484
- shouldBeClobberedByNull: null,
485
- shouldGetTwoFieldsWhenDeepMerge: {
486
- firstField: { color: 'red' },
487
- secondField: { color: 'black' }
488
- },
489
- shouldBeClobberedByObj: { thisObj: true }
490
- }
491
- });
492
- }
493
- );
494
-
495
- it(
496
- 'should not throw with one non-colliding arrays with no arrayStrategy',
497
- function() {
498
- testAllMerges({
499
- createOne: function() { return { hasDeepStructure: 'yes' }; },
500
- createTwo: function() { return { hasArrayDeeper: [] }; },
501
- mergeShouldThrow: false,
502
- mergeIntoShouldThrow: false,
503
- mergeDeepShouldThrow: false,
504
- mergeDeepIntoShouldThrow: false,
505
- shallowResultShouldBe: {
506
- hasDeepStructure: 'yes',
507
- hasArrayDeeper: []
508
- },
509
- deepResultShouldBe: {
510
- hasDeepStructure: 'yes',
511
- hasArrayDeeper: []
512
- }
513
- });
514
- }
515
- );
516
-
517
- it(
518
- 'should not throw with two non-colliding arrays with no arrayStrategy',
519
- function() {
520
- testAllMerges({
521
- createOne: function() {
522
- return {
523
- arrayOne: [1]
524
- };
525
- },
526
- createTwo: function() {
527
- return {
528
- arrayTwo: [2]
529
- };
530
- },
531
- arrayStrategy: mergeHelpers.ArrayStrategies.Clobber,
532
- mergeShouldThrow: false,
533
- mergeIntoShouldThrow: false,
534
- mergeDeepShouldThrow: false,
535
- mergeDeepIntoShouldThrow: false
536
- });
537
- }
538
- ),
539
-
540
- it('should throw if no arrayStrategy supplied on Array merges', function() {
541
- testAllMerges({
542
- createOne: function() {
543
- return {
544
- arrayOne: [1]
545
- };
546
- },
547
- createTwo: function() {
548
- return {
549
- arrayOne: [9]
550
- };
551
- },
552
- shallowResultShouldBe: {
553
- arrayOne: [9]
554
- },
555
- // Doesn't throw on the shallow versions.
556
- mergeShouldThrow: false,
557
- mergeIntoShouldThrow: false,
558
- mergeDeepShouldThrow: true,
559
- mergeDeepIntoShouldThrow: true
560
- });
561
- }),
562
-
563
- it('should not throw if never forced to merge two Arrays', function() {
564
- testAllMerges({
565
- createOne: function() {
566
- return {
567
- arrayOne: [1]
568
- };
569
- },
570
- createTwo: function() {
571
- return {
572
- arrayTwo: [9] // <-- See, never forced to merge with arrayOne
573
- };
574
- },
575
- shallowResultShouldBe: {
576
- arrayOne: [1],
577
- arrayTwo: [9]
578
- },
579
- // Doesn't throw on the shallow versions.
580
- mergeShouldThrow: false,
581
- mergeIntoShouldThrow: false,
582
- mergeDeepShouldThrow: false,
583
- mergeDeepIntoShouldThrow: false
584
- });
585
- }),
586
-
587
- it(
588
- 'should correctly deep merge Arrays with arrayStrategy clobber',
589
- function() {
590
- testAllMerges({
591
- createOne: function() {
592
- return {
593
- deep: {
594
- array: [1],
595
- nonArray: {
596
- hi: 'hi'
597
- }
598
- }
599
- };
600
- },
601
- createTwo: function() {
602
- return {
603
- deep: {
604
- array: [10,11, {deepFieldInArray: 'yes'}],
605
- nonArray: {
606
- bye: 'bye'
607
- }
608
- }
609
- };
610
- },
611
- shallowResultShouldBe: {
612
- deep: {
613
- array: [10,11, {deepFieldInArray: 'yes'}],
614
- nonArray: {
615
- bye: 'bye'
616
- }
617
- }
618
- },
619
- deepResultShouldBe: {
620
- deep: {
621
- array: [10,11, {deepFieldInArray: 'yes'}],
622
- nonArray: {
623
- hi: 'hi',
624
- bye: 'bye'
625
- }
626
- }
627
- },
628
- arrayStrategy: mergeHelpers.ArrayStrategies.Clobber,
629
- mergeShouldThrow: false,
630
- mergeIntoShouldThrow: false,
631
- mergeDeepShouldThrow: false,
632
- mergeDeepIntoShouldThrow: false
633
- });
634
- }
635
- ),
636
-
637
-
638
- it(
639
- 'should correctly deep merge Arrays with arrayStrategy indexByIndex',
640
- function() {
641
- testAllMerges({
642
- createOne: function() {
643
- return {
644
- deep: {
645
- array: [{
646
- atIndexZero: {
647
- indexZeroGreeting: 'You Are At Index Zero',
648
- indexZeroStatus: false
649
- }
650
- }]
651
- }
652
- };
653
- },
654
- createTwo: function() {
655
- return {
656
- deep: {
657
- array: [{
658
- atIndexZero: {
659
- indexZeroStatus: true,
660
- newFieldAtIndexZero: 'newField'
661
- }
662
- },
663
- {
664
- atIndexOne: {
665
- nothingOriginallyAtThisIndex: true
666
- }
667
- }]
668
- }
669
- };
670
- },
671
- shallowResultShouldBe: {
672
- deep: {
673
- array: [{
674
- atIndexZero: {
675
- indexZeroStatus: true,
676
- newFieldAtIndexZero: 'newField'
677
- }
678
- },
679
- {
680
- atIndexOne: {
681
- nothingOriginallyAtThisIndex: true
682
- }
683
- }]
684
- }
685
- },
686
- deepResultShouldBe: {
687
- deep: {
688
- array: [{
689
- atIndexZero: {
690
- indexZeroGreeting: 'You Are At Index Zero', // <-- See!?
691
- indexZeroStatus: true,
692
- newFieldAtIndexZero: 'newField'
693
- }
694
- },
695
- {
696
- atIndexOne: {
697
- nothingOriginallyAtThisIndex: true
698
- }
699
- }]
700
- }
701
- },
702
- arrayStrategy: mergeHelpers.ArrayStrategies.IndexByIndex,
703
- mergeShouldThrow: false,
704
- mergeIntoShouldThrow: false,
705
- mergeDeepShouldThrow: false,
706
- mergeDeepIntoShouldThrow: false
707
- }
708
- );
709
- }),
710
-
711
- it('should merge deep data correctly', function() {
712
- var mergeData = {
713
- shallowObject: {
714
- deepObject: {
715
- c: 'c',
716
- d: 'd'
717
- },
718
- deepObject2: {
719
- three: 'three',
720
- four: 'four'
721
- }
722
- }
723
- };
724
- var deepResult = {
725
- shallowObject: {
726
- deepObject: {
727
- a: 'a',
728
- b: 'b',
729
- c: 'c',
730
- d: 'd'
731
- },
732
- deepObject2: {
733
- one: 'one',
734
- two: 'two',
735
- three: 'three',
736
- four: 'four'
737
- }
738
- }
739
- };
740
- var shallowResult = mergeData; // The same
741
-
742
- testAllMerges({
743
- createOne: function() { return twoLevelsDeep(); },
744
- createTwo: function() { return mergeData; },
745
- mergeShouldThrow: false,
746
- mergeIntoShouldThrow: false,
747
- mergeDeepShouldThrow: false,
748
- mergeDeepIntoShouldThrow: false,
749
- deepResultShouldBe: deepResult,
750
- shallowResultShouldBe: shallowResult
751
- });
752
- });
753
-
754
- it('should fill in complementing regions of data trees', function() {
755
- var mergeData = {
756
- shallowObject: {
757
- newDepth: {
758
- hello: 'goodbye'
759
- }
760
- }
761
- };
762
- var deepResult = {
763
- shallowObject: {
764
- deepObject: {
765
- a: 'a',
766
- b: 'b'
767
- },
768
- deepObject2: {
769
- one: 'one',
770
- two: 'two'
771
- },
772
- // This is added!
773
- newDepth: {
774
- hello: 'goodbye'
775
- }
776
- }
777
- };
778
- var shallowResult = mergeData; // The same
779
-
780
- testAllMerges({
781
- createOne: function() { return twoLevelsDeep(); },
782
- createTwo: function() { return mergeData; },
783
- // The deep merge of the two
784
- deepResultShouldBe: deepResult,
785
- shallowResultShouldBe: shallowResult
786
- });
787
- });
788
-
789
- /**
790
- * Arrays and Objects wipe out terminals (and vice versa).
791
- */
792
- it('should clobber terminals with objects/arrays vice versa', function() {
793
- var makeOne = function() {
794
- return {
795
- thisFieldDoesNothing: {},
796
- deep: {
797
- // Objects
798
- thisShouldSwitchFromObjectToTerminal: {hello: 'goodbye'},
799
- thisShouldSwitchFromTerminalToObject: 'yes',
800
-
801
- // Arrays
802
- thisShouldSwitchFromArrayToTerminal: ['goodbye'],
803
- thisShouldSwitchFromTerminalToArray: 'yes'
804
- }
805
- };
806
- };
807
- var makeTwo = function() {
808
- return {
809
- deep: {
810
- // Objects
811
- thisShouldSwitchFromObjectToTerminal: 'terminal',
812
- thisShouldSwitchFromTerminalToObject: {nowItsAnObject: 'yay'},
813
-
814
- // Arrays
815
- thisShouldSwitchFromArrayToTerminal: 'terminal',
816
- thisShouldSwitchFromTerminalToArray: ['yay']
817
- }
818
- };
819
- };
820
- var deepResult = {
821
- thisFieldDoesNothing: {
822
-
823
- },
824
- deep: {
825
- // Objects
826
- thisShouldSwitchFromObjectToTerminal: 'terminal',
827
- thisShouldSwitchFromTerminalToObject: {nowItsAnObject: 'yay'},
828
-
829
- // Arrays
830
- thisShouldSwitchFromArrayToTerminal: 'terminal',
831
- thisShouldSwitchFromTerminalToArray: ['yay']
832
- }
833
- };
834
-
835
- testAllMerges({
836
- createOne: makeOne,
837
- createTwo: makeTwo,
838
- deepResultShouldBe: deepResult, // The deep merge of the two
839
- shallowResultShouldBe: deepResult // The same this time.
840
- });
841
- });
842
-
843
- /**
844
- * Arrays wipe out Objects and Objects wipe out Arrays.
845
- */
846
- it('should clobber terminals with objects/arrays vice versa', function() {
847
- var makeOne = function() {
848
- return {
849
- thisFieldDoesNothing: {},
850
- deep: {
851
- thisShouldSwitchFromObjectToArray: {hello: 'goodbye'},
852
- thisShouldSwitchFromArrayToObject: [1,2,3]
853
- }
854
- };
855
- };
856
- var makeTwo = function() {
857
- return {
858
- deep: {
859
- thisShouldSwitchFromObjectToArray: [7,8,9],
860
- thisShouldSwitchFromArrayToObject: {iUsedToBeAnArray:true}
861
- }
862
- };
863
- };
864
- var deepResult = {
865
- thisFieldDoesNothing: {},
866
- deep: {
867
- thisShouldSwitchFromObjectToArray: [7,8,9],
868
- thisShouldSwitchFromArrayToObject: {iUsedToBeAnArray:true}
869
- }
870
- };
871
-
872
- testAllMerges({
873
- createOne: makeOne,
874
- createTwo: makeTwo,
875
- deepResultShouldBe: deepResult, // The deep merge of the two
876
- shallowResultShouldBe: deepResult // The same this time.
877
- });
878
- });
879
-
880
- it(
881
- 'should not clobber terminals/objects/arrays that are not over-written',
882
- function() {
883
- testAllMerges({
884
- createOne: function() {
885
- return {
886
- deep: {
887
- terminal: 'I am a terminal',
888
- object: {iAmAnObject: true},
889
- array: [1,2,3]
890
- }
891
- };
892
- },
893
- createTwo: function() {
894
- return {
895
- deep: {
896
- completelyDifferentField: 'no collision here!'
897
- }
898
- };
899
- },
900
- shallowResultShouldBe: {
901
- deep: {
902
- completelyDifferentField: 'no collision here!'
903
- }
904
- },
905
- deepResultShouldBe: {
906
- deep: {
907
- completelyDifferentField: 'no collision here!',
908
- terminal: 'I am a terminal',
909
- object: {iAmAnObject: true},
910
- array: [1,2,3]
911
- }
912
- },
913
- mergeShouldThrow: false,
914
- mergeIntoShouldThrow: false,
915
- mergeDeepShouldThrow: false,
916
- mergeDeepIntoShouldThrow: false
917
- }
918
- );
919
- }),
920
-
921
- it('should correctly clobber terminals with cloned arrays', function() {
922
- testAllMerges({
923
- createOne: function() {
924
- return {
925
- deep: {
926
- changesFromTermToArray: 'I am a terminal'
927
- }
928
- };
929
- },
930
- createTwo: function() {
931
- return {
932
- deep: {
933
- changesFromTermToArray: ['I', 'am', 'an', 'array']
934
- }
935
- };
936
- },
937
- shallowResultShouldBe: {
938
- deep: {
939
- changesFromTermToArray: ['I', 'am', 'an', 'array']
940
- }
941
- },
942
- deepResultShouldBe: {
943
- deep: {
944
- changesFromTermToArray: ['I', 'am', 'an', 'array']
945
- }
946
- },
947
- mergeShouldThrow: false,
948
- mergeIntoShouldThrow: false,
949
- mergeDeepShouldThrow: false,
950
- mergeDeepIntoShouldThrow: false
951
- });
952
- }),
953
-
954
- it('should detect terminals correctly in merge* functions', function() {
955
- var boolResFalse = isTerminal(false);
956
- expect(boolResFalse).toBe(true);
957
-
958
- var boolResTrue = isTerminal(true);
959
- expect(boolResTrue).toBe(true);
960
-
961
- var numRes = isTerminal(123);
962
- expect(numRes).toBe(true);
963
-
964
- var zeroRes = isTerminal(0);
965
- expect(zeroRes).toBe(true);
966
-
967
- var stringRes = isTerminal('i am a string');
968
- expect(stringRes).toBe(true);
969
-
970
- var objLiteralRes = isTerminal({somethingHere: true});
971
- expect(objLiteralRes).toBe(false);
972
-
973
- var undefRes = isTerminal(undefined);
974
- expect(undefRes).toBe(true);
975
-
976
- var nullRes = isTerminal(null);
977
- expect(nullRes).toBe(true);
978
-
979
- var arrRes = isTerminal([]);
980
- expect(arrRes).toBe(false);
981
- });
982
-});
983
-