docs(tests): clarify editor publication and service receipt assertions

This commit is contained in:
2026-11-12 18:00:29 +08:00
parent 05a41813f3
commit 245f6e3254
2 changed files with 7 additions and 0 deletions
+4
View File
@@ -34,6 +34,7 @@ test('runtime preparation shares the document publication boundary; metadata has
assert.equal(editor.updateRuntime(value => ({ ...value, ready: true }), old.rootRevision).status, 'applied');
assert.equal(editor.getSnapshot().rootRevision, 2); assert.equal(editor.getSnapshot().revision, 1);
assert.equal(editor.updateRuntime(value => value, old.rootRevision).code, 'stale_plan');
// ack 在此只是命令来源标签:检查它不新增用户操作/历史,未经过真实回执或网络协调。
editor.dispatch({ type: 'element.data', target: target('a'), patch: { title: 'canonical' } }, { origin: 'ack' });
assert.equal(editor.getSnapshot().operations.size, 1); assert.equal(editor.getSnapshot().history.undo.length, 1);
});
@@ -165,6 +166,7 @@ test('async transactions reject, close their draft, and never publish the asynch
await Promise.resolve();
tx.dispatch({ type: 'element.data', target: target('a'), patch: { title: 'after await' } });
});
// 在拒绝后推进微任务,让 async 回调尝试执行后半段;关闭的 tx 应挡住迟到写入。
assert.equal(result.status, 'rejected'); await Promise.resolve(); await Promise.resolve();
assert.strictEqual(editor.getSnapshot(), before);
assert.throws(() => captured.dispatch({ type: 'element.remove', target: target('a') }), /transaction_closed/);
@@ -253,6 +255,7 @@ test('cross-child connection belongs to the common ancestor; cyclic canvas owner
const canvases = [{ id: 'root', parent: null }, { id: 'child', parent: { canvasId: 'root', entryElementId: 'f' } }, { id: 'sibling', parent: { canvasId: 'root', entryElementId: 'g' } }];
const registry = createRegistry(builtinComponents());
const document = createDocument('p', canvases, elements);
// 这里只调用归属查询 commonCanvas,没有真的创建兄弟分区连线;随后独立检查循环 reparent 拒绝。
assert.equal(commonCanvas(document, 'child', 'sibling'), 'root');
const editor = setup(elements, { registry, document }); const before = editor.getSnapshot();
assert.equal(editor.dispatch({ type: 'canvas.reparent', canvasId: 'root', parent: { canvasId: 'child', entryElementId: 'a' } }).status, 'rejected');
@@ -312,6 +315,7 @@ test('growing grouped history can yield during recovery encoding without alterin
for(let i=0;i<300;i++)assert.equal(editor.dispatch({type:'element.data',target:target('a'),patch:{title:'step-'+i}},{groupId:'large-history'}).status,'applied');
const entry=editor.getSnapshot().history.entries.get(editor.getSnapshot().history.undo.at(-1));
assert.equal(entry.changes.length,300);let tick=0,yields=0;
// 编码拿到旧 entry 后再追加同组输入,检查历史扩展不会原地改写已捕获的 300 项前缀。
const pending=sealRecoveryImageAsync(17,1,1,entry,{now:()=>tick++,frameBudgetMs:1,yieldTask:async()=>{yields++;}});
assert.equal(editor.dispatch({type:'element.data',target:target('a'),patch:{title:'later'}},{groupId:'large-history'}).status,'applied');
const image=await pending;assert.ok(yields>0);assert.deepEqual(image.payload,JSON.parse(JSON.stringify(entry)));assert.equal(image.payload.changes.length,300);
+3
View File
@@ -82,6 +82,7 @@ test('only explicit identity-matched terminal proof is zero-write; bare 4xx stay
test('partial canonical receipts replace participants and retain unrelated future components and versions',async()=>{
const{port,request}=setup();const status=await port.submit(request,new AbortController().signal),proposal=port.canonical(request,status,snapshot);
assert.equal(proposal.document.elements.get('a').data.title,'renamed');assert.deepEqual(proposal.document.elements.get('future'),future);assert.equal(proposal.versions.get(child),7);assert.equal(proposal.versions.get(root),4);
// 每次损坏成功结果的副本,分别隔离版本覆盖越界和元素 owner 越界;原 ACK 基线始终不变。
const broken=structuredClone(status);broken.canonical.model.versions={[root]:4,[child]:8};
assert.throws(()=>port.canonical(request,broken,snapshot),/invalid_canonical_response|receipt_coverage_mismatch/);
const wrong=structuredClone(status);wrong.canonical.model.elements.push({...future,ownerCanvasId:root});
@@ -146,6 +147,7 @@ test('historical applied proof validates frozen identity and coverage, retains n
const{port,request}=setup(),status=await port.submit(request,new AbortController().signal),proof=port.verifyApplied(request,status.canonical),proposal=port.canonical(request,status,snapshot);
proof.assertCanonical(proposal);assert.equal(proof.versions.get(root),4);assert.equal(proof.versions.set,undefined);
const changed=createDocument(root,canvases,[{...a,data:{...a.data,title:'later remote'}},f,future]);
// 成功回执证明版本 4 的执行事实;更高版本允许出现后续远程编辑,同版本却不允许正文分歧。
proof.assertCanonical({document:changed,versions:new Map([[root,5],[child,7]])});
assert.throws(()=>proof.assertCanonical({document:changed,versions:new Map([[root,4],[child,7]])}),/canonical_version_mismatch/);
assert.throws(()=>proof.assertCanonical({document,versions:new Map([[root,3],[child,7]])}),/recovery_read_behind_receipt/);
@@ -180,6 +182,7 @@ test('Foundry compiles the proven projection without changing logical intents or
const port=createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport:async()=>({status:200,body:{protocol_version:2,request_id:requestId,status:'applied',operation_ids:[first.id,second.id],model:chunk(false)}})});
const compiled=port.compileBatch([first,second],requestId,{...snapshot,runtime:{...snapshot.runtime,ledger:ledger.getSnapshot()}}),request=ledger.freeze({...compiled,id:requestId,operationIds:[first.id,second.id]}),wire=JSON.parse(request.body).operations;
assert.ok(JSON.stringify(wire).length<JSON.stringify([first,second].map(({id,changes})=>({id,changes}))).length);
// final 由本地 applyChanges 对照确认;固定 transport 回执这里只检查逻辑覆盖与 applied 分类,不模拟后端实际执行 final。
assert.equal(applyChanges(document,wire.flatMap(op=>op.changes)).elements.get('a').data.title,'final');
assert.equal(ledger.getSnapshot().operations.get(first.id).intent.changes[0].after,middle);
assert.deepEqual(wire.map(op=>op.id),request.operationIds);assert.equal((await port.submit(request,new AbortController().signal)).outcome,'applied');