Fix confirm Yes/No selected-state visibility in chat questions and add aria-pressed accessibility support. - Strengthen CSS specificity for .chat-question-response__confirm--selected so the selected style beats the global .btn/.btn:hover rules, using token-driven --cta-* colors for light/dark themes. - Add dedicated hover and focus-visible states for the selected confirm button. - Add aria-pressed to the Yes/No confirm buttons so assistive tech reflects the same selected state. - Add regression tests covering the selected visual/aria state. - Add changeset (patch) documenting the fix. Files changed: .changeset/fn-7613-confirm-selected-state.md | 7 ++++ .../app/components/ChatQuestionResponse.css | 40 ++++++++++++++++++++-- .../app/components/ChatQuestionResponse.tsx | 8 +++++ .../__tests__/ChatQuestionResponse.test.tsx | 26 ++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-7613 Fusion-Task-Lineage: d648044e-ab30-4542-9402-a7bfbfe6563e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
94 lines
4.7 KiB
TypeScript
94 lines
4.7 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 without leftover live controls", () => {
|
|
const originalInnerWidth = window.innerWidth;
|
|
Object.defineProperty(window, "innerWidth", { configurable: true, value: 390 });
|
|
window.dispatchEvent(new Event("resize"));
|
|
|
|
try {
|
|
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();
|
|
expect(screen.queryByRole("button", { name: "Send answer" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("radio")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
expect(document.querySelector(".chat-question-response__actions")).toBeNull();
|
|
expect(document.querySelectorAll(".chat-question-response__option")).toHaveLength(0);
|
|
} finally {
|
|
Object.defineProperty(window, "innerWidth", { configurable: true, value: originalInnerWidth });
|
|
window.dispatchEvent(new Event("resize"));
|
|
}
|
|
});
|
|
|
|
it("marks the clicked confirm option as visually selected and moves the marker on re-click (FN-7613)", async () => {
|
|
const user = userEvent.setup();
|
|
render(<ChatQuestionResponse parsed={parsed} onSubmit={vi.fn()} />);
|
|
|
|
const yesButton = screen.getByTestId("chat-question-response-option-confirm-yes");
|
|
const noButton = screen.getByTestId("chat-question-response-option-confirm-no");
|
|
|
|
// Neither selected before any click.
|
|
expect(yesButton).not.toHaveClass("chat-question-response__confirm--selected");
|
|
expect(noButton).not.toHaveClass("chat-question-response__confirm--selected");
|
|
expect(yesButton).toHaveAttribute("aria-pressed", "false");
|
|
expect(noButton).toHaveAttribute("aria-pressed", "false");
|
|
|
|
await user.click(yesButton);
|
|
expect(yesButton).toHaveClass("chat-question-response__confirm--selected");
|
|
expect(noButton).not.toHaveClass("chat-question-response__confirm--selected");
|
|
expect(yesButton).toHaveAttribute("aria-pressed", "true");
|
|
expect(noButton).toHaveAttribute("aria-pressed", "false");
|
|
|
|
await user.click(noButton);
|
|
expect(noButton).toHaveClass("chat-question-response__confirm--selected");
|
|
expect(yesButton).not.toHaveClass("chat-question-response__confirm--selected");
|
|
expect(noButton).toHaveAttribute("aria-pressed", "true");
|
|
expect(yesButton).toHaveAttribute("aria-pressed", "false");
|
|
});
|
|
|
|
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");
|
|
});
|
|
});
|