test: cover five-second conversation ordering and interaction pauses

Add App regression coverage and document five-second sidebar sorting, live row updates, and deferred reordering during interaction. The ordering hook and App wiring were already included in ee4eb73. Focused validation: 23 tests passed.
This commit is contained in:
mesalogo
2026-10-02 19:00:10 +08:00
parent ee4eb737e0
commit 0ce9606c89
4 changed files with 81 additions and 4 deletions
+54 -3
View File
@@ -3857,6 +3857,15 @@ describe("App", () => {
});
it("keeps parallel streaming conversations in message order while persisting fresh deltas", async () => {
const realSetInterval = window.setInterval.bind(window);
const orderingTicks: Array<() => void> = [];
vi.spyOn(window, "setInterval").mockImplementation(((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
if (timeout === 5_000 && typeof handler === "function") {
orderingTicks.push(handler as () => void);
return realSetInterval(() => {}, timeout);
}
return realSetInterval(handler, timeout, ...args);
}) as typeof window.setInterval);
const snapshots: ConversationSnapshot[] = ["First", "Second"].map((title, index) => ({
id: `parallel-${index}`, projectId, title, updatedAt: 100 - index, messages: [],
}));
@@ -3870,6 +3879,10 @@ describe("App", () => {
fireEvent.click(screen.getByLabelText("发送"));
await waitFor(() => expect(run).toHaveBeenCalledTimes(index + 1));
}
expect(titles()).toEqual(["First", "Second"]);
act(() => screen.getByLabelText("向 GoodBuddy 提问").focus());
expect(orderingTicks.length).toBeGreaterThan(0);
await act(async () => orderingTicks.forEach(tick => tick()));
expect(titles()).toEqual(["Second", "First"]);
const first = run.mock.calls[0]![0];
const second = run.mock.calls[1]![0];
@@ -3893,7 +3906,19 @@ describe("App", () => {
expect(titles()).toEqual(["Second", "First"]);
});
it.each([false, true])("keeps summary and detail order stable across refreshes and moves a newly messaged conversation forward (channel: %s)", async (channel) => {
it.each([false, true].flatMap(channel =>
(["none", "mouse", "focus", "menu"] as const).map(pause => ({ channel, pause })),
))("batches summary and detail ordering every 5 seconds with live rows (channel: $channel, pause: $pause)", async ({ channel, pause }) => {
const realSetInterval = window.setInterval.bind(window);
const orderingTicks: Array<() => void> = [];
vi.spyOn(window, "setInterval").mockImplementation(((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
if (timeout === 5_000 && typeof handler === "function") {
orderingTicks.push(handler as () => void);
// Keep async Testing Library waits real, but advance five-second ticks explicitly.
return realSetInterval(() => {}, timeout);
}
return realSetInterval(handler, timeout, ...args);
}) as typeof window.setInterval);
const snapshots: ConversationSnapshot[] = ["Earlier", "Later"].map((title, index) => ({
id: `stable-${index}`, projectId, title, updatedAt: 900 - index,
...(channel ? { remote: { channel: "weixin" as const, accountDisplay: "Account", conversationType: "direct" as const } } : {}),
@@ -3913,10 +3938,36 @@ describe("App", () => {
fireEvent.click(container.querySelectorAll(".conversation-item")[1]!);
await screen.findByText("Body Earlier");
expect(titles()).toEqual(["Later", "Refreshed Earlier"]);
const list = container.querySelector<HTMLElement>(".conversation-list")!;
const composer = screen.getByLabelText("向 GoodBuddy 提问");
act(() => composer.focus());
if (pause === "mouse") fireEvent.mouseEnter(list);
if (pause === "focus") act(() => screen.getByLabelText("更多会话操作 Refreshed Earlier").focus());
if (pause === "menu") {
fireEvent.click(screen.getByLabelText("更多会话操作 Refreshed Earlier"));
await waitFor(() => expect(screen.getByRole("menu")).toContainElement(document.activeElement as HTMLElement));
expect(list).not.toContainElement(screen.getByRole("menu"));
}
snapshots[0] = { ...snapshots[0]!, updatedAt: 1100, messages: [...snapshots[0]!.messages,
{ id: "new-message", role: "user", state: "complete", content: "New turn", createdAt: 200 }] };
{ id: "new-message", role: "user", state: "complete", content: "New turn", createdAt: 200 }], title: "Live Earlier" };
fireEvent(window, new Event("focus"));
await waitFor(() => expect(titles()).toEqual(["Refreshed Earlier", "Later"]));
await waitFor(() => expect(titles()).toEqual(["Later", "Live Earlier"]));
expect(await screen.findByText("New turn")).toBeInTheDocument();
expect(orderingTicks.length).toBeGreaterThan(0);
await act(async () => orderingTicks.forEach(tick => tick()));
if (pause !== "none") {
expect(titles()).toEqual(["Later", "Live Earlier"]);
if (pause === "mouse") fireEvent.mouseLeave(list);
if (pause === "menu") {
fireEvent.keyDown(screen.getByRole("menu"), { key: "Escape" });
await waitFor(() => expect(screen.queryByRole("menu")).not.toBeInTheDocument());
await waitFor(() => expect(screen.getByLabelText("更多会话操作 Live Earlier")).toHaveFocus());
}
act(() => composer.focus());
expect(titles()).toEqual(["Later", "Live Earlier"]);
await act(async () => orderingTicks.forEach(tick => tick()));
}
expect(titles()).toEqual(["Live Earlier", "Later"]);
});
it("uses the dedicated pin update for channel conversations", async () => {