Judge / frontend /components /AnswerDisplay.test.tsx
Gonzalo Asencio
fix(frontend): render card chips for backticked card names in answers (#67)
4285ae6 unverified
Raw
History Blame Contribute Delete
6 kB
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(
<AnswerDisplay answer={null} loading={false} error={null} />
);
expect(container.firstChild).toBeNull();
});
it('renders skeleton when loading', () => {
const { container } = render(
<AnswerDisplay answer={null} loading={true} error={null} />
);
expect(container.firstChild).toBeTruthy();
});
it('renders a styled badge for a card keyword in the answer', () => {
render(
<AnswerDisplay answer="Use ACCELERATE to win" loading={false} error={null} />
);
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(
<AnswerDisplay answer="The banish effect removes" loading={false} error={null} />
);
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(
<AnswerDisplay answer="hide a card with [HIDDEN] now" loading={false} error={null} />
);
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(
<AnswerDisplay answer="ASSAULT and DEFLECT are different" loading={false} error={null} />
);
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(
<AnswerDisplay answer="Then Jhin Virtuoso deals damage" loading={false} error={null} />
);
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(
<AnswerDisplay answer="You play `Jhin Virtuoso` on your turn" loading={false} error={null} />
);
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(
<AnswerDisplay answer="A solar eclipse occurs at night" loading={false} error={null} />
);
expect(document.querySelector('[data-slot="hover-card-trigger"]')).toBeNull();
});
it('renders an icon for a known :rb_*: symbol token', () => {
render(
<AnswerDisplay answer="Pay to gain :rb_might: now" loading={false} error={null} />
);
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(
<AnswerDisplay answer="Costs :rb_energy_2: energy" loading={false} error={null} />
);
const el = screen.getByLabelText('2 energy');
expect(el.tagName).toBe('SPAN');
expect(el).toHaveTextContent('2');
});
it('leaves unknown :rb_*: noise as plain text', () => {
render(
<AnswerDisplay answer="trace :rb_kwargs: here" loading={false} error={null} />
);
expect(screen.queryByRole('img')).toBeNull();
expect(screen.getByText(/:rb_kwargs:/)).toBeInTheDocument();
});
it('promotes a leading "Reasoning:" into a section eyebrow, not body text', () => {
render(
<AnswerDisplay answer={'Reasoning:\n\nThe rule applies here'} loading={false} error={null} />
);
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(
<AnswerDisplay answer={'Answer: The card wins'} loading={false} error={null} />
);
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(
<AnswerDisplay answer="Just a plain sentence about the rules" loading={false} error={null} />
);
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(
<AnswerDisplay answer={null} loading={true} error={null} />
);
const dots = container.querySelectorAll('.animate-bounce');
expect(dots).toHaveLength(3);
});
});