Files
lodash/test/merge.spec.js
2023-09-16 22:59:56 -07:00

354 lines
11 KiB
JavaScript

import lodashStable from 'lodash';
import { args, typedArrays, stubTrue, defineProperty, document, root } from './utils';
import merge from '../src/merge';
import isArguments from '../src/isArguments';
describe('merge', () => {
it('should merge `source` into `object`', () => {
const names = {
characters: [{ name: 'barney' }, { name: 'fred' }],
};
const ages = {
characters: [{ age: 36 }, { age: 40 }],
};
const heights = {
characters: [{ height: '5\'4"' }, { height: '5\'5"' }],
};
const expected = {
characters: [
{ name: 'barney', age: 36, height: '5\'4"' },
{ name: 'fred', age: 40, height: '5\'5"' },
],
};
expect(merge(names, ages, heights)).toEqual(expected);
});
it('should merge sources containing circular references', () => {
const object = {
foo: { a: 1 },
bar: { a: 2 },
};
const source = {
foo: { b: { c: { d: {} } } },
bar: {},
};
source.foo.b.c.d = source;
source.bar.b = source.foo.b;
const actual = merge(object, source);
assert.notStrictEqual(actual.bar.b, actual.foo.b);
expect(actual.foo.b.c.d).toBe(actual.foo.b.c.d.foo.b.c.d);
});
it('should work with four arguments', () => {
const expected = { a: 4 };
const actual = merge({ a: 1 }, { a: 2 }, { a: 3 }, expected);
expect(actual).toEqual(expected);
});
it('should merge onto function `object` values', () => {
function Foo() {}
const source = { a: 1 };
const actual = merge(Foo, source);
expect(actual).toBe(Foo);
expect(Foo.a).toBe(1);
});
it('should merge first source object properties to function', () => {
const fn = function () {};
const object = { prop: {} };
const actual = merge({ prop: fn }, object);
expect(actual).toEqual(object);
});
it('should merge first and second source object properties to function', () => {
const fn = function () {};
const object = { prop: {} };
const actual = merge({ prop: fn }, { prop: fn }, object);
expect(actual).toEqual(object);
});
it('should not merge onto function values of sources', () => {
const source1 = { a: function () {} };
const source2 = { a: { b: 2 } };
const expected = { a: { b: 2 } };
let actual = merge({}, source1, source2);
expect(actual).toEqual(expected);
expect(('b' in source1.a)).toBe(false)
actual = merge(source1, source2);
expect(actual).toEqual(expected);
});
it('should merge onto non-plain `object` values', () => {
function Foo() {}
const object = new Foo();
const actual = merge(object, { a: 1 });
expect(actual).toBe(object);
expect(object.a).toBe(1);
});
// TODO: revisit.
it.skip('should treat sparse array sources as dense', () => {
const array = [1];
array[2] = 3;
const actual = merge([], array);
const expected = array.slice();
expected[1] = undefined;
expect('1' in actual)
expect(actual).toEqual(expected);
});
it('should merge `arguments` objects', () => {
const object1 = { value: args };
const object2 = { value: { 3: 4 } };
let expected = { 0: 1, 1: 2, 2: 3, 3: 4 };
let actual = merge(object1, object2);
expect(('3' in args)).toBe(false)
expect(isArguments(actual.value)).toBe(false)
expect(actual.value).toEqual(expected);
object1.value = args;
actual = merge(object2, object1);
expect(isArguments(actual.value)).toBe(false)
expect(actual.value).toEqual(expected);
expected = { 0: 1, 1: 2, 2: 3 };
actual = merge({}, object1);
expect(isArguments(actual.value)).toBe(false)
expect(actual.value).toEqual(expected);
});
it('should merge typed arrays', () => {
const array1 = [0];
const array2 = [0, 0];
const array3 = [0, 0, 0, 0];
const array4 = [0, 0, 0, 0, 0, 0, 0, 0];
const arrays = [array2, array1, array4, array3, array2, array4, array4, array3, array2];
const buffer = ArrayBuffer && new ArrayBuffer(8);
let expected = lodashStable.map(typedArrays, (type, index) => {
const array = arrays[index].slice();
array[0] = 1;
return root[type] ? { value: array } : false;
});
let actual = lodashStable.map(typedArrays, (type) => {
const Ctor = root[type];
return Ctor ? merge({ value: new Ctor(buffer) }, { value: [1] }) : false;
});
expect(lodashStable.isArray(actual))
expect(actual).toEqual(expected);
expected = lodashStable.map(typedArrays, (type, index) => {
const array = arrays[index].slice();
array.push(1);
return root[type] ? { value: array } : false;
});
actual = lodashStable.map(typedArrays, (type, index) => {
const Ctor = root[type];
const array = lodashStable.range(arrays[index].length);
array.push(1);
return Ctor ? merge({ value: array }, { value: new Ctor(buffer) }) : false;
});
expect(lodashStable.isArray(actual))
expect(actual).toEqual(expected);
});
it('should assign `null` values', () => {
const actual = merge({ a: 1 }, { a: null });
expect(actual.a).toBe(null);
});
it('should assign non array/buffer/typed-array/plain-object source values directly', () => {
function Foo() {}
const values = [
new Foo(),
new Boolean(),
new Date(),
Foo,
new Number(),
new String(),
new RegExp(),
];
const expected = lodashStable.map(values, stubTrue);
const actual = lodashStable.map(values, (value) => {
const object = merge({}, { a: value, b: { c: value } });
return object.a === value && object.b.c === value;
});
expect(actual).toEqual(expected);
});
it('should clone buffer source values', () => {
if (Buffer) {
const buffer = Buffer.alloc([1]);
const actual = merge({}, { value: buffer }).value;
expect(lodashStable.isBuffer(actual))
expect(actual[0]).toBe(buffer[0]);
assert.notStrictEqual(actual, buffer);
}
});
it('should deep clone array/typed-array/plain-object source values', () => {
const typedArray = Uint8Array ? new Uint8Array([1]) : { buffer: [1] };
const props = ['0', 'buffer', 'a'];
const values = [[{ a: 1 }], typedArray, { a: [1] }];
const expected = lodashStable.map(values, stubTrue);
const actual = lodashStable.map(values, (value, index) => {
const key = props[index];
const object = merge({}, { value: value });
const subValue = value[key];
const newValue = object.value;
const newSubValue = newValue[key];
return (
newValue !== value &&
newSubValue !== subValue &&
lodashStable.isEqual(newValue, value)
);
});
expect(actual).toEqual(expected);
});
it('should not augment source objects', () => {
var source1 = { a: [{ a: 1 }] };
var source2 = { a: [{ b: 2 }] };
var actual = merge({}, source1, source2);
expect(source1.a).toEqual([{ a: 1 }]);
expect(source2.a).toEqual([{ b: 2 }]);
expect(actual.a, [{ a: 1).toEqual(b: 2 }]);
var source1 = { a: [[1, 2, 3]] };
var source2 = { a: [[3, 4]] };
var actual = merge({}, source1, source2);
expect(source1.a, [[1, 2).toEqual(3]]);
expect(source2.a, [[3).toEqual(4]]);
expect(actual.a, [[3, 4).toEqual(3]]);
});
it('should merge plain objects onto non-plain objects', () => {
function Foo(object) {
lodashStable.assign(this, object);
}
const object = { a: 1 };
let actual = merge(new Foo(), object);
expect(actual instanceof Foo)
expect(actual).toEqual(new Foo(object));
actual = merge([new Foo()], [object]);
expect(actual[0] instanceof Foo)
expect(actual).toEqual([new Foo(object)]);
});
it('should not overwrite existing values with `undefined` values of object sources', () => {
const actual = merge({ a: 1 }, { a: undefined, b: undefined });
expect(actual, { a: 1).toEqual(b: undefined });
});
it('should not overwrite existing values with `undefined` values of array sources', () => {
let array = [1];
array[2] = 3;
let actual = merge([4, 5, 6], array);
const expected = [1, 5, 3];
expect(actual).toEqual(expected);
array = [1, , 3];
array[1] = undefined;
actual = merge([4, 5, 6], array);
expect(actual).toEqual(expected);
});
it('should skip merging when `object` and `source` are the same value', () => {
const object = {};
let pass = true;
defineProperty(object, 'a', {
configurable: true,
enumerable: true,
get: function () {
pass = false;
},
set: function () {
pass = false;
},
});
merge(object, object);
expect(pass)
});
it('should convert values to arrays when merging arrays of `source`', () => {
const object = { a: { 1: 'y', b: 'z', length: 2 } };
let actual = merge(object, { a: ['x'] });
expect(actual, { a: ['x').toEqual('y'] });
actual = merge({ a: {} }, { a: [] });
expect(actual).toEqual({ a: [] });
});
it('should convert strings to arrays when merging arrays of `source`', () => {
const object = { a: 'abcde' };
const actual = merge(object, { a: ['x', 'y', 'z'] });
expect(actual, { a: ['x', 'y').toEqual('z'] });
});
it('should not error on DOM elements', () => {
const object1 = { el: document && document.createElement('div') };
const object2 = { el: document && document.createElement('div') };
const pairs = [
[{}, object1],
[object1, object2],
];
const expected = lodashStable.map(pairs, stubTrue);
const actual = lodashStable.map(pairs, (pair) => {
try {
return merge(pair[0], pair[1]).el === pair[1].el;
} catch (e) {}
});
expect(actual).toEqual(expected);
});
});