n8n/packages/workflow/test/AugmentObject.test.ts

577 lines
12 KiB
TypeScript

import type { IDataObject } from '@/Interfaces';
import { augmentArray, augmentObject } from '@/AugmentObject';
import { deepCopy } from '@/utils';
describe('AugmentObject', () => {
describe('augmentArray', () => {
test('should work with arrays', () => {
const originalObject = [1, 2, 3, 4, null];
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentArray(originalObject);
expect(augmentedObject.push(5)).toEqual(6);
expect(augmentedObject).toEqual([1, 2, 3, 4, null, 5]);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.pop()).toEqual(5);
expect(augmentedObject).toEqual([1, 2, 3, 4, null]);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.shift()).toEqual(1);
expect(augmentedObject).toEqual([2, 3, 4, null]);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.unshift(1)).toEqual(5);
expect(augmentedObject).toEqual([1, 2, 3, 4, null]);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.splice(1, 1)).toEqual([2]);
expect(augmentedObject).toEqual([1, 3, 4, null]);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.slice(1)).toEqual([3, 4, null]);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.reverse()).toEqual([null, 4, 3, 1]);
expect(originalObject).toEqual(copyOriginal);
});
test('should work with arrays on any level', () => {
const originalObject = {
a: {
b: {
c: [
{
a3: {
b3: {
c3: '03' as string | null,
},
},
aa3: '01',
},
{
a3: {
b3: {
c3: '13',
},
},
aa3: '11',
},
],
},
},
aa: [
{
a3: {
b3: '2',
},
aa3: '1',
},
],
};
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentObject(originalObject);
// On first level
augmentedObject.aa[0].a3.b3 = '22';
expect(augmentedObject.aa[0].a3.b3).toEqual('22');
expect(originalObject.aa[0].a3.b3).toEqual('2');
// Make sure that also array operations as push and length work as expected
// On lower levels
augmentedObject.a.b.c[0].a3.b3.c3 = '033';
expect(augmentedObject.a.b.c[0].a3.b3.c3).toEqual('033');
expect(originalObject.a.b.c[0].a3.b3.c3).toEqual('03');
augmentedObject.a.b.c[1].a3.b3.c3 = '133';
expect(augmentedObject.a.b.c[1].a3.b3.c3).toEqual('133');
expect(originalObject.a.b.c[1].a3.b3.c3).toEqual('13');
augmentedObject.a.b.c.push({
a3: {
b3: {
c3: '23',
},
},
aa3: '21',
});
augmentedObject.a.b.c[2].a3.b3.c3 = '233';
expect(augmentedObject.a.b.c[2].a3.b3.c3).toEqual('233');
augmentedObject.a.b.c[2].a3.b3.c3 = '2333';
expect(augmentedObject.a.b.c[2].a3.b3.c3).toEqual('2333');
augmentedObject.a.b.c[2].a3.b3.c3 = null;
expect(augmentedObject.a.b.c[2].a3.b3.c3).toEqual(null);
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject.a.b.c.length).toEqual(3);
expect(augmentedObject.aa).toEqual([
{
a3: {
b3: '22',
},
aa3: '1',
},
]);
expect(augmentedObject.a.b.c).toEqual([
{
a3: {
b3: {
c3: '033',
},
},
aa3: '01',
},
{
a3: {
b3: {
c3: '133',
},
},
aa3: '11',
},
{
a3: {
b3: {
c3: null,
},
},
aa3: '21',
},
]);
expect(augmentedObject).toEqual({
a: {
b: {
c: [
{
a3: {
b3: {
c3: '033',
},
},
aa3: '01',
},
{
a3: {
b3: {
c3: '133',
},
},
aa3: '11',
},
{
a3: {
b3: {
c3: null,
},
},
aa3: '21',
},
],
},
},
aa: [
{
a3: {
b3: '22',
},
aa3: '1',
},
],
});
expect(originalObject).toEqual(copyOriginal);
});
});
describe('augmentObject', () => {
test('should work with simple values on first level', () => {
const date = new Date(1680089084200);
const regexp = new RegExp('^test$', 'ig');
const originalObject: IDataObject = {
1: 11,
2: '22',
a: 111,
b: '222',
d: date,
r: regexp,
};
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentObject(originalObject);
augmentedObject[1] = 911;
expect(originalObject[1]).toEqual(11);
expect(augmentedObject[1]).toEqual(911);
augmentedObject[2] = '922';
expect(originalObject[2]).toEqual('22');
expect(augmentedObject[2]).toEqual('922');
augmentedObject.a = 9111;
expect(originalObject.a).toEqual(111);
expect(augmentedObject.a).toEqual(9111);
augmentedObject.b = '9222';
expect(originalObject.b).toEqual('222');
expect(augmentedObject.b).toEqual('9222');
augmentedObject.c = 3;
expect({ ...originalObject, d: date.toJSON(), r: {} }).toEqual(copyOriginal);
expect(augmentedObject).toEqual({
1: 911,
2: '922',
a: 9111,
b: '9222',
c: 3,
d: date.toJSON(),
r: regexp.toString(),
});
});
test('should work with simple values on sub-level', () => {
const originalObject = {
a: {
b: {
cc: '3',
},
bb: '2',
},
aa: '1',
};
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentObject(originalObject);
augmentedObject.a.bb = '92';
expect(originalObject.a.bb).toEqual('2');
expect(augmentedObject.a.bb).toEqual('92');
augmentedObject.a.b.cc = '93';
expect(originalObject.a.b.cc).toEqual('3');
expect(augmentedObject.a.b.cc).toEqual('93');
// @ts-ignore
augmentedObject.a.b.ccc = {
d: '4',
};
// @ts-ignore
expect(augmentedObject.a.b.ccc).toEqual({ d: '4' });
// @ts-ignore
augmentedObject.a.b.ccc.d = '94';
// @ts-ignore
expect(augmentedObject.a.b.ccc.d).toEqual('94');
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject).toEqual({
a: {
b: {
cc: '93',
ccc: {
d: '94',
},
},
bb: '92',
},
aa: '1',
});
});
test('should work with complex values on first level', () => {
const originalObject = {
a: {
b: {
cc: '3',
c2: null,
},
bb: '2',
},
aa: '1',
};
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentObject(originalObject);
augmentedObject.a = { new: 'NEW' };
expect(originalObject.a).toEqual({
b: {
c2: null,
cc: '3',
},
bb: '2',
});
expect(augmentedObject.a).toEqual({ new: 'NEW' });
augmentedObject.aa = '11';
expect(originalObject.aa).toEqual('1');
expect(augmentedObject.aa).toEqual('11');
augmentedObject.aaa = {
bbb: {
ccc: '333',
},
};
expect(originalObject).toEqual(copyOriginal);
expect(augmentedObject).toEqual({
a: {
new: 'NEW',
},
aa: '11',
aaa: {
bbb: {
ccc: '333',
},
},
});
});
test('should work with delete and reset', () => {
const originalObject = {
a: {
b: {
c: {
d: '4' as string | undefined,
} as { d?: string; dd?: string } | undefined,
cc: '3' as string | undefined,
},
bb: '2' as string | undefined,
},
aa: '1' as string | undefined,
};
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentObject(originalObject);
// Remove multiple values
delete augmentedObject.a.b.c!.d;
expect(augmentedObject.a.b.c!.d).toEqual(undefined);
expect(originalObject.a.b.c!.d).toEqual('4');
expect(augmentedObject).toEqual({
a: {
b: {
c: {},
cc: '3',
},
bb: '2',
},
aa: '1',
});
expect(originalObject).toEqual(copyOriginal);
delete augmentedObject.a.b.c;
expect(augmentedObject.a.b.c).toEqual(undefined);
expect(originalObject.a.b.c).toEqual({ d: '4' });
expect(augmentedObject).toEqual({
a: {
b: {
cc: '3',
},
bb: '2',
},
aa: '1',
});
expect(originalObject).toEqual(copyOriginal);
// Set deleted values again
augmentedObject.a.b.c = { dd: '444' };
expect(augmentedObject.a.b.c).toEqual({ dd: '444' });
expect(originalObject).toEqual(copyOriginal);
augmentedObject.a.b.c.d = '44';
expect(augmentedObject).toEqual({
a: {
b: {
c: {
d: '44',
dd: '444',
},
cc: '3',
},
bb: '2',
},
aa: '1',
});
expect(originalObject).toEqual(copyOriginal);
});
// Is almost identical to above test
test('should work with setting to undefined and reset', () => {
const originalObject = {
a: {
b: {
c: {
d: '4' as string | undefined,
} as { d?: string; dd?: string } | undefined,
cc: '3' as string | undefined,
},
bb: '2' as string | undefined,
},
aa: '1' as string | undefined,
};
const copyOriginal = deepCopy(originalObject);
const augmentedObject = augmentObject(originalObject);
// Remove multiple values
augmentedObject.a.b.c!.d = undefined;
expect(augmentedObject.a.b.c!.d).toEqual(undefined);
expect(originalObject.a.b.c!.d).toEqual('4');
expect(augmentedObject).toEqual({
a: {
b: {
c: {},
cc: '3',
},
bb: '2',
},
aa: '1',
});
expect(originalObject).toEqual(copyOriginal);
augmentedObject.a.b.c = undefined;
expect(augmentedObject.a.b.c).toEqual(undefined);
expect(originalObject.a.b.c).toEqual({ d: '4' });
expect(augmentedObject).toEqual({
a: {
b: {
cc: '3',
},
bb: '2',
},
aa: '1',
});
expect(originalObject).toEqual(copyOriginal);
// Set deleted values again
augmentedObject.a.b.c = { dd: '444' };
expect(augmentedObject.a.b.c).toEqual({ dd: '444' });
expect(originalObject).toEqual(copyOriginal);
augmentedObject.a.b.c.d = '44';
expect(augmentedObject).toEqual({
a: {
b: {
c: {
d: '44',
dd: '444',
},
cc: '3',
},
bb: '2',
},
aa: '1',
});
expect(originalObject).toEqual(copyOriginal);
});
test('should be faster than doing a deepCopy', () => {
const iterations = 100;
const originalObject: IDataObject = {
a: {
b: {
c: {
d: {
e: {
f: 12345,
},
},
},
},
},
};
for (let i = 0; i < 10; i++) {
originalObject[i.toString()] = deepCopy(originalObject);
}
let startTime = new Date().getTime();
for (let i = 0; i < iterations; i++) {
const augmentedObject = augmentObject(originalObject);
for (let i = 0; i < 5000; i++) {
augmentedObject.a!.b.c.d.e.f++;
}
}
const timeAugmented = new Date().getTime() - startTime;
startTime = new Date().getTime();
for (let i = 0; i < iterations; i++) {
const copiedObject = deepCopy(originalObject);
for (let i = 0; i < 5000; i++) {
copiedObject.a!.b.c.d.e.f++;
}
}
const timeCopied = new Date().getTime() - startTime;
expect(timeAugmented).toBeLessThan(timeCopied);
});
test('should ignore non-enumerable keys', () => {
const originalObject = { a: 1, b: 2 };
Object.defineProperty(originalObject, '__hiddenProp', { enumerable: false });
const augmentedObject = augmentObject(originalObject);
expect(Object.keys(augmentedObject)).toEqual(['a', 'b']);
});
test('should return property descriptors', () => {
const originalObject = {
x: {
y: {},
z: {},
},
};
const augmentedObject = augmentObject(originalObject);
expect(Object.getOwnPropertyDescriptor(augmentedObject.x, 'y')).toEqual({
configurable: true,
enumerable: true,
value: {},
writable: true,
});
delete augmentedObject.x.y;
expect(augmentedObject.x.hasOwnProperty('y')).toEqual(false);
augmentedObject.x.y = 42;
expect(augmentedObject.x.hasOwnProperty('y')).toEqual(true);
expect(Object.getOwnPropertyDescriptor(augmentedObject.x, 'y')).toEqual({
configurable: true,
enumerable: true,
value: 42,
writable: true,
});
});
test('should return valid values on `has` calls', () => {
const originalObject = {
x: {
y: {},
},
};
const augmentedObject = augmentObject(originalObject);
expect('y' in augmentedObject.x).toBe(true);
expect('z' in augmentedObject.x).toBe(false);
augmentedObject.x.z = 5;
expect('z' in augmentedObject.x).toBe(true);
expect('y' in augmentedObject.x).toBe(true);
});
});
});