std/collections/deep_merge_test.ts
2024-04-29 11:57:30 +09:00

431 lines
7.9 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertEquals, assertStrictEquals } from "@std/assert";
import { deepMerge } from "./deep_merge.ts";
Deno.test("deepMerge() handles simple merge", () => {
assertEquals(
deepMerge({
foo: true,
}, {
bar: true,
}),
{
foo: true,
bar: true,
},
);
});
Deno.test("deepMerge() handles symbol merge", () => {
assertEquals(
deepMerge({}, {
[Symbol.for("deepmerge.test")]: true,
}),
{
[Symbol.for("deepmerge.test")]: true,
},
);
});
Deno.test("deepMerge() ignores non enumerable", () => {
assertEquals(
deepMerge(
{},
Object.defineProperties({}, {
foo: { enumerable: false, value: true },
bar: { enumerable: true, value: true },
}),
),
{
bar: true,
},
);
});
Deno.test("deepMerge() handles nested merge", () => {
assertEquals(
deepMerge({
foo: {
bar: true,
},
}, {
foo: {
baz: true,
quux: {},
},
qux: true,
}),
{
foo: {
bar: true,
baz: true,
quux: {},
},
qux: true,
},
);
});
Deno.test("deepMerge() prevents prototype merge", () => {
assertEquals(
deepMerge({
constructor: undefined,
}, {
foo: true,
}),
{
constructor: undefined,
foo: true,
},
);
});
Deno.test("deepMerge() prevents calling Object.prototype.__proto__ accessor property", () => {
Object.defineProperty(Object.prototype, "__proto__", {
get() {
throw new Error(
"Unexpected Object.prototype.__proto__ getter property call",
);
},
set() {
throw new Error(
"Unexpected Object.prototype.__proto__ setter property call",
);
},
configurable: true,
});
try {
assertEquals<unknown>(
deepMerge({
foo: true,
}, {
bar: true,
["__proto__"]: {},
}),
{
foo: true,
bar: true,
},
);
} finally {
// deno-lint-ignore no-explicit-any
delete (Object.prototype as any).__proto__;
}
});
Deno.test("deepMerge() overrides target (non-mergeable source)", () => {
assertEquals(
deepMerge({
foo: {
bar: true,
},
}, {
foo: true,
}),
{
foo: true,
},
);
});
Deno.test("deepMerge() overrides target (non-mergeable destination, object like)", () => {
const CustomClass = class {};
assertEquals(
deepMerge({
foo: new CustomClass(),
}, {
foo: true,
}),
{
foo: true,
},
);
});
Deno.test("deepMerge() overrides target (non-mergeable destination, array like)", () => {
assertEquals(
deepMerge({
foo: [],
}, {
foo: true,
}),
{
foo: true,
},
);
});
Deno.test("deepMerge() overrides target (different object like source and destination)", () => {
assertEquals(
deepMerge({
foo: {},
}, {
foo: [1, 2],
}),
{
foo: [1, 2],
},
);
assertEquals(
deepMerge({
foo: [],
}, {
foo: { bar: true },
}),
{
foo: { bar: true },
},
);
});
Deno.test("deepMerge() handles primitive types handling", () => {
const CustomClass = class {};
const expected = {
boolean: true,
null: null,
undefined: undefined,
number: 1,
bigint: 1n,
string: "string",
symbol: Symbol.for("deepmerge.test"),
object: { foo: true },
regexp: /regex/,
date: new Date(),
function() {},
async async() {},
arrow: () => {},
class: new CustomClass(),
get get() {
return true;
},
};
assertEquals(
deepMerge({
boolean: false,
null: undefined,
undefined: null,
number: -1,
bigint: -1n,
string: "foo",
symbol: Symbol(),
object: null,
regexp: /foo/,
date: new Date(0),
function: function () {},
async: async function () {},
arrow: () => {},
class: null,
get: false,
}, expected),
expected,
);
});
Deno.test("deepMerge() handles array merge (replace)", () => {
assertEquals(
deepMerge({
foo: [1, 2, 3],
}, {
foo: [4, 5, 6],
}, { arrays: "replace" }),
{
foo: [4, 5, 6],
},
);
});
Deno.test("deepMerge() handles array merge (merge)", () => {
assertEquals(
deepMerge({
foo: [1, 2, 3],
}, {
foo: [4, 5, 6],
}, { arrays: "merge" }),
{
foo: [1, 2, 3, 4, 5, 6],
},
);
});
Deno.test("deepMerge() handles maps merge (replace)", () => {
assertEquals(
deepMerge({
map: new Map([["foo", true]]),
}, {
map: new Map([["bar", true]]),
}, { maps: "replace" }),
{
map: new Map([["bar", true]]),
},
);
});
Deno.test("deepMerge() handles maps merge (merge)", () => {
assertEquals(
deepMerge({
map: new Map([["foo", true]]),
}, {
map: new Map([["bar", true]]),
}, { maps: "merge" }),
{
map: new Map([["foo", true], ["bar", true]]),
},
);
});
Deno.test("deepMerge() handles sets merge (replace)", () => {
assertEquals(
deepMerge({
set: new Set(["foo"]),
}, {
set: new Set(["bar"]),
}, { sets: "replace" }),
{
set: new Set(["bar"]),
},
);
});
Deno.test("deepMerge() handles sets merge (merge)", () => {
assertEquals(
deepMerge({
set: new Set(["foo"]),
}, {
set: new Set(["bar"]),
}, { sets: "merge" }),
{
set: new Set(["foo", "bar"]),
},
);
});
Deno.test("deepMerge() handles nested replace", () => {
assertEquals(
deepMerge(
{
a: "A1",
b: ["B11", "B12"],
c: {
d: "D1",
e: ["E11"],
},
},
{
b: [],
c: {
d: "D2",
e: [],
},
},
{
arrays: "replace",
},
),
{
a: "A1",
b: [],
c: {
d: "D2",
e: [],
},
},
);
});
Deno.test("deepMerge() handles complex test", () => {
assertEquals(
deepMerge({
foo: {
bar: {
quux: new Set(["foo"]),
grault: {},
},
},
}, {
foo: {
bar: {
baz: true,
qux: [1, 2],
grault: {
garply: false,
},
},
corge: "deno",
[Symbol.for("deepmerge.test")]: true,
},
}),
{
foo: {
bar: {
quux: new Set(["foo"]),
baz: true,
qux: [1, 2],
grault: {
garply: false,
},
},
corge: "deno",
[Symbol.for("deepmerge.test")]: true,
},
},
);
});
Deno.test("deepMerge() handles circular references", () => {
const expected = { foo: true } as { foo: boolean; bar: unknown };
expected.bar = expected;
assertEquals(deepMerge({}, expected), expected);
assertEquals(deepMerge(expected, {}), expected);
assertEquals(deepMerge(expected, expected), expected);
const source = {
foo: { b: { c: { d: {} } } },
bar: {},
};
const object = {
foo: { a: 1 },
bar: { a: 2 },
};
source.foo.b.c.d = source;
// deno-lint-ignore no-explicit-any
(source.bar as any).b = source.foo.b;
// deno-lint-ignore no-explicit-any
const result: any = deepMerge(source, object);
assertStrictEquals(result.foo.b.c.d, result.foo.b.c.d.foo.b.c.d);
});
Deno.test("deepMerge() handles target object is not modified", () => {
const record = {
foo: {
bar: true,
},
baz: [1, 2, 3],
quux: new Set([1, 2, 3]),
};
assertEquals(
deepMerge(record, {
foo: {
qux: false,
},
baz: [4, 5, 6],
quux: new Set([4, 5, 6]),
}, { arrays: "merge", sets: "merge" }),
{
foo: {
bar: true,
qux: false,
},
baz: [1, 2, 3, 4, 5, 6],
quux: new Set([1, 2, 3, 4, 5, 6]),
},
);
assertEquals(record, {
foo: {
bar: true,
},
baz: [1, 2, 3],
quux: new Set([1, 2, 3]),
});
});