2024-01-01 21:11:32 +00:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2024-04-29 02:57:30 +00:00
|
|
|
import { assertEquals, assertStrictEquals } from "@std/assert";
|
2021-08-05 12:14:45 +00:00
|
|
|
import { deepMerge } from "./deep_merge.ts";
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles simple merge", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: true,
|
|
|
|
}, {
|
|
|
|
bar: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: true,
|
|
|
|
bar: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles symbol merge", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({}, {
|
|
|
|
[Symbol.for("deepmerge.test")]: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
[Symbol.for("deepmerge.test")]: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() ignores non enumerable", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge(
|
|
|
|
{},
|
|
|
|
Object.defineProperties({}, {
|
|
|
|
foo: { enumerable: false, value: true },
|
|
|
|
bar: { enumerable: true, value: true },
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
{
|
|
|
|
bar: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles nested merge", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: {
|
|
|
|
bar: true,
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
foo: {
|
|
|
|
baz: true,
|
|
|
|
quux: {},
|
|
|
|
},
|
|
|
|
qux: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: {
|
|
|
|
bar: true,
|
|
|
|
baz: true,
|
|
|
|
quux: {},
|
|
|
|
},
|
|
|
|
qux: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() prevents prototype merge", () => {
|
2021-09-01 08:08:03 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
constructor: undefined,
|
|
|
|
}, {
|
|
|
|
foo: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
constructor: undefined,
|
|
|
|
foo: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() prevents calling Object.prototype.__proto__ accessor property", () => {
|
2021-11-02 16:41:30 +00:00
|
|
|
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 {
|
2022-05-20 12:08:49 +00:00
|
|
|
assertEquals<unknown>(
|
2021-11-02 16:41:30 +00:00
|
|
|
deepMerge({
|
|
|
|
foo: true,
|
|
|
|
}, {
|
|
|
|
bar: true,
|
|
|
|
["__proto__"]: {},
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: true,
|
|
|
|
bar: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
delete (Object.prototype as any).__proto__;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() overrides target (non-mergeable source)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: {
|
|
|
|
bar: true,
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
foo: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() overrides target (non-mergeable destination, object like)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
const CustomClass = class {};
|
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: new CustomClass(),
|
|
|
|
}, {
|
|
|
|
foo: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() overrides target (non-mergeable destination, array like)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: [],
|
|
|
|
}, {
|
|
|
|
foo: true,
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() overrides target (different object like source and destination)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: {},
|
|
|
|
}, {
|
|
|
|
foo: [1, 2],
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: [1, 2],
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: [],
|
|
|
|
}, {
|
|
|
|
foo: { bar: true },
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
foo: { bar: true },
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles primitive types handling", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles array merge (replace)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: [1, 2, 3],
|
|
|
|
}, {
|
|
|
|
foo: [4, 5, 6],
|
|
|
|
}, { arrays: "replace" }),
|
|
|
|
{
|
|
|
|
foo: [4, 5, 6],
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles array merge (merge)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
foo: [1, 2, 3],
|
|
|
|
}, {
|
|
|
|
foo: [4, 5, 6],
|
|
|
|
}, { arrays: "merge" }),
|
|
|
|
{
|
|
|
|
foo: [1, 2, 3, 4, 5, 6],
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles maps merge (replace)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
map: new Map([["foo", true]]),
|
|
|
|
}, {
|
|
|
|
map: new Map([["bar", true]]),
|
|
|
|
}, { maps: "replace" }),
|
|
|
|
{
|
|
|
|
map: new Map([["bar", true]]),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles maps merge (merge)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
map: new Map([["foo", true]]),
|
|
|
|
}, {
|
|
|
|
map: new Map([["bar", true]]),
|
|
|
|
}, { maps: "merge" }),
|
|
|
|
{
|
|
|
|
map: new Map([["foo", true], ["bar", true]]),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles sets merge (replace)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
set: new Set(["foo"]),
|
|
|
|
}, {
|
|
|
|
set: new Set(["bar"]),
|
|
|
|
}, { sets: "replace" }),
|
|
|
|
{
|
|
|
|
set: new Set(["bar"]),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles sets merge (merge)", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
assertEquals(
|
|
|
|
deepMerge({
|
|
|
|
set: new Set(["foo"]),
|
|
|
|
}, {
|
|
|
|
set: new Set(["bar"]),
|
|
|
|
}, { sets: "merge" }),
|
|
|
|
{
|
|
|
|
set: new Set(["foo", "bar"]),
|
|
|
|
},
|
2022-09-22 07:20:40 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles nested replace", () => {
|
2022-09-22 07:20:40 +00:00
|
|
|
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: [],
|
|
|
|
},
|
|
|
|
},
|
2021-08-05 12:14:45 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles complex test", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles circular references", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
const expected = { foo: true } as { foo: boolean; bar: unknown };
|
|
|
|
expected.bar = expected;
|
|
|
|
assertEquals(deepMerge({}, expected), expected);
|
2022-05-14 15:39:19 +00:00
|
|
|
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);
|
2021-08-05 12:14:45 +00:00
|
|
|
});
|
|
|
|
|
2023-12-20 09:48:02 +00:00
|
|
|
Deno.test("deepMerge() handles target object is not modified", () => {
|
2021-08-05 12:14:45 +00:00
|
|
|
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]),
|
|
|
|
});
|
|
|
|
});
|