Files
Endoleap/tests/headless/recovery-views.test.mjs
T

44 lines
7.4 KiB
JavaScript

/**
* 恢复编码缓存的所有权边界测试。真正由内部拥有的不可变文档/表可复用编码,外部浅冻结对象不能据此获得同样信任。
* 通过暂时拦截 Map 遍历检查重复工作,并比较旧 image 的内容和引用;拦截均在 finally 恢复。
*/
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {createDocument,applyChanges} from '../../src/lib/canvas-core/document.ts';
import {createLedger} from '../../src/lib/canvas-runtime/ledger.ts';
import {encodeRecovery} from '../../src/lib/canvas-runtime/recovery.ts';
// 节点工厂和双节点基线;未改动的 B 可用来检查局部编辑后编码是否复用。
const node=id=>({id,incarnation:1,ownerCanvasId:'root',componentId:'text',dataVersion:1,structure:'node',data:{title:id,text:''},geometry:{x:0,y:0,width:100,height:100,rotation:0}}),doc=()=>createDocument('root',[{id:'root',parent:null}],[node('A'),node('B')]);
// 组装 encodeRecovery 需要的最小状态根,默认 acknowledged 与 working 共用文档,其他运行时表为空。
const snapshot=(document,rootRevision=0)=>({rootRevision,revision:0,document,operations:new Map(),history:{undo:[],redo:[],entries:new Map()},runtime:{acknowledged:document,versions:new Map(),runs:new Map(),resources:new Map(),resourcePins:new Map(),receipts:new Map(),conflicts:new Map(),ledger:createLedger().getSnapshot()}});
// 仅 rootRevision 变化时不再枚举文档;改动 A 后新 image 更新 A、复用 B,并保持先前 image 不变。
test('metadata-only roots reuse the document encoding and preserve prior images through a later edit',()=>{
const document=doc(),one=encodeRecovery(snapshot(document));let two;const values=Map.prototype.values;try{Map.prototype.values=function(){if(this.size===2)throw Error('metadata root enumerated document records');return values.call(this);};two=encodeRecovery(snapshot(document,1));}finally{Map.prototype.values=values;}assert.strictEqual(one.payload.document,two.payload.document);assert.strictEqual(one.payload.document,one.payload.runtime.acknowledged);const changed=applyChanges(document,[{kind:'field',target:{id:'A',incarnation:1},field:'data.text',before:'',after:'new'}]),three=encodeRecovery(snapshot(changed,2));assert.equal(three.payload.document.elements[0].data.text,'new');assert.equal(one.payload.document.elements[0].data.text,'');assert.strictEqual(three.payload.document.elements[1],one.payload.document.elements[1]);assert.strictEqual(three.payload.document,encodeRecovery(snapshot(changed,3)).payload.document);
});
// 外部 envelope 即使冻结,内部 Map 成员与元素数据仍可能变;每次编码重新捕获,既不冻结调用者也不污染旧 image。
// lookalike 只复制包络再冻结,内容虽然来自框架文档,也不能继承原包络私有的所有权标记。
test('unowned document envelopes reread mutable map membership and nested data without freezing callers',()=>{
const base=doc(),mutable=node('mutable'),elements=new Map([['mutable',mutable]]),document=Object.freeze({...base,elements}),one=encodeRecovery(snapshot(document));mutable.data.text='changed';elements.set('other',node('other'));const two=encodeRecovery(snapshot(document,1));assert.equal(two.payload.document.elements.length,2);assert.equal(two.payload.document.elements[0].data.text,'changed');assert.equal(one.payload.document.elements.length,1);assert.equal(one.payload.document.elements[0].data.text,'');assert.equal(Object.isFrozen(mutable),false);const lookalike=Object.freeze({...base});assert.notStrictEqual(encodeRecovery(snapshot(lookalike)).payload.document,encodeRecovery(snapshot(lookalike,1)).payload.document);
});
// applyChanges 接管表成员关系并不证明外部元素深不可变:源表后来加项不进入新表,嵌套数据变化仍须重新捕获。
test('factory table ownership does not imply deep immutability of values inherited from an external document',()=>{
const base=doc(),mutable=node('external'),source={...base,elements:new Map([['external',mutable]])},document=applyChanges(source,[]),one=encodeRecovery(snapshot(document));mutable.data.text='later';source.elements.set('new',node('new'));const two=encodeRecovery(snapshot(document,1));assert.equal(two.payload.document.elements.length,1);assert.equal(two.payload.document.elements[0].data.text,'later');assert.equal(one.payload.document.elements[0].data.text,'');assert.equal(Object.isFrozen(mutable),false);
});
import {readMap,freeze} from '../../src/lib/canvas-core/values.ts';
import * as publicCore from '../../src/lib/canvas-core/index.ts';
// 内部表编码可跨元数据代次复用,新增表项只生成新成员集合并复用安全旧值;内部 provenance 也不能泄漏为公共 API。
test('metadata roots reuse owned table entry encodings while new tables retain old image membership',()=>{
assert.equal(Object.hasOwn(publicCore,'isOwnedMapView'),false,'provenance is an internal codec optimization');
const state=snapshot(doc()),entries=readMap(new Map([['one',freeze({text:'first'})],['two',freeze({text:'second'})]]));state.operations=entries;state.history.entries=entries;state.runtime.receipts=entries;const one=encodeRecovery(state),iterate=Map.prototype[Symbol.iterator];let two;
try{Map.prototype[Symbol.iterator]=function(){if(this.size===2)throw Error('owned table enumerated again');return iterate.call(this);};two=encodeRecovery({...state,rootRevision:1});}finally{Map.prototype[Symbol.iterator]=iterate;}
assert.strictEqual(two.payload.operations,one.payload.operations);assert.strictEqual(two.payload.history.entries,one.payload.history.entries);assert.strictEqual(two.payload.runtime.receipts,one.payload.runtime.receipts);assert.strictEqual(one.payload.operations,one.payload.runtime.receipts);
const changed=readMap(new Map([...entries,['three',freeze({text:'third'})]])),three=encodeRecovery({...state,rootRevision:2,operations:changed});assert.equal(three.payload.operations.length,3);assert.equal(one.payload.operations.length,2);assert.strictEqual(three.payload.operations[0][1],one.payload.operations[0][1]);
});
// 冻结外部 Map 不冻结成员或阻止 set;内部表里的浅冻结值也可能含可变子项,两种情况都必须重读并保留旧快照。
test('external maps and immutable-looking maps reread membership; owned tables still reread mutable nested values',()=>{
const state=snapshot(doc()),external=Object.freeze(new Map([['one',{text:'first'}]]));state.runtime.receipts=external;const one=encodeRecovery(state);external.get('one').text='second';external.set('two',{text:'other'});const two=encodeRecovery({...state,rootRevision:1});assert.deepEqual(two.payload.runtime.receipts,[['one',{text:'second'}],['two',{text:'other'}]]);assert.equal(one.payload.runtime.receipts.length,1);assert.equal(one.payload.runtime.receipts[0][1].text,'first');assert.equal(Object.isFrozen(external.get('one')),false);
// readMap 保护表成员关系,shallow 仅冻结第一层;嵌套 mutable 仍须在每次编码时重新捕获。
const mutable={text:'first'},shallow=Object.freeze({mutable}),owned=readMap(new Map([['one',shallow]]));state.runtime.receipts=owned;const three=encodeRecovery({...state,rootRevision:2});mutable.text='changed';const four=encodeRecovery({...state,rootRevision:3});assert.equal(three.payload.runtime.receipts[0][1].mutable.text,'first');assert.equal(four.payload.runtime.receipts[0][1].mutable.text,'changed');assert.equal(Object.isFrozen(mutable),false);
});