Render structured assistant question prompts directly in chat surfaces. - Add parser and formatter support for question tool calls. - Add reusable question response UI for select, multi-select, text, and confirm prompts. - Wire regular chat and quick chat to show live and answered question states. - Cover parsing and chat response behavior with dashboard tests and localized labels. Files changed: .changeset/fn-6501-chat-question-response.md | 5 + docs/dashboard-guide.md | 2 + .../app/components/ChatQuestionResponse.css | 194 ++++++++++++++++ .../app/components/ChatQuestionResponse.tsx | 247 +++++++++++++++++++++ packages/dashboard/app/components/ChatView.tsx | 85 ++++++- packages/dashboard/app/components/QuickChatFAB.tsx | 84 ++++++- .../__tests__/ChatQuestionResponse.test.tsx | 52 +++++ .../app/components/__tests__/ChatView.test.tsx | 51 +++++ .../app/components/__tests__/QuickChatFAB.test.tsx | 64 ++++++ .../utils/__tests__/parseQuestionToolCall.test.ts | 82 +++++++ .../dashboard/app/utils/parseQuestionToolCall.ts | 240 ++++++++++++++++++++ packages/i18n/locales/en/app.json | 10 + 12 files changed, 1105 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-6501 Fusion-Task-Lineage: 1fd4a3aa-b2d7-4157-a196-852fdeda680d
53 lines
2.5 KiB
TypeScript
53 lines
2.5 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { ChatQuestionResponse } from "../ChatQuestionResponse";
|
|
import type { ParsedQuestionToolCall } from "../../utils/parseQuestionToolCall";
|
|
|
|
const parsed: ParsedQuestionToolCall = {
|
|
questions: [
|
|
{ id: "single", type: "single_select", question: "Pick one", options: [{ id: "a", label: "Alpha" }, { id: "b", label: "Beta", description: "Second" }] },
|
|
{ id: "multi", type: "multi_select", question: "Pick many", options: [{ id: "x", label: "X" }, { id: "y", label: "Y" }] },
|
|
{ id: "text", type: "text", question: "Explain" },
|
|
{ id: "confirm", type: "confirm", question: "Proceed?" },
|
|
],
|
|
};
|
|
|
|
describe("ChatQuestionResponse", () => {
|
|
it("renders all question controls and validates before submit", async () => {
|
|
const user = userEvent.setup();
|
|
const onSubmit = vi.fn();
|
|
render(<ChatQuestionResponse parsed={parsed} onSubmit={onSubmit} />);
|
|
|
|
expect(screen.getByTestId("chat-question-response")).toBeInTheDocument();
|
|
const submit = screen.getByTestId("chat-question-response-submit");
|
|
expect(submit).toBeDisabled();
|
|
|
|
await user.click(screen.getByTestId("chat-question-response-option-single-a"));
|
|
await user.click(screen.getByTestId("chat-question-response-option-multi-x"));
|
|
await user.type(screen.getByTestId("chat-question-response-text-text"), "Need the safe path");
|
|
await user.click(screen.getByTestId("chat-question-response-option-confirm-yes"));
|
|
|
|
expect(submit).toBeEnabled();
|
|
await user.click(submit);
|
|
|
|
expect(onSubmit).toHaveBeenCalledWith(
|
|
"> Q: Pick one\nAlpha\n\n> Q: Pick many\nX\n\n> Q: Explain\nNeed the safe path\n\n> Q: Proceed?\nYes",
|
|
{ single: "a", multi: ["x"], text: "Need the safe path", confirm: true },
|
|
);
|
|
});
|
|
|
|
it("renders an answered read-only summary", () => {
|
|
render(<ChatQuestionResponse parsed={parsed} answered submittedAnswer="> Q: Pick one\nAlpha" onSubmit={vi.fn()} />);
|
|
|
|
expect(screen.getByText("Answered")).toBeInTheDocument();
|
|
expect(screen.getByTestId("chat-question-response-submitted-answer")).toHaveTextContent("Alpha");
|
|
expect(screen.queryByTestId("chat-question-response-submit")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("supports compact mode", () => {
|
|
render(<ChatQuestionResponse parsed={{ questions: [parsed.questions[0]!] }} compact onSubmit={vi.fn()} />);
|
|
expect(screen.getByTestId("chat-question-response")).toHaveClass("chat-question-response--compact");
|
|
});
|
|
});
|