8b4b9ef8da
Forcing "type" to be explicit, works best will rollup on the frontend to exclude depdendencies
334 lines
8.6 KiB
TypeScript
334 lines
8.6 KiB
TypeScript
import { type EachTestingContext } from '@/testing/types/EachTestingContext.type';
|
|
import { deepMerge } from '@/utils';
|
|
|
|
type DeepMergeTestCase<T extends object> = {
|
|
source: Required<T>;
|
|
target: Required<T>;
|
|
expected: T;
|
|
};
|
|
|
|
describe('deepMerge', () => {
|
|
describe('primitive values', () => {
|
|
type PrimitiveValue = { value: string | number | boolean };
|
|
|
|
const primitiveTestCases: EachTestingContext<
|
|
DeepMergeTestCase<PrimitiveValue>
|
|
>[] = [
|
|
{
|
|
title: 'should override string values',
|
|
context: {
|
|
source: { value: 'hello' },
|
|
target: { value: 'world' },
|
|
expected: { value: 'world' },
|
|
},
|
|
},
|
|
{
|
|
title: 'should override number values',
|
|
context: {
|
|
source: { value: 42 },
|
|
target: { value: 24 },
|
|
expected: { value: 24 },
|
|
},
|
|
},
|
|
{
|
|
title: 'should override boolean values',
|
|
context: {
|
|
source: { value: true },
|
|
target: { value: false },
|
|
expected: { value: false },
|
|
},
|
|
},
|
|
];
|
|
|
|
it.each(primitiveTestCases)(
|
|
'$title',
|
|
({ context: { source, target, expected } }) => {
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('null and undefined handling', () => {
|
|
type NullableValue = { value: string | null };
|
|
|
|
const nullTestCases: EachTestingContext<
|
|
DeepMergeTestCase<NullableValue>
|
|
>[] = [
|
|
{
|
|
title: 'should preserve null values from target',
|
|
context: {
|
|
source: { value: 'hello' },
|
|
target: { value: null },
|
|
expected: { value: null },
|
|
},
|
|
},
|
|
{
|
|
title: 'should ignore undefined values from target',
|
|
context: {
|
|
source: { value: 'hello' },
|
|
target: { value: undefined as unknown as null },
|
|
expected: { value: 'hello' },
|
|
},
|
|
},
|
|
{
|
|
title: 'should override null values from source',
|
|
context: {
|
|
source: { value: null },
|
|
target: { value: 'world' },
|
|
expected: { value: 'world' },
|
|
},
|
|
},
|
|
];
|
|
|
|
it.each(nullTestCases)(
|
|
'$title',
|
|
({ context: { source, target, expected } }) => {
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
},
|
|
);
|
|
|
|
type MixedNullValue = { a: number | null; b: number };
|
|
|
|
const mixedNullTestCase: EachTestingContext<
|
|
DeepMergeTestCase<MixedNullValue>
|
|
> = {
|
|
title: 'should handle mixed null and undefined values',
|
|
context: {
|
|
source: { a: 1, b: 2 },
|
|
target: { a: null, b: 2 },
|
|
expected: { a: null, b: 2 },
|
|
},
|
|
};
|
|
|
|
it(mixedNullTestCase.title, () => {
|
|
const { source, target, expected } = mixedNullTestCase.context;
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
});
|
|
});
|
|
|
|
describe('array handling', () => {
|
|
type ArrayValue = { arr: Array<string | number> | null };
|
|
|
|
const arrayTestCases: EachTestingContext<DeepMergeTestCase<ArrayValue>>[] =
|
|
[
|
|
{
|
|
title: 'should concatenate arrays',
|
|
context: {
|
|
source: { arr: [1, 2] },
|
|
target: { arr: [3, 4] },
|
|
expected: { arr: [1, 2, 3, 4] },
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle empty target array',
|
|
context: {
|
|
source: { arr: [1, 2] },
|
|
target: { arr: [] },
|
|
expected: { arr: [1, 2] },
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle empty source array',
|
|
context: {
|
|
source: { arr: [] },
|
|
target: { arr: [1, 2] },
|
|
expected: { arr: [1, 2] },
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle null target array',
|
|
context: {
|
|
source: { arr: ['a', 'b'] },
|
|
target: { arr: null },
|
|
expected: { arr: null },
|
|
},
|
|
},
|
|
];
|
|
|
|
it.each(arrayTestCases)(
|
|
'$title',
|
|
({ context: { source, target, expected } }) => {
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('nested objects', () => {
|
|
type NestedValue = {
|
|
nested: {
|
|
a: number;
|
|
b: number;
|
|
deep?: {
|
|
a: number;
|
|
b: number;
|
|
};
|
|
arr: Array<number>;
|
|
obj: {
|
|
a: number;
|
|
b: number;
|
|
};
|
|
} | null;
|
|
};
|
|
|
|
const nestedTestCases: EachTestingContext<
|
|
DeepMergeTestCase<NestedValue>
|
|
>[] = [
|
|
{
|
|
title: 'should merge nested objects',
|
|
context: {
|
|
source: { nested: { a: 1, b: 2, arr: [], obj: { a: 1, b: 2 } } },
|
|
target: { nested: { a: 1, b: 3, arr: [], obj: { a: 1, b: 2 } } },
|
|
expected: { nested: { a: 1, b: 3, arr: [], obj: { a: 1, b: 2 } } },
|
|
},
|
|
},
|
|
{
|
|
title: 'should merge deeply nested objects',
|
|
context: {
|
|
source: {
|
|
nested: {
|
|
a: 1,
|
|
b: 2,
|
|
deep: { a: 1, b: 2 },
|
|
arr: [],
|
|
obj: { a: 1, b: 2 },
|
|
},
|
|
},
|
|
target: {
|
|
nested: {
|
|
a: 1,
|
|
b: 2,
|
|
deep: { a: 1, b: 3 },
|
|
arr: [],
|
|
obj: { a: 1, b: 2 },
|
|
},
|
|
},
|
|
expected: {
|
|
nested: {
|
|
a: 1,
|
|
b: 2,
|
|
deep: { a: 1, b: 3 },
|
|
arr: [],
|
|
obj: { a: 1, b: 2 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle null nested object in target',
|
|
context: {
|
|
source: { nested: { a: 1, b: 2, arr: [], obj: { a: 1, b: 2 } } },
|
|
target: { nested: null },
|
|
expected: { nested: null },
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle complex nested structures',
|
|
context: {
|
|
source: {
|
|
nested: {
|
|
a: 1,
|
|
b: 2,
|
|
arr: [1, 2],
|
|
obj: { a: 1, b: 2 },
|
|
},
|
|
},
|
|
target: {
|
|
nested: {
|
|
a: 1,
|
|
b: 2,
|
|
arr: [3, 4],
|
|
obj: { a: 1, b: 3 },
|
|
},
|
|
},
|
|
expected: {
|
|
nested: {
|
|
a: 1,
|
|
b: 2,
|
|
arr: [1, 2, 3, 4],
|
|
obj: { a: 1, b: 3 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
it.each(nestedTestCases)(
|
|
'$title',
|
|
({ context: { source, target, expected } }) => {
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
type EdgeValue = {
|
|
a: number | Date | RegExp;
|
|
b: number | Date | RegExp;
|
|
};
|
|
|
|
const edgeTestCases: EachTestingContext<DeepMergeTestCase<EdgeValue>>[] = [
|
|
{
|
|
title: 'should handle Date objects by replacing them',
|
|
context: {
|
|
source: { a: new Date('2023-01-01'), b: new Date('2023-01-01') },
|
|
target: { a: new Date('2023-12-31'), b: new Date('2023-12-31') },
|
|
expected: { a: new Date('2023-12-31'), b: new Date('2023-12-31') },
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle RegExp objects by replacing them',
|
|
context: {
|
|
source: { a: /test1/, b: /test1/ },
|
|
target: { a: /test2/, b: /test2/ },
|
|
expected: { a: /test2/, b: /test2/ },
|
|
},
|
|
},
|
|
{
|
|
title: 'should handle mixed Date and RegExp objects',
|
|
context: {
|
|
source: { a: new Date('2023-01-01'), b: /test1/ },
|
|
target: { a: new Date('2023-12-31'), b: /test2/ },
|
|
expected: { a: new Date('2023-12-31'), b: /test2/ },
|
|
},
|
|
},
|
|
];
|
|
|
|
it.each(edgeTestCases)(
|
|
'$title',
|
|
({ context: { source, target, expected } }) => {
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
},
|
|
);
|
|
|
|
type NestedDateValue = {
|
|
a: { value: Date };
|
|
b: { value: Date };
|
|
};
|
|
|
|
const nestedDateTestCase: EachTestingContext<
|
|
DeepMergeTestCase<NestedDateValue>
|
|
> = {
|
|
title: 'should handle Date objects in nested structures',
|
|
context: {
|
|
source: {
|
|
a: { value: new Date('2023-01-01') },
|
|
b: { value: new Date('2023-01-01') },
|
|
},
|
|
target: {
|
|
a: { value: new Date('2023-12-31') },
|
|
b: { value: new Date('2023-12-31') },
|
|
},
|
|
expected: {
|
|
a: { value: new Date('2023-12-31') },
|
|
b: { value: new Date('2023-12-31') },
|
|
},
|
|
},
|
|
};
|
|
|
|
it(nestedDateTestCase.title, () => {
|
|
const { source, target, expected } = nestedDateTestCase.context;
|
|
expect(deepMerge(source, target)).toEqual(expected);
|
|
});
|
|
});
|
|
});
|