docs(tests): explain editing leases and conditional wire compaction
This commit is contained in:
@@ -30,6 +30,7 @@ const values=document=>({elements:[...document.elements],canvases:[...document.c
|
||||
// original→长中间值→final可合成一个字段修改,但两逻辑ID都在;比较wire前后ledger及完整apply结果。
|
||||
test('wire projection composes long field edits, preserves every original logical ID and leaves ledger/history inputs untouched',()=>{
|
||||
const f=fixture([[field('original',long)],[field(long,'final')]]),before=JSON.stringify([...f.ledger.getSnapshot().operations]),wire=f.project();
|
||||
// changes 可以集中到末个 wire 操作,但每个逻辑操作 ID 都须保留,便于回执完整覆盖原 ledger。
|
||||
assert.deepEqual(wire.map(op=>op.id),f.intents.map(op=>op.id));
|
||||
assert.deepEqual(wire.at(-1).changes,[field('original','final','data.text',{afterPresent:true})]);
|
||||
assert.ok(JSON.stringify(wire).length<JSON.stringify(f.intents.map(({id,changes})=>({id,changes}))).length);
|
||||
@@ -46,6 +47,7 @@ test('position-only compaction keeps title, opaque data and the original before
|
||||
});
|
||||
|
||||
// new创建/更新/删除最终无节点但有墓碑;wire仍留创建与删除,不把生命周期错误压成无操作。
|
||||
// 这里是保留条件与生命周期的 wire 压缩;整体本地抵消须由 elision 另行证明,不能直接套用普通 create→delete 合并。
|
||||
test('created records absorb checked updates while create/delete keeps tombstone and lifecycle evidence',()=>{
|
||||
const created={...node('new'),data:{title:'new',text:long}},updated={...created,data:{...created.data,text:'final'}};
|
||||
const f=fixture([[{kind:'element',id:'new',before:null,after:created}],[{...field(long,'final'),target:{id:'new',incarnation:1}}],[{kind:'element',id:'new',before:updated,after:null}]]),wire=f.project();
|
||||
@@ -57,6 +59,7 @@ test('created records absorb checked updates while create/delete keeps tombstone
|
||||
// 可选字段从absent到值再absent,净值虽零仍保留前置存在性检查,apply结果与逐项操作相同。
|
||||
test('net-zero field changes retain conditional presence checks; omitted true flags compose with explicit true flags',()=>{
|
||||
const changes=[field(null,long,'data.optional',{beforePresent:false}),field(long,null,'data.optional',{beforePresent:true,afterPresent:false})];
|
||||
// 值最终仍缺失,不等于可以删除条件:beforePresent=false 还能阻止在已有 optional 的文档上误应用。
|
||||
const compressed=compressChanges(changes,{preserveChecks:true,preserveLifecycle:true});assert.equal(compressed.length,1);assert.equal(compressed[0].beforePresent,false);assert.equal(compressed[0].afterPresent,false);
|
||||
assert.deepEqual(values(applyChanges(document,compressed)),values(applyChanges(document,changes)));
|
||||
const f=fixture([[changes[0]],[changes[1]]]);assert.equal(f.project().at(-1).changes[0].beforePresent,false);
|
||||
@@ -105,6 +108,7 @@ test('projection checks only affected records and leaves a large untouched docum
|
||||
test('wire cost uses UTF-8 bytes, so a Unicode guard cannot turn a shorter JS string into a larger request',()=>{
|
||||
const a={...node('a',child),data:{title:'😀'.repeat(50),text:'😀'.repeat(50),blob:'x'}};
|
||||
const base=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'frame'}}],[{...node('frame'),componentId:'frame'},a]);
|
||||
// 待压缩字段是很短的 blob,而参与范围护栏需携带含 emoji 的元素;UTF-8 成本会抵消压缩收益。
|
||||
const middle='m'.repeat(150),f=fixture([[field('x',middle,'data.blob')],[field(middle,'final','data.blob')]],base),wire=f.project([child]);
|
||||
assert.deepEqual(wire,f.intents.map(({id,changes})=>({id,changes})));
|
||||
});
|
||||
|
||||
@@ -50,6 +50,7 @@ test('connection drag owns the project gesture, previews without history, commit
|
||||
const path = { type: 'bezier-chain', knots: [] };
|
||||
for (let i = 0; i < 20; i++) sessions.updateConnection('one', { ...path, startControl: { x: i, y: 0 } });
|
||||
assert.equal(editor.getSnapshot().history.undo.length, 0); assert.equal(editor.getSnapshot().operations.size, 0); assert.equal(editor.getSnapshot().document.elements.get('edge').path, null);
|
||||
// 20 次预览只改会话;commit 将最后一次 x=19 的控制向量提交成一个操作及一个撤销单元。
|
||||
assert.equal(sessions.commitConnection('one').status, 'applied'); assert.equal(editor.getSnapshot().history.undo.length, 1); assert.equal(editor.getSnapshot().operations.size, 1);
|
||||
assert.equal(editor.getSnapshot().document.elements.get('edge').path.startControl.x, 19);
|
||||
sessions.beginConnection('one', target); sessions.updateConnection('one', { ...path, startControl: { x: 99, y: 0 } }); detach();
|
||||
@@ -94,6 +95,7 @@ test('workspace retains a failed close for retry and stops edits before releasin
|
||||
await assert.rejects(() => workspace.close(), /workspace_close_failed/); assert.equal(leader.active, true); assert.equal(closed, false);assert.equal(digestClosed,false);
|
||||
assert.equal(a.editor.dispatch({ type: 'element.data', target: { id: 'outside', incarnation: 1 }, patch: { title: 'late' } }).status, 'rejected');
|
||||
assert.equal(workspace.getProjects().size, 1);
|
||||
// 第一次关闭失败后 workspace 仍保留 project 和 leader,供同一 owner 重试保存;成功后才关闭共享资源和存储。
|
||||
broken = false; await workspace.close(); assert.equal(leader.active, false); assert.equal(closed, true);assert.equal(digestClosed,true);
|
||||
});
|
||||
|
||||
@@ -117,6 +119,7 @@ test('a canceled pointer lease cannot update, finish or cancel a newer gesture i
|
||||
const lease = () => { const token = sessions.getSnapshot().gesture.token; return guardEditingLease(() => sessions.getSnapshot().gesture?.token === token, { preview: patch => sessions.update('view', target, patch), commit: () => sessions.commit('view'), cancel: () => sessions.cancel('pointer_cancel') }); };
|
||||
sessions.begin('view', [target]); const first = lease(); first.preview({ x: 100 }); first.cancel();
|
||||
sessions.begin('view', [target]); const second = lease(); second.preview({ x: 200 });
|
||||
// 同一 view/element 的第二场手势有新 token;旧 lease 的三个入口都应无效,不能只防止重复 commit。
|
||||
first.preview({ x: 500 }); first.commit(); first.cancel();
|
||||
assert.equal(sessions.getSnapshot().gesture.preview.get('outside').x, 200); assert.equal(editor.getSnapshot().history.undo.length, 0);
|
||||
second.commit(); assert.equal(editor.getSnapshot().document.elements.get('outside').geometry.x, 200); assert.equal(editor.getSnapshot().history.undo.length, 1);
|
||||
|
||||
Reference in New Issue
Block a user