- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。 - 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。 - 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。 - 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
105 lines
4.3 KiB
TypeScript
105 lines
4.3 KiB
TypeScript
import { expect, it, vi } from "vitest";
|
||
import { 创建编辑会话 } from "../../src/功能/正文写作/编辑会话";
|
||
import { 接口错误 } from "../../src/接口/传输";
|
||
import type {
|
||
正文草稿,
|
||
正文记录,
|
||
保存请求,
|
||
} from "../../src/功能/正文写作/数据";
|
||
|
||
const draft = (text: string): 正文草稿 => ({
|
||
format_version: 1,
|
||
paragraphs: [
|
||
{ paragraph_id: "p1", content: [{ type: "text", text, marks: [] }] },
|
||
],
|
||
});
|
||
const record: 正文记录 = {
|
||
work_id: "w",
|
||
document_id: "doc",
|
||
chapter_id: "c",
|
||
branch_id: "main",
|
||
revision: 1,
|
||
state: "saved",
|
||
document: draft("原稿"),
|
||
visible_text: "原稿",
|
||
document_hash: "hash1",
|
||
visible_text_hash: "text1",
|
||
};
|
||
const scope = {
|
||
author_id: "a",
|
||
work_id: "w",
|
||
chapter_id: "c",
|
||
branch_id: "main",
|
||
};
|
||
|
||
// @muse-case {"case_id":"NC-editor-save-generation","environment":"离线组件/实际编辑器;HTTP响应替身不计真实链路","given":"保存尚在途,作者继续输入","when":"旧请求返回成功","then":["推进服务器基线,保留新输入与未提交状态"],"contract":"docs/系统架构/新版设计/接口契约/正文结构与中文选区.md"}
|
||
it("NC-editor-save-generation:保存中的新输入保留,迟到回执只更新原会话", async () => {
|
||
let finish!: (v: {
|
||
revision: number;
|
||
document_hash: string;
|
||
receipt_id: string;
|
||
}) => void;
|
||
const save = vi.fn(
|
||
() =>
|
||
new Promise<{
|
||
revision: number;
|
||
document_hash: string;
|
||
receipt_id: string;
|
||
}>((r) => (finish = r)),
|
||
);
|
||
const buffer = { write: vi.fn(), clear: vi.fn() };
|
||
const session = 创建编辑会话(scope, record, save, buffer);
|
||
session.change(draft("第一稿"));
|
||
const request = session.save();
|
||
session.change(draft("第一稿和新输入"));
|
||
finish({ revision: 2, document_hash: "hash2", receipt_id: "receipt" });
|
||
await request;
|
||
expect(session.snapshot().draft).toEqual(draft("第一稿和新输入"));
|
||
expect(session.snapshot().base_revision).toBe(2);
|
||
expect(session.snapshot().status).toBe("dirty");
|
||
expect(buffer.clear).not.toHaveBeenCalled();
|
||
session.dispose();
|
||
await session.save();
|
||
expect(save).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
// @muse-case {"case_id":"NC-editor-save-conflict","environment":"离线组件/实际编辑器;HTTP响应替身不计真实链路","given":"保存遇到真实版本冲突代码","when":"保留输入并尝试再次提交","then":["不自动提升基线或覆盖服务器"],"contract":"docs/系统架构/新版设计/接口契约/正文结构与中文选区.md"}
|
||
it("NC-editor-save-conflict:版本冲突保留本地稿且不会自动换基线提交", async () => {
|
||
const save = vi.fn(async () => {
|
||
throw new 接口错误("REVISION_CONFLICT", "正文已更新", 409);
|
||
});
|
||
const session = 创建编辑会话(scope, record, save, {
|
||
write: vi.fn(),
|
||
clear: vi.fn(),
|
||
});
|
||
session.change(draft("我的稿"));
|
||
await session.save();
|
||
await session.save();
|
||
expect(session.snapshot().status).toBe("conflict");
|
||
expect(session.snapshot().draft).toEqual(draft("我的稿"));
|
||
expect(session.snapshot().base_revision).toBe(1);
|
||
expect(save).toHaveBeenCalledTimes(1);
|
||
});
|
||
|
||
// @muse-case {"case_id":"NC-editor-save-retry","environment":"离线组件/实际编辑器;HTTP响应替身不计真实链路","given":"请求结果未知,随后继续编辑","when":"重试保存","then":["重用原命令和原载荷,新输入保留"],"contract":"docs/系统架构/新版设计/接口契约/正文结构与中文选区.md"}
|
||
it("NC-editor-save-retry:未知结果重试原命令,后续输入不改变在途载荷", async () => {
|
||
const requests: 保存请求[] = [];
|
||
const save = vi.fn(async (r: 保存请求) => {
|
||
requests.push(r);
|
||
if (requests.length === 1)
|
||
throw new 接口错误("NETWORK_UNAVAILABLE", "连接中断", 0);
|
||
return { revision: 2, document_hash: "hash2", receipt_id: "receipt" };
|
||
});
|
||
const session = 创建编辑会话(scope, record, save, {
|
||
write: vi.fn(),
|
||
clear: vi.fn(),
|
||
});
|
||
session.change(draft("发送稿"));
|
||
await session.save();
|
||
session.change(draft("新增内容"));
|
||
await session.save();
|
||
expect(requests[1]).toEqual(requests[0]);
|
||
expect(session.snapshot().draft).toEqual(draft("新增内容"));
|
||
expect(session.snapshot().status).toBe("dirty");
|
||
});
|