opentelemetry-js/packages/opentelemetry-core/test/utils/merge.test.ts

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;
}