Files
fusion/packages/dashboard/app/components/__tests__/ChatQuestionResponse.test.tsx
gsxdsm 81fbb656ac FN-7613: fix confirm Yes/No button selected-state visibility
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>
2026-07-06 19:03:06 -07:00

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");
});
});