Skip to content

QA plan: React chat UI (#17)

Story: React + Vite chat UI (question, answer, sources) (#17)
Capability: Web UI (#16)
Spec: specs/ui.md

TypeCoverage
Unit (Vitest + RTL)Question textarea renders; Enter submits, Shift+Enter inserts newline
UnitLoading state renders while request is in flight
UnitAnswer and source chunks render correctly given a mock API response
UnitSource list is collapsed by default; clicking expands
UnitCitation markers in the answer text match source indices
UnitQA history: multiple question/answer pairs render in order
UnitError state renders an error message in the answer area; page does not crash
Unitnpm run build produces a dist/ directory (verified by CI step, not a Vitest test)
AccessibilityKey interactive elements (textarea, submit button, source toggle) have accessible labels
e2e (Playwright)Full interaction flow: type question, submit, see answer and sources, sources collapse/expand

Note: API key intake and badge behavior are in Story #63 (separate plan). This plan covers the base chat UI without auth state.

  • React + Vite project scaffolded with Vitest and React Testing Library configured
  • Mock API response fixture available in tests
  • All Vitest unit tests pass
  • Build step: npm run build exits 0 and produces dist/ with index.html and hashed assets
  • Playwright e2e passes against local Vite dev server (mocked API backend)
  • Accessibility: no critical violations on axe scan of the rendered page
  • No TypeScript errors (tsc --noEmit exits 0)

Unit: local and CI (Vitest headless). e2e: local (headed) and CI (Playwright headless against Vite dev server with mocked API). No AWS access required.

Vitest, React Testing Library, Playwright, axe-core (via @axe-core/playwright or equivalent). tsc for type checking.

  • mockApiResponse: a hardcoded JSON object matching the /query response schema with 2 sources
  • mockApiError: simulates a network error response
  • MSW (Mock Service Worker) or Playwright route intercept to stub API calls in tests
test("source list is collapsed by default", async () => {
// Given a rendered QAPair with two sources
render(<QAPair answer="Test answer" sources={mockSources} />);
// When no interaction has occurred
// Then source content is not visible
expect(screen.queryByText(mockSources[0].content)).not.toBeInTheDocument();
});
  • Vite dev proxy configuration may differ from CloudFront routing in production. Add an e2e smoke test against the QA CloudFront URL (covered in Wire UI plan #18) rather than relying on the proxy alone.
  • CSS modules class-name instability can cause RTL getByRole failures if aria roles are missing. Prefer role-based and label-based queries over class-based selectors in all test assertions.

Spec: question input submits, answer displays, source chunks are collapsible, input clears after submit, npm run build produces deployable dist/. All are direct exit criteria above.