- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。 - 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。 - 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。 - 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
92 lines
4.0 KiB
TypeScript
92 lines
4.0 KiB
TypeScript
import { afterEach, expect, it, vi } from "vitest";
|
||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||
import { Ui运行观察 } from "../../src/功能/作者经验/运行观察";
|
||
|
||
const api = vi.hoisted(() => ({ GET: vi.fn(), POST: vi.fn() }));
|
||
vi.mock("../../src/接口/生成/客户端", () => ({ 客户端: api }));
|
||
afterEach(() => {
|
||
cleanup();
|
||
vi.clearAllMocks();
|
||
});
|
||
const source = {
|
||
locator: {
|
||
task_id: "task-1",
|
||
attempt_id: "attempt-1",
|
||
revision: 4,
|
||
content_hash: "a".repeat(64),
|
||
},
|
||
runtime: {
|
||
task_id: "task-1",
|
||
attempt_id: "attempt-1",
|
||
attempt_state: "failed",
|
||
evidence: [],
|
||
},
|
||
};
|
||
function show() {
|
||
api.GET.mockResolvedValue({ data: [] });
|
||
return render(
|
||
<QueryClientProvider
|
||
client={
|
||
new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||
}
|
||
>
|
||
<Ui运行观察
|
||
taskId="task-1"
|
||
scope="author-1"
|
||
steps={[{ step_id: "检查", state: "failed", attempt_id: "attempt-1" }]}
|
||
/>
|
||
</QueryClientProvider>,
|
||
);
|
||
}
|
||
|
||
// @muse-case {"case_id":"NC-run-observation-retry","environment":"离线","given":"固定请求、来源与作者;按用例环境准备独立测试输入","when":"通过实际公开入口执行并核对拒绝、状态及持久结果","then":["NC-run-observation-retry:先核来源,失败保留原话并重试同一观察命令"],"contract":"docs/系统架构/新版设计/模块设计/B07-作者经验.md#有来源的运行观察"}
|
||
it("NC-run-observation-retry:先核来源,失败保留原话并重试同一观察命令", async () => {
|
||
api.POST.mockResolvedValueOnce({ data: source });
|
||
show();
|
||
expect(api.POST).not.toHaveBeenCalled();
|
||
fireEvent.change(screen.getByLabelText("观察哪一步"), {
|
||
target: { value: "attempt-1" },
|
||
});
|
||
fireEvent.click(screen.getByRole("button", { name: "核对这次执行依据" }));
|
||
await screen.findByText(/所选尝试:未能完成/);
|
||
expect(api.POST).toHaveBeenCalledTimes(1);
|
||
fireEvent.change(screen.getByLabelText("观察到的现象"), {
|
||
target: { value: "检查没有完成" },
|
||
});
|
||
fireEvent.change(screen.getByLabelText("你的原话"), {
|
||
target: { value: "原话保留" },
|
||
});
|
||
fireEvent.change(screen.getByLabelText("这次的背景"), {
|
||
target: { value: "固定测试背景" },
|
||
});
|
||
api.POST.mockRejectedValueOnce(new Error("响应中断"));
|
||
fireEvent.click(screen.getByRole("button", { name: "保存观察" }));
|
||
await screen.findByText("响应中断");
|
||
const body = api.POST.mock.calls[1][1].body;
|
||
expect((screen.getByLabelText("你的原话") as HTMLTextAreaElement).value).toBe(
|
||
"原话保留",
|
||
);
|
||
api.POST.mockResolvedValueOnce({
|
||
data: { observation_id: "saved-1", deduped: true },
|
||
});
|
||
fireEvent.click(screen.getByRole("button", { name: "保存观察" }));
|
||
await screen.findByText("已恢复同一条观察。");
|
||
expect(api.POST.mock.calls[2][1].body).toEqual(body);
|
||
expect(body.observation).not.toHaveProperty("state");
|
||
});
|
||
|
||
// @muse-case {"case_id":"NC-run-observation-source","environment":"离线","given":"固定请求、来源与作者;按用例环境准备独立测试输入","when":"通过实际公开入口执行并核对拒绝、状态及持久结果","then":["NC-run-observation-source:不同任务或尝试的读回不能成为保存依据"],"contract":"docs/系统架构/新版设计/模块设计/B07-作者经验.md#有来源的运行观察"}
|
||
it("NC-run-observation-source:不同任务或尝试的读回不能成为保存依据", async () => {
|
||
api.POST.mockResolvedValue({
|
||
data: { ...source, runtime: { ...source.runtime, task_id: "other-task" } },
|
||
});
|
||
show();
|
||
fireEvent.change(screen.getByLabelText("观察哪一步"), {
|
||
target: { value: "attempt-1" },
|
||
});
|
||
fireEvent.click(screen.getByRole("button", { name: "核对这次执行依据" }));
|
||
await screen.findByText("返回依据与选中的执行尝试不同。");
|
||
expect(screen.queryByRole("button", { name: "保存观察" })).toBeNull();
|
||
});
|