From 245f6e32549a9b80875e465377c4a4d5c8c6cfe3 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 18:00:29 +0800 Subject: [PATCH] docs(tests): clarify editor publication and service receipt assertions --- tests/headless/core.test.mjs | 4 ++++ tests/headless/service.test.mjs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/tests/headless/core.test.mjs b/tests/headless/core.test.mjs index ec934cd..1be83b2 100644 --- a/tests/headless/core.test.mjs +++ b/tests/headless/core.test.mjs @@ -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); diff --git a/tests/headless/service.test.mjs b/tests/headless/service.test.mjs index 6b83083..8ccb856 100644 --- a/tests/headless/service.test.mjs +++ b/tests/headless/service.test.mjs @@ -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({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');