muse-agent-example/web/tests/交互/作者偏好.test.tsx
zizi ae02fd1732 前端:工作台页面、事件标签与旅程用例
- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。
- 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。
- 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。
- 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
2026-09-18 01:15:07 +08:00

143 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }),
),
);
});