Files
Endoleap/tests/headless/valid-json.test.mjs
T

29 lines
3.6 KiB
JavaScript

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {validJson,freeze} from '../../src/lib/canvas-core/values.ts';
test('transitively immutable JSON can reuse its proof without enumerating the same object again',()=>{
const value=freeze({data:{text:'safe'},meta:{number:1,flag:true,empty:null}});assert.equal(validJson(value),true);const values=Object.values;try{Object.values=object=>{if(object===value)throw Error('immutable root enumerated twice');return values(object);};assert.equal(validJson(value),true);}finally{Object.values=values;}
});
test('a cached subtree still observes the validation depth limit at each new position',()=>{
const leaf=freeze({value:1});assert.equal(validJson(leaf),true);const nest=count=>{let value=leaf;while(count--)value=Object.freeze({child:value});return value;};assert.equal(validJson(nest(63)),true);assert.equal(validJson(nest(64)),false);assert.equal(validJson(leaf,64),false);assert.equal(validJson(Object.freeze({}),64),true);
});
test('shallow freeze and getters do not hide mutable values or bypass subsequent validation',()=>{
const child={value:1},parent=Object.freeze({child});assert.equal(validJson(parent),true);child.value=undefined;assert.equal(validJson(parent),false);child.value=2;assert.equal(validJson(parent),true);
let field=1,calls=0;const getter=Object.freeze({get value(){calls++;return field;}});assert.equal(validJson(getter),true);field=Infinity;assert.equal(validJson(getter),false);assert.equal(calls,2);
});
test('sparse/inherited array entries and overridden every methods keep their observable behavior',()=>{
const prototype=Object.create(Array.prototype);prototype[0]=1;const sparse=new Array(1);Object.setPrototypeOf(sparse,prototype);Object.freeze(sparse);assert.equal(validJson(sparse),true);prototype[0]=undefined;assert.equal(validJson(sparse),false);
let accepted=true,calls=0;const custom=[1];Object.defineProperty(custom,'every',{value(){calls++;return accepted;}});Object.freeze(custom);assert.equal(validJson(custom),true);accepted=false;assert.equal(validJson(custom),false);assert.equal(calls,2);
let native=true,methodReads=0;const getterArray=[1];Object.defineProperty(getterArray,'every',{get(){methodReads++;return native?Array.prototype.every:()=>false;}});Object.freeze(getterArray);const parent=Object.freeze({getterArray});assert.equal(validJson(parent),true);native=false;assert.equal(validJson(parent),false);assert.equal(methodReads,2);
const hole=Object.freeze(new Array(1));assert.equal(validJson(hole),true);assert.equal(validJson(hole,64),true);
});
test('ordinary invalid JSON, cycles and unsupported prototypes remain invalid',()=>{
const cycle={};cycle.self=cycle;for(const value of [undefined,NaN,Infinity,()=>{},Symbol('x'),1n,{value:undefined},[undefined],new Date(),Object.create(null),cycle])assert.equal(validJson(value),false);assert.equal(validJson({text:'中文',number:0,empty:[]}),true);
});
test('proxy reflection failures and mutable inherited array methods cannot produce an immutable proof',()=>{
const target={value:1},proxy=new Proxy(target,{isExtensible(){throw Error('optional inspection denied');}});assert.equal(validJson(proxy),true);target.value=undefined;assert.equal(validJson(proxy),false);
const arrayTarget=Object.freeze([1]);let accepts=true;const arrayProxy=new Proxy(arrayTarget,{get(target,key,receiver){if(key==='every')return accepts?Array.prototype.every:()=>false;return Reflect.get(target,key,receiver);}}),parent=Object.freeze({arrayProxy});assert.equal(validJson(parent),true);accepts=false;assert.equal(validJson(parent),false);
const other=new Proxy([1],{getPrototypeOf(){throw Error('no shape proof');}});assert.equal(validJson(other),true);
});