fix: unify chat process and reply presentation
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import type { InteractionAnswer, InteractionRequest } from "../../src/features/agent/api/interaction-requests";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
|
||||
for (const width of [1440, 375, 320]) {
|
||||
test(`question card aligns wrapped choices and accepts a text-only answer at ${width}px`, async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await mockAgentApi(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.setContent('<html lang="zh-CN"><head><link rel="stylesheet" href="/src/styles.css"></head><body><main id="question-reader" data-agent-theme="chat" style="max-width:700px;margin:16px auto;padding:12px"></main></body></html>');
|
||||
await page.evaluate(async () => {
|
||||
const entry = await (await fetch("/src/main.tsx")).text();
|
||||
const reactUrl = entry.match(/from "([^"\n]*\/react\.js[^"\n]*)"/)?.[1];
|
||||
const clientUrl = entry.match(/from "([^"\n]*\/react-dom_client\.js[^"\n]*)"/)?.[1];
|
||||
if (!reactUrl || !clientUrl) throw new Error("Vite React module paths unavailable");
|
||||
const reactModule = await import(reactUrl);
|
||||
const React = reactModule.default ?? reactModule;
|
||||
const clientModule = await import(clientUrl);
|
||||
const { createRoot } = clientModule.default ?? clientModule;
|
||||
const { AgentInteractionRequestCard } = await import("/src/features/agent/components/agent-interaction-request-card.tsx");
|
||||
const root = createRoot(document.getElementById("question-reader"));
|
||||
function Fixture({ custom }: { custom: boolean }) {
|
||||
const [draft, setDraft] = React.useState([] as InteractionAnswer[]);
|
||||
const [submitted, setSubmitted] = React.useState(false);
|
||||
const record: InteractionRequest = { version: "agent-interaction-request@1", interaction_id: custom ? "free-text" : "closed-choices", message_id: "m", turn_id: "t", call_id: "c",
|
||||
questions: [{ question_id: "target", title: "确认要定位的对象", body: custom ? "可直接填写需要定位的对象和地址线索。" : "当前记录包含公司上报地址和三组模拟管段位置。不同目标会在地图上定位到不同位置,请选择要定位的对象。",
|
||||
options: custom ? [] : [
|
||||
{ id: "company", label: "公司上报地址:安徽省滁州市凤阳县西泉镇的公司点位" },
|
||||
{ id: "pipe1", label: "模拟管段 Pipe19026(用户给出的约315米位置)" },
|
||||
{ id: "pipe2", label: "模拟管段 Pipe18751(用户给出的约516米位置)" },
|
||||
{ id: "pipe3", label: "模拟管段 Pipe18750(用户给出的约523米位置)" }
|
||||
], mode: "single", required: true, allow_custom: custom }],
|
||||
status: submitted ? "answered" : "pending", revision: submitted ? 1 : 0, draft_revision: 0, draft: [], created_at: "2026-10-28T09:19:24Z",
|
||||
...(submitted ? { answers: draft.map((answer: InteractionAnswer) => ({ ...answer, selected_options: [] })) } : {}) };
|
||||
return React.createElement(AgentInteractionRequestCard, { entry: { record, draft, dirty: draft.length > 0, busy: false }, checking: false,
|
||||
onChange: (next: InteractionAnswer[]) => setDraft(next), onAction: (action: string) => { if (action === "reply") setSubmitted(true); } });
|
||||
}
|
||||
const render = (custom: boolean) => root.render(React.createElement(Fixture, { key: String(custom), custom }));
|
||||
Object.assign(window, { showTextOnlyQuestion: () => render(true) });
|
||||
render(false);
|
||||
});
|
||||
const card = page.getByRole("article", { name: "需要补充的信息" });
|
||||
await expect(card.getByRole("radio")).toHaveCount(4);
|
||||
await expect(card.getByRole("textbox")).toHaveCount(0);
|
||||
await expect(card.getByRole("button", { name: "提交回答", exact: true })).toBeDisabled();
|
||||
const choice = card.getByRole("radio").last();
|
||||
const backgroundBefore = await choice.locator("xpath=ancestor::label").evaluate((element) => getComputedStyle(element).backgroundColor);
|
||||
await choice.focus();
|
||||
await page.keyboard.press("Space");
|
||||
await expect(choice).toBeChecked();
|
||||
const backgroundAfter = await choice.locator("xpath=ancestor::label").evaluate((element) => getComputedStyle(element).backgroundColor);
|
||||
expect(backgroundAfter).not.toBe(backgroundBefore);
|
||||
const offsets = await card.getByRole("radio").evaluateAll((inputs) => inputs.map((input) => {
|
||||
const label = input.closest("label")!;
|
||||
const text = label.lastElementChild!;
|
||||
const radioBounds = input.getBoundingClientRect();
|
||||
const textBounds = text.getBoundingClientRect();
|
||||
return Math.abs(radioBounds.top + radioBounds.height / 2 - textBounds.top - parseFloat(getComputedStyle(text).lineHeight) / 2);
|
||||
}));
|
||||
expect(Math.max(...offsets)).toBeLessThanOrEqual(1);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
|
||||
await card.screenshot({ path: testInfo.outputPath(`question-choices-${width}.png`) });
|
||||
|
||||
await page.evaluate(() => (window as unknown as { showTextOnlyQuestion: () => void }).showTextOnlyQuestion());
|
||||
await expect(card.getByRole("radio")).toHaveCount(0);
|
||||
const custom = card.getByRole("textbox", { name: "确认要定位的对象:补充说明" });
|
||||
await custom.fill("请定位公司上报地址,并保留现有管段标记。");
|
||||
await expect(card.getByRole("button", { name: "提交回答", exact: true })).toBeEnabled();
|
||||
await card.screenshot({ path: testInfo.outputPath(`question-custom-${width}.png`) });
|
||||
await card.getByRole("button", { name: "提交回答", exact: true }).click();
|
||||
const reply = page.getByRole("group", { name: "已提交的回答" });
|
||||
await expect(reply.getByText("已回答", { exact: true })).toBeVisible();
|
||||
await expect(card).toHaveCount(0);
|
||||
await expect(page.getByRole("textbox")).toHaveCount(0);
|
||||
await expect(reply.getByText("请定位公司上报地址,并保留现有管段标记。", { exact: true })).toBeVisible();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user