- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。 - 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。 - 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。 - 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
143 lines
5.2 KiB
TypeScript
143 lines
5.2 KiB
TypeScript
import { beforeEach, afterEach, expect, it, vi } from "vitest";
|
||
import {
|
||
cleanup,
|
||
fireEvent,
|
||
render,
|
||
screen,
|
||
waitFor,
|
||
within,
|
||
} from "@testing-library/react";
|
||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||
import { MemoryRouter } from "react-router-dom";
|
||
import { Ui偏好页 } from "../../src/功能/作者经验/偏好页";
|
||
const api = vi.hoisted(() => ({
|
||
list: vi.fn(),
|
||
save: vi.fn(),
|
||
review: vi.fn(),
|
||
confirm: vi.fn(),
|
||
revoke: vi.fn(),
|
||
}));
|
||
vi.mock("../../src/功能/作者经验/数据", () => ({
|
||
列出偏好: api.list,
|
||
保存偏好: api.save,
|
||
打开审阅: api.review,
|
||
确认偏好: api.confirm,
|
||
撤回偏好: api.revoke,
|
||
}));
|
||
vi.mock("../../src/功能/作品规划/数据", () => ({ 列出作品: async () => [] }));
|
||
function row(id = "pending", state = "pending", text = "用动作显现心理。") {
|
||
return {
|
||
preference_id: id,
|
||
revision: 1,
|
||
content_hash: id + "-hash",
|
||
state,
|
||
payload: {
|
||
topic: id,
|
||
text,
|
||
quote: "保留作者原话",
|
||
scope: { kind: "author", work_id: "", task_id: "" },
|
||
origin: "explicit",
|
||
duration: "persistent",
|
||
feedback_id: "",
|
||
},
|
||
conflicts: [],
|
||
};
|
||
}
|
||
function mount() {
|
||
return render(
|
||
<MemoryRouter>
|
||
<QueryClientProvider
|
||
client={
|
||
new QueryClient({
|
||
defaultOptions: {
|
||
queries: { retry: false },
|
||
mutations: { retry: false },
|
||
},
|
||
})
|
||
}
|
||
>
|
||
<Ui偏好页 />
|
||
</QueryClientProvider>
|
||
</MemoryRouter>,
|
||
);
|
||
}
|
||
beforeEach(() => {
|
||
vi.clearAllMocks();
|
||
api.review.mockResolvedValue({ review_id: "review", review_hash: "hash" });
|
||
api.confirm.mockResolvedValue({});
|
||
api.revoke.mockResolvedValue({});
|
||
api.save.mockResolvedValue({ results: [{ state: "active" }] });
|
||
});
|
||
afterEach(cleanup);
|
||
|
||
// @muse-case {"case_id":"NC-ui-preference-conflict","environment":"离线界面和传输替身","given":"真实作者、作品及确切正文反馈版本","when":"保存范围偏好、审阅冲突、确认、撤回及重新消费","then":["点名冲突并审阅后才能确认"],"contract":"docs/系统架构/新版设计/模块设计/B07-作者经验.md"}
|
||
it("NC-ui-preference-conflict:点名冲突并审阅后才能确认", async () => {
|
||
api.list.mockResolvedValue([
|
||
row("old", "active", "解释人物感受。"),
|
||
{ ...row(), conflicts: [["old", 1]] },
|
||
]);
|
||
mount();
|
||
const card = await screen.findByRole("region", { name: "pending" });
|
||
const check = within(card).getByRole("button", {
|
||
name: "核对确认内容",
|
||
}) as HTMLButtonElement;
|
||
expect(check.disabled).toBe(true);
|
||
fireEvent.click(within(card).getByRole("checkbox"));
|
||
fireEvent.click(check);
|
||
const confirm = await within(card).findByRole("button", {
|
||
name: "确认这项偏好",
|
||
});
|
||
expect(api.confirm).not.toHaveBeenCalled();
|
||
fireEvent.click(confirm);
|
||
await waitFor(() =>
|
||
expect(api.confirm).toHaveBeenCalledWith(
|
||
expect.objectContaining({
|
||
preference_id: "pending",
|
||
revision: 1,
|
||
content_hash: "pending-hash",
|
||
replace_versions: [["old", 1]],
|
||
}),
|
||
{ review_id: "review", review_hash: "hash" },
|
||
),
|
||
);
|
||
});
|
||
|
||
// @muse-case {"case_id":"NC-ui-preference-stale","environment":"离线界面和传输替身","given":"真实作者、作品及确切正文反馈版本","when":"保存范围偏好、审阅冲突、确认、撤回及重新消费","then":["旧批准失败后清除可提交审阅"],"contract":"docs/系统架构/新版设计/模块设计/B07-作者经验.md"}
|
||
it("NC-ui-preference-stale:旧批准失败后清除可提交审阅", async () => {
|
||
api.list.mockResolvedValue([row()]);
|
||
api.confirm.mockRejectedValue(new Error("偏好版本已改变"));
|
||
mount();
|
||
const card = await screen.findByRole("region", { name: "pending" });
|
||
fireEvent.click(within(card).getByRole("button", { name: "核对确认内容" }));
|
||
fireEvent.click(
|
||
await within(card).findByRole("button", { name: "确认这项偏好" }),
|
||
);
|
||
await within(card).findByText("偏好版本已改变");
|
||
expect(
|
||
within(card).queryByRole("button", { name: "确认这项偏好" }),
|
||
).toBeNull();
|
||
});
|
||
|
||
// @muse-case {"case_id":"NC-ui-preference-scope","environment":"离线界面和传输替身","given":"真实作者、作品及确切正文反馈版本","when":"保存范围偏好、审阅冲突、确认、撤回及重新消费","then":["任务要求不被编辑成长期范围"],"contract":"docs/系统架构/新版设计/模块设计/B07-作者经验.md"}
|
||
it("NC-ui-preference-scope:任务要求不被编辑成长期范围", async () => {
|
||
api.list.mockResolvedValue([
|
||
{
|
||
...row("one-off", "active"),
|
||
payload: {
|
||
...row().payload,
|
||
duration: "request",
|
||
scope: { kind: "task", work_id: "work", task_id: "task" },
|
||
},
|
||
},
|
||
]);
|
||
mount();
|
||
const card = await screen.findByRole("region", { name: "pending" });
|
||
expect(within(card).queryByRole("button", { name: "修正内容" })).toBeNull();
|
||
fireEvent.click(within(card).getByRole("button", { name: "撤回这项偏好" }));
|
||
await waitFor(() =>
|
||
expect(api.revoke).toHaveBeenCalledWith(
|
||
expect.objectContaining({ preference_id: "one-off", revision: 1 }),
|
||
),
|
||
);
|
||
});
|