char/fjson

friendly JSON implementation in TypeScript

git clone https://git.t4t.associates/char/fjson

Charlotte Somrestructurebb03820

main
3.4 KiB111 linesraw
1// vibed
2
3import { deepStrictEqual, ok, strictEqual, throws } from "node:assert/strict";
4import { apply, applyWith, Document } from "./mod.ts";
5
6// Edit output is best-effort whitespace: new entries are spliced in without
7// reformatting their neighbors. Expected strings below are verified against
8// the reference implementation.
9Deno.test("apply drills down into nested maps", () => {
10  strictEqual(
11    apply("x: 0, y: { a: 1, b: 2 }", { y: { c: 3 } }),
12    "x: 0, y: { a: 1, b: 2 \n  c: 3\n}",
13  );
14});
15
16Deno.test("apply replaces nested maps after deletion", () => {
17  strictEqual(
18    apply("x: 0, y: { a: 1, b: 2 }", { y: undefined }, { y: { c: 3 } }),
19    "x: 0,  \ny: {\n  c: 3\n}",
20  );
21});
22
23Deno.test("apply removes properties set to undefined", () => {
24  strictEqual(apply("a: 1\nb: 2", { a: undefined }), " \nb: 2");
25  strictEqual(
26    apply("a: { b: 1, c: 2 }", { a: { b: undefined } }),
27    "a: {   c: 2 }",
28  );
29});
30
31Deno.test("apply replaces arrays wholesale", () => {
32  strictEqual(apply("a: [1, 2]", { a: [3] }), "a: [3]");
33  // Numeric keys are strings: the object replaces the array, then merges in.
34  strictEqual(
35    apply("a: [1, 2]", { a: { 0: 3 } }),
36    'a: {\n  "0": 3\n}',
37  );
38});
39
40Deno.test("apply creates missing intermediate maps", () => {
41  strictEqual(apply("a: 1", { b: { c: 2 } }), "a: 1\nb: {\n  c: 2\n}");
42  strictEqual(apply("", { a: 1 }), "a: 1");
43});
44
45Deno.test("apply preserves comments", () => {
46  const source = "// head\na: 1 // keep\nb: { c: 2 /* inside */ }";
47  const result = apply(source, { b: { d: 3 } });
48  ok(result.includes("// head"), result);
49  ok(result.includes("// keep"), result);
50  ok(!result.includes("/* inside */") || result.includes("c: 2"), result);
51  deepStrictEqual(
52    apply("a: 1 // keep", { b: 2 }),
53    "a: 1 // keep\nb: 2",
54  );
55});
56
57Deno.test("apply rejects invalid overrides with TypeError", () => {
58  for (
59    const override of [
60      null,
61      42,
62      "text",
63      [1, 2],
64      () => {},
65    ]
66  ) {
67    throws(() => apply("a: 1", override), TypeError, String(override));
68  }
69  const cyclic: Record<string, unknown> = {};
70  cyclic["self"] = cyclic;
71  throws(() => apply("a: 1", cyclic), TypeError);
72});
73
74Deno.test("apply maps nesting failures to RangeError", () => {
75  let deep: unknown = null;
76  for (let i = 0; i < 130; i += 1) deep = [deep];
77  throws(() => apply("a: 1", { a: deep }), RangeError);
78  const nested: Record<string, unknown> = {};
79  let cursor = nested;
80  for (let i = 0; i < 130; i += 1) {
81    const next: Record<string, unknown> = {};
82    cursor["n"] = next;
83    cursor = next;
84  }
85  throws(() => apply("a: 1", nested), RangeError);
86});
87
88Deno.test("applyWith honors custom limits", () => {
89  let deep: unknown = null;
90  for (let i = 0; i < 10; i += 1) deep = [deep];
91  const text = applyWith("a: 0", { maxDepth: 10 }, { a: deep });
92  deepStrictEqual(Document.parseObject(text, { maxDepth: 10 }).toValue(), {
93    a: deep,
94  });
95  const document = Document.parseObject("a: 0", { maxDepth: 10 });
96  document.apply({ a: deep });
97  deepStrictEqual(document.toValue(), { a: deep });
98  throws(() => applyWith("a: 0", { maxDepth: 2 }, { a: deep }), RangeError);
99});
100
101Deno.test("apply requires an object document", () => {
102  throws(() => apply("[1, 2]", { a: 1 }), SyntaxError);
103});
104
105Deno.test("apply skips non-serializable leaves", () => {
106  strictEqual(apply("a: 1", { b: undefined, c: 2 }), "a: 1\nc: 2");
107  strictEqual(
108    apply("a: 1", { b: () => {} } as unknown as Record<string, unknown>),
109    "a: 1",
110  );
111});