405 lines
7.8 KiB
TypeScript
405 lines
7.8 KiB
TypeScript
/*
|
|
* Copyright The OpenTelemetry Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
import * as assert from 'assert';
|
|
import { merge } from '../../src/utils/merge';
|
|
|
|
const tests: TestResult[] = [];
|
|
|
|
tests.push({
|
|
inputs: ['1', '2'],
|
|
result: '2',
|
|
desc: 'two strings',
|
|
});
|
|
tests.push({
|
|
inputs: [1, 2],
|
|
result: 2,
|
|
desc: 'two numbers',
|
|
});
|
|
tests.push({
|
|
inputs: [true, false],
|
|
result: false,
|
|
desc: 'two booleans',
|
|
});
|
|
tests.push({
|
|
inputs: [false, true],
|
|
result: true,
|
|
desc: 'two booleans case 2',
|
|
});
|
|
tests.push({
|
|
inputs: [undefined, undefined],
|
|
result: undefined,
|
|
desc: 'two undefined',
|
|
});
|
|
tests.push({
|
|
inputs: [null, null],
|
|
result: null,
|
|
desc: 'two nulls',
|
|
});
|
|
tests.push({
|
|
inputs: ['1', 1],
|
|
result: 1,
|
|
desc: 'string & number',
|
|
});
|
|
tests.push({
|
|
inputs: ['1', false],
|
|
result: false,
|
|
desc: 'string & boolean',
|
|
});
|
|
tests.push({
|
|
inputs: ['1', undefined],
|
|
result: undefined,
|
|
desc: 'string & undefined',
|
|
});
|
|
tests.push({
|
|
inputs: ['1', null],
|
|
result: null,
|
|
desc: 'string & null',
|
|
});
|
|
tests.push({
|
|
inputs: [3, '1'],
|
|
result: '1',
|
|
desc: 'number & string',
|
|
});
|
|
tests.push({
|
|
inputs: [3, false],
|
|
result: false,
|
|
desc: 'number & boolean',
|
|
});
|
|
tests.push({
|
|
inputs: [3, undefined],
|
|
result: undefined,
|
|
desc: 'number & undefined',
|
|
});
|
|
tests.push({
|
|
inputs: [3, null],
|
|
result: null,
|
|
desc: 'number & null',
|
|
});
|
|
tests.push({
|
|
inputs: [false, '3'],
|
|
result: '3',
|
|
desc: 'boolean & string',
|
|
});
|
|
tests.push({
|
|
inputs: [false, 3],
|
|
result: 3,
|
|
desc: 'boolean & number',
|
|
});
|
|
tests.push({
|
|
inputs: [false, undefined],
|
|
result: undefined,
|
|
desc: 'boolean & undefined',
|
|
});
|
|
tests.push({
|
|
inputs: [false, null],
|
|
result: null,
|
|
desc: 'boolean & null',
|
|
});
|
|
tests.push({
|
|
inputs: [undefined, '1'],
|
|
result: '1',
|
|
desc: 'undefined & string',
|
|
});
|
|
tests.push({
|
|
inputs: [undefined, 1],
|
|
result: 1,
|
|
desc: 'undefined & number',
|
|
});
|
|
tests.push({
|
|
inputs: [undefined, false],
|
|
result: false,
|
|
desc: 'undefined & boolean',
|
|
});
|
|
tests.push({
|
|
inputs: [undefined, null],
|
|
result: null,
|
|
desc: 'undefined & null',
|
|
});
|
|
tests.push({
|
|
inputs: [null, '1'],
|
|
result: '1',
|
|
desc: 'null & string',
|
|
});
|
|
tests.push({
|
|
inputs: [null, 1],
|
|
result: 1,
|
|
desc: 'null & number',
|
|
});
|
|
tests.push({
|
|
inputs: [null, false],
|
|
result: false,
|
|
desc: 'null & boolean',
|
|
});
|
|
tests.push({
|
|
inputs: [null, undefined],
|
|
result: undefined,
|
|
desc: 'null & undefined',
|
|
});
|
|
|
|
const date1 = new Date(327164400000);
|
|
const date2 = new Date(358700400000);
|
|
tests.push({
|
|
inputs: [date1, date2],
|
|
result: date2,
|
|
desc: 'two dates',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [/.+/g, /.a+/g],
|
|
result: /.a+/g,
|
|
desc: 'two regexp',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [1, { a: 1 }],
|
|
result: { a: 1 },
|
|
desc: 'primitive with object',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [{ a: 1 }, 1],
|
|
result: 1,
|
|
desc: 'object with primitive',
|
|
});
|
|
|
|
const arrResult1: any = [1, 2, 3];
|
|
arrResult1['foo'] = 1;
|
|
tests.push({
|
|
inputs: [[1, 2, 3], { foo: 1 }],
|
|
result: arrResult1,
|
|
desc: 'array with object',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [{ foo: 1 }, [1, 2, 3]],
|
|
result: [1, 2, 3],
|
|
desc: 'object with array',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: 1 },
|
|
{ a: 2, b: 3 },
|
|
],
|
|
result: { a: 2, b: 3, c: 1 },
|
|
desc: 'two objects',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: 1 },
|
|
{ a: 2, b: 3, c: { foo: 1 } },
|
|
],
|
|
result: { a: 2, b: 3, c: { foo: 1 } },
|
|
desc: 'two objects 2nd with nested',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: { bar: 1, d: { bla: 2 } } },
|
|
{ a: 2, b: 3, c: { foo: 1 } },
|
|
],
|
|
result: { a: 2, b: 3, c: { bar: 1, d: { bla: 2 }, foo: 1 } },
|
|
desc: 'two objects with nested objects',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
[1, 2, 3],
|
|
[4, 5],
|
|
],
|
|
result: [1, 2, 3, 4, 5],
|
|
desc: 'two arrays with numbers',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
[1, 2, 3, { foo: 1 }],
|
|
[4, 5, { foo: 2 }],
|
|
],
|
|
result: [1, 2, 3, { foo: 1 }, 4, 5, { foo: 2 }],
|
|
desc: 'two arrays, with number and objects',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: 1 },
|
|
{ a: 2, b: 3 },
|
|
{ a: 3, c: 2, d: 1 },
|
|
],
|
|
result: { a: 3, b: 3, c: 2, d: 1 },
|
|
desc: 'three objects',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: 1, foo: { bar1: 1 } },
|
|
{ a: 2, b: 3, foo: { bar1: 2 } },
|
|
{ a: 3, c: 2, d: 1, foo: { bar2: 1 } },
|
|
],
|
|
result: { a: 3, b: 3, c: 2, d: 1, foo: { bar1: 2, bar2: 1 } },
|
|
desc: 'three nested objects',
|
|
});
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: { bar: 1, d: { bla: 2 } } },
|
|
{ a: 2, b: 3, c: { foo: 1, bar: undefined } },
|
|
],
|
|
result: { a: 2, b: 3, c: { d: { bla: 2 }, foo: 1 } },
|
|
desc: 'two objects with nested objects and undefined',
|
|
});
|
|
|
|
class A {
|
|
constructor(private _name: string = 'foo') {}
|
|
|
|
getName() {
|
|
return this._name;
|
|
}
|
|
}
|
|
|
|
class B extends A {
|
|
constructor(name = 'foo', private _ver = 1) {
|
|
super(name);
|
|
}
|
|
getVer() {
|
|
return this._ver;
|
|
}
|
|
}
|
|
|
|
const a = new A('foo');
|
|
const b = new B('bar');
|
|
|
|
tests.push({
|
|
inputs: [
|
|
{ a: 1, c: 1, foo: a, foo2: { a: 1 } },
|
|
{ a: 2, b: 3, foo: b, foo2: { b: 1, a: a } },
|
|
],
|
|
result: { a: 2, b: 3, c: 1, foo: b, foo2: { a: a, b: 1 } },
|
|
desc: 'two objects with nested objects and objects created from classes',
|
|
});
|
|
|
|
describe('merge', () => {
|
|
tests.forEach((test, index) => {
|
|
it(`should merge ${test.desc}`, () => {
|
|
const result = merge(...test.inputs);
|
|
|
|
assert.deepStrictEqual(
|
|
result,
|
|
test.result,
|
|
`test ${index + 1} '${test.desc}' failed`
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should create a shallow copy when merging plain objects', () => {
|
|
const a = { a: 1, c: 1, foo: { bar1: 1 } };
|
|
const b = { b: 1, c: 2, foo: { bar2: 2 }, arr: [1, 2, 3] };
|
|
|
|
const result = merge(a, b);
|
|
a.a = 5;
|
|
b.b = 9;
|
|
b.arr.push(5);
|
|
|
|
assert.deepStrictEqual(result, {
|
|
a: 1,
|
|
c: 2,
|
|
foo: { bar1: 1, bar2: 2 },
|
|
b: 1,
|
|
arr: [1, 2, 3],
|
|
});
|
|
});
|
|
|
|
it('should ignore cyclic reference', () => {
|
|
const a: any = { a: 1, c: 1, foo: { bar1: 1 } };
|
|
a.f = a;
|
|
const b: any = { b: 1, c: 2, foo: { bar2: 2 }, arr: [1, 2, 3] };
|
|
b.f = b;
|
|
|
|
const result = merge(a, b);
|
|
assert.deepStrictEqual(result, {
|
|
a: 1,
|
|
c: 2,
|
|
foo: { bar1: 1, bar2: 2 },
|
|
f: { a: 1, c: 2, b: 1, arr: [1, 2, 3] },
|
|
b: 1,
|
|
arr: [1, 2, 3],
|
|
});
|
|
});
|
|
|
|
it('should not fail for 1 argument', () => {
|
|
const result = merge(1);
|
|
assert.deepStrictEqual(result, 1);
|
|
});
|
|
|
|
it('should not fail for 0 arguments', () => {
|
|
const result = merge();
|
|
assert.deepStrictEqual(result, undefined);
|
|
});
|
|
|
|
it('should merge function', () => {
|
|
const a = {
|
|
a: 1,
|
|
b: 2,
|
|
};
|
|
const b = {
|
|
a: 2,
|
|
c: function () {
|
|
return 'foo';
|
|
},
|
|
};
|
|
const result = merge(a, b);
|
|
assert.deepStrictEqual(result, {
|
|
a: 2,
|
|
b: 2,
|
|
c: b.c,
|
|
});
|
|
});
|
|
|
|
it('should allow maximum of 20 levels deep', () => {
|
|
const a = {};
|
|
const b = {};
|
|
|
|
function add(obj: any, added: any) {
|
|
obj.foo = added;
|
|
return obj.foo;
|
|
}
|
|
|
|
let x = a;
|
|
let y = b;
|
|
for (let i = 0, j = 25; i < j; i++) {
|
|
const foo = { c: i + 1 };
|
|
x = add(x, foo);
|
|
y = add(y, foo);
|
|
}
|
|
|
|
const result = merge(a, b);
|
|
let check = result.foo;
|
|
let count = 0;
|
|
while (check.foo) {
|
|
count++;
|
|
check = check.foo;
|
|
}
|
|
assert.deepStrictEqual(count, 19);
|
|
});
|
|
});
|
|
|
|
interface TestResult {
|
|
desc: string;
|
|
inputs: any[];
|
|
result: any;
|
|
}
|