char/fjson
friendly JSON implementation in TypeScript
git clone https://git.t4t.associates/char/fjson
bb03820
main
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" , () => { 10strictEqual ( 11apply ( "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" , () => { 17strictEqual ( 18apply ( "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" , () => { 24strictEqual ( apply ( "a: 1\nb: 2" , { a :undefined }), " \nb: 2" ); 25strictEqual ( 26apply ( "a: { b: 1, c: 2 }" , { a :{ b :undefined } }), 27"a: { c: 2 }" , 28); 29}); 30 31Deno . test ( "apply replaces arrays wholesale" , () => { 32strictEqual ( apply ( "a: [1, 2]" , { a :[ 3 ] }), "a: [3]" ); 33// Numeric keys are strings: the object replaces the array, then merges in. 34strictEqual ( 35apply ( "a: [1, 2]" , { a :{ 0 :3 } }), 36'a: {\n "0": 3\n}' , 37); 38}); 39 40Deno . test ( "apply creates missing intermediate maps" , () => { 41strictEqual ( apply ( "a: 1" , { b :{ c :2 } }), "a: 1\nb: {\n c: 2\n}" ); 42strictEqual ( apply ( "" , { a :1 }), "a: 1" ); 43}); 44 45Deno . test ( "apply preserves comments" , () => { 46const source = "// head\na: 1 // keep\nb: { c: 2 /* inside */ }" ; 47const result = apply ( source , { b :{ d :3 } }); 48ok ( result . includes ( "// head" ), result ); 49ok ( result . includes ( "// keep" ), result ); 50ok ( ! result . includes ( "/* inside */" ) || result . includes ( "c: 2" ), result ); 51deepStrictEqual ( 52apply ( "a: 1 // keep" , { b :2 }), 53"a: 1 // keep\nb: 2" , 54); 55}); 56 57Deno . test ( "apply rejects invalid overrides with TypeError" , () => { 58for ( 59const override of [ 60null , 6142 , 62"text" , 63[ 1 , 2 ], 64() => {}, 65] 66) { 67throws (() => apply ( "a: 1" , override ), TypeError , String ( override )); 68} 69const cyclic :Record < string , unknown > = {}; 70cyclic [ "self" ] = cyclic ; 71throws (() => apply ( "a: 1" , cyclic ), TypeError ); 72}); 73 74Deno . test ( "apply maps nesting failures to RangeError" , () => { 75let deep :unknown = null ; 76for ( let i = 0 ; i < 130 ; i += 1 ) deep = [ deep ]; 77throws (() => apply ( "a: 1" , { a :deep }), RangeError ); 78const nested :Record < string , unknown > = {}; 79let cursor = nested ; 80for ( let i = 0 ; i < 130 ; i += 1 ) { 81const next :Record < string , unknown > = {}; 82cursor [ "n" ] = next ; 83cursor = next ; 84} 85throws (() => apply ( "a: 1" , nested ), RangeError ); 86}); 87 88Deno . test ( "applyWith honors custom limits" , () => { 89let deep :unknown = null ; 90for ( let i = 0 ; i < 10 ; i += 1 ) deep = [ deep ]; 91const text = applyWith ( "a: 0" , { maxDepth :10 }, { a :deep }); 92deepStrictEqual ( Document . parseObject ( text , { maxDepth :10 }). toValue (), { 93a :deep , 94}); 95const document = Document . parseObject ( "a: 0" , { maxDepth :10 }); 96document . apply ({ a :deep }); 97deepStrictEqual ( document . toValue (), { a :deep }); 98throws (() => applyWith ( "a: 0" , { maxDepth :2 }, { a :deep }), RangeError ); 99}); 100 101Deno . test ( "apply requires an object document" , () => { 102throws (() => apply ( "[1, 2]" , { a :1 }), SyntaxError ); 103}); 104 105Deno . test ( "apply skips non-serializable leaves" , () => { 106strictEqual ( apply ( "a: 1" , { b :undefined , c :2 }), "a: 1\nc: 2" ); 107strictEqual ( 108apply ( "a: 1" , { b :() => {} } as unknown as Record < string , unknown >), 109"a: 1" , 110); 111});