Files
Agent-System/web/src/lib/blockSavePolicy.test.ts
T
weichenandCursor 9b43bd6a73 fix(web): bind editor saves to the opened doc and real PATCH
Drop test-only ETag helpers. App now calls saveControlledBlockEdit,
which PATCHes once on the bound editor docId or fail-closes. Resume
hydrate after a blocked first load, and refuse silent doc switches.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 20:00:47 +00:00

94 lines
3.2 KiB
TypeScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
DOC_SWITCH_WHILE_EDITING_MESSAGE,
EDITOR_DOC_MISMATCH_MESSAGE,
HEAD_CONFLICT_SAVE_MESSAGE,
STALE_BLOCK_SAVE_MESSAGE,
decideActiveDocSwitch,
decideEditorDocBinding,
interpretBlockSaveError,
resolveSaveTargetBlock,
} from './blockSavePolicy';
describe('resolveSaveTargetBlock', () => {
it('keeps the editor and does not fake success when the block id is gone', () => {
const decision = resolveSaveTargetBlock(
[{ id: 'blk_rebuilt', content: '同一段正文' }],
{ id: 'blk_from_modal', content: '同一段正文' },
);
assert.equal(decision.ok, false);
if (decision.ok) return;
assert.equal(decision.keepEditor, true);
assert.equal(decision.reason, 'block_not_found');
assert.equal(decision.message, STALE_BLOCK_SAVE_MESSAGE);
});
it('never retargets another block that happens to share the same content', () => {
const children = [
{ id: 'blk_other', content: '条款甲' },
{ id: 'blk_current', content: '条款甲' },
];
const stale = resolveSaveTargetBlock(children, { id: 'blk_missing', content: '条款甲' });
assert.equal(stale.ok, false);
const hit = resolveSaveTargetBlock(children, { id: 'blk_current', content: '条款甲(已改)' });
assert.deepEqual(hit, { ok: true, index: 1 });
});
});
describe('editor session document binding', () => {
it('fail-closes when activeDocId drifts away from the opened editor doc', () => {
const decision = decideEditorDocBinding({
editorDocId: 'doc_proj6140_intake',
activeDocId: 'doc_proj6140_research',
});
assert.equal(decision.ok, false);
if (decision.ok) return;
assert.equal(decision.keepEditor, true);
assert.equal(decision.reason, 'doc_mismatch');
assert.equal(decision.message, EDITOR_DOC_MISMATCH_MESSAGE);
});
it('accepts a save only when the editor is still bound to the active doc', () => {
assert.deepEqual(
decideEditorDocBinding({
editorDocId: 'doc_proj6140_intake',
activeDocId: 'doc_proj6140_intake',
}),
{ ok: true },
);
});
it('blocks switching documents while the editor is open', () => {
const blocked = decideActiveDocSwitch({
editorDocId: 'doc_a',
nextDocId: 'doc_b',
});
assert.equal(blocked.allow, false);
if (blocked.allow) return;
assert.equal(blocked.keepEditor, true);
assert.equal(blocked.message, DOC_SWITCH_WHILE_EDITING_MESSAGE);
assert.deepEqual(decideActiveDocSwitch({ editorDocId: 'doc_a', nextDocId: 'doc_a' }), {
allow: true,
});
assert.deepEqual(decideActiveDocSwitch({ editorDocId: null, nextDocId: 'doc_b' }), {
allow: true,
});
});
});
describe('interpretBlockSaveError', () => {
it('409 keeps the draft in the editor', () => {
const ui = interpretBlockSaveError({ code: 'DOCUMENT_HEAD_CONFLICT' });
assert.equal(ui.keepEditor, true);
assert.equal(ui.conflict, HEAD_CONFLICT_SAVE_MESSAGE);
});
it('other PATCH failures also keep the editor', () => {
const ui = interpretBlockSaveError({ message: 'network down' });
assert.equal(ui.keepEditor, true);
assert.match(ui.conflict, /network down/);
assert.match(ui.conflict, /仍保留/);
});
});