/**
* `AskDisplay` — the guide-routing clarification card.
*
* Pins the two contracts a host depends on: the picked option's label reaches
* `onSelect` VERBATIM (the backend resolves the user's reply against the labels
* it offered, so a reworded send breaks routing), and the card is inert without
* a handler (replayed history / observer surfaces must not offer a button that
* would post into someone else's dialog).
*/
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { AskDisplay } from '../ask-display'
import type { AskSegment } from '../types/message.types'
const card = (question: string): AskSegment => ({
type: 'ask',
question,
options: [
{ label: 'Find documentation', description: 'How the feature works and how to set it up' },
{ label: 'Work with workspace data' },
],
})
describe('AskDisplay', () => {
it('renders the question, numbered options and their descriptions', () => {
render()
expect(screen.getByText('What do you want to work on?')).toBeInTheDocument()
expect(screen.getByText('1.')).toBeInTheDocument()
expect(screen.getByText('Find documentation')).toBeInTheDocument()
expect(screen.getByText('How the feature works and how to set it up')).toBeInTheDocument()
expect(screen.getByText('2.')).toBeInTheDocument()
expect(screen.getByText('Work with workspace data')).toBeInTheDocument()
})
it('sends the picked option label verbatim', () => {
const onSelect = vi.fn()
render()
fireEvent.click(screen.getByText('Find documentation'))
expect(onSelect).toHaveBeenCalledWith('Find documentation')
})
it('renders locked without a handler: options disabled and veiled', () => {
const { container } = render()
// Kept as buttons on purpose — assistive tech should hear "dimmed button",
// not read the options as prose that was never actionable.
const options = screen.getAllByRole('button')
expect(options).toHaveLength(2)
for (const option of options) expect(option).toBeDisabled()
// The veil carries the visible lock: it dims the block, owns the pointer
// (not-allowed cursor anywhere over it) and explains itself on hover.
const veil = container.querySelector('[title]')
expect(veil).toBeTruthy()
expect(veil?.className).toContain('cursor-not-allowed')
})
it('tells assistive tech WHY the options are disabled', () => {
const { container, rerender } = render()
// The veil is decorative (aria-hidden), so its `title` never reaches a
// screen reader — the group's description is what does.
const describedBy = screen.getByRole('group').getAttribute('aria-describedby')
expect(describedBy).toBeTruthy()
expect(container.querySelector(`#${CSS.escape(describedBy!)}`)?.textContent).toBe(
'This question was already answered',
)
// An answerable card describes nothing — there is no lock to explain.
rerender()
expect(screen.getByRole('group')).not.toHaveAttribute('aria-describedby')
})
it('hides the pager for a single card', () => {
render()
expect(screen.queryByText(/of 1/)).not.toBeInTheDocument()
expect(screen.queryByLabelText('Next question')).not.toBeInTheDocument()
})
it('pages through a run of cards, one question at a time', () => {
render()
expect(screen.getByText('1 of 2')).toBeInTheDocument()
expect(screen.getByText('First?')).toBeInTheDocument()
expect(screen.queryByText('Second?')).not.toBeInTheDocument()
// At the first page there is nowhere back to go.
expect(screen.getByLabelText('Previous question')).toBeDisabled()
fireEvent.click(screen.getByLabelText('Next question'))
expect(screen.getByText('2 of 2')).toBeInTheDocument()
expect(screen.getByText('Second?')).toBeInTheDocument()
expect(screen.getByLabelText('Next question')).toBeDisabled()
})
})