jest.mock('@/lib/cardIndex', () => ({ CARD_INDEX: [ { clean_name: 'jhin virtuoso', image_url: 'https://example.com/jhin.png', set_label: 'Unleashed', riftbound_id: 'unl-181-219', }, { clean_name: 'eclipse', image_url: 'https://example.com/eclipse.png', set_label: 'Unleashed', riftbound_id: 'unl-200-219', }, ], })); import { render, screen } from '@testing-library/react'; import { AnswerDisplay } from './AnswerDisplay'; describe('AnswerDisplay', () => { it('renders null when no answer, no loading, no error', () => { const { container } = render( ); expect(container.firstChild).toBeNull(); }); it('renders skeleton when loading', () => { const { container } = render( ); expect(container.firstChild).toBeTruthy(); }); it('renders a styled badge for a card keyword in the answer', () => { render( ); const text = screen.getByText('ACCELERATE'); expect(text.tagName).toBe('SPAN'); const bg = text.previousElementSibling as HTMLElement; expect(bg).toHaveStyle({ backgroundColor: '#26705f' }); }); it('renders general game keywords as a dotted-underline tooltip trigger (no badge)', () => { render( ); const el = screen.getByText('banish'); // not a colored badge: no skewed background sibling expect(el.previousElementSibling).toBeNull(); expect(el.className).toContain('underline'); }); it('strips the brackets around a bracketed keyword like [HIDDEN]', () => { render( ); expect(screen.getByText('HIDDEN')).toBeInTheDocument(); // the literal "[" / "]" should not survive next to the badge expect(screen.queryByText(/\[\s*HIDDEN/)).toBeNull(); expect(screen.queryByText(/HIDDEN\s*\]/)).toBeNull(); }); it('renders multiple badges in one answer', () => { render( ); expect(screen.getByText('ASSAULT').previousElementSibling).toHaveStyle({ backgroundColor: '#bb2f65' }); expect(screen.getByText('DEFLECT').previousElementSibling).toHaveStyle({ backgroundColor: '#93af34' }); }); it('renders a card chip for a multi-word card name in the answer', () => { render( ); expect(screen.getByText('JHIN VIRTUOSO')).toBeInTheDocument(); expect(document.querySelector('[data-slot="hover-card-trigger"]')).not.toBeNull(); }); it('renders a card chip for a backticked card name (inline code)', () => { render( ); expect(screen.getByText('JHIN VIRTUOSO')).toBeInTheDocument(); expect(document.querySelector('[data-slot="hover-card-trigger"]')).not.toBeNull(); }); it('does not chip single-word card names in the answer', () => { render( ); expect(document.querySelector('[data-slot="hover-card-trigger"]')).toBeNull(); }); it('renders an icon for a known :rb_*: symbol token', () => { render( ); const icon = screen.getByAltText('Might'); expect(icon.tagName).toBe('IMG'); expect(icon).toHaveAttribute('src', '/rb_might.svg'); }); it('renders a CSS circle for an energy token', () => { render( ); const el = screen.getByLabelText('2 energy'); expect(el.tagName).toBe('SPAN'); expect(el).toHaveTextContent('2'); }); it('leaves unknown :rb_*: noise as plain text', () => { render( ); expect(screen.queryByRole('img')).toBeNull(); expect(screen.getByText(/:rb_kwargs:/)).toBeInTheDocument(); }); it('promotes a leading "Reasoning:" into a section eyebrow, not body text', () => { render( ); const label = screen.getByText('reasoning'); expect(label.tagName).toBe('SPAN'); expect(label.className).toContain('uppercase'); // the ":" should be consumed by the label, not leak into the body expect(screen.queryByText(/Reasoning:/)).toBeNull(); }); it('renders an "Answer:" eyebrow in the accent color and keeps the conclusion text', () => { render( ); const label = screen.getByText('answer'); expect(label.className).toContain('text-brand-accent'); expect(screen.getByText(/The card wins/)).toBeInTheDocument(); }); it('leaves an ordinary paragraph untouched when there is no section heading', () => { render( ); expect(screen.queryByText('answer')).toBeNull(); expect(screen.queryByText('reasoning')).toBeNull(); expect(screen.getByText(/Just a plain sentence/)).toBeInTheDocument(); }); it('renders three bouncing dots when loading', () => { const { container } = render( ); const dots = container.querySelectorAll('.animate-bounce'); expect(dots).toHaveLength(3); }); });