deckhearth/test/components/ScannerResultPanel.test.js

132 lines
4.6 KiB
JavaScript
Raw Normal View History

// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import ScannerResultPanel from '../../components/scanner/ScannerResultPanel.js';
import { VOCAB } from '../../lib/collection-vocabulary.js';
const FOCUSED_CARD = {
id: 'card-1',
name: 'Lightning Bolt',
set: 'Alpha',
rarity: 'Common',
cardNumber: '161',
image_url: 'https://example.com/bolt.jpg',
condition: 'NM',
isFoil: false,
confidence: 0.92,
};
function createQueueFixture({ addingCardIds = new Set() } = {}) {
return {
addingCardIds,
updateCardMetadata: vi.fn(),
};
}
function renderPanel(overrides = {}) {
const props = {
focusedCard: null,
queue: createQueueFixture(),
isIdentifying: false,
commitError: null,
onAddToOwned: vi.fn(),
onAddToList: vi.fn(),
onRescan: vi.fn(),
...overrides,
};
return {
...render(<ScannerResultPanel {...props} />),
props,
};
}
describe('ScannerResultPanel', () => {
afterEach(() => cleanup());
it('renders empty state illustration and instructional copy', () => {
renderPanel();
expect(
screen.getByText('Point your camera at a card or upload an image to see a match.')
).toBeTruthy();
expect(screen.getByRole('heading', { name: 'Scan Result' })).toBeTruthy();
expect(screen.queryByRole('button', { name: VOCAB.ADD_TO_MY_COLLECTION })).toBeNull();
expect(screen.queryByRole('button', { name: /upload/i })).toBeNull();
expect(screen.queryByRole('button', { name: /batch/i })).toBeNull();
});
it('shows Identifying… placeholder when identifying without a focused card', () => {
renderPanel({ isIdentifying: true });
expect(screen.getByText('Identifying…')).toBeTruthy();
expect(
screen.queryByText('Point your camera at a card or upload an image to see a match.')
).toBeNull();
});
it('renders populated card metadata, controls, and vocab action labels', () => {
const queue = createQueueFixture();
renderPanel({ focusedCard: FOCUSED_CARD, queue });
expect(screen.getByRole('heading', { name: FOCUSED_CARD.name, level: 3 })).toBeTruthy();
expect(screen.getByText('Alpha · Common · 161')).toBeTruthy();
expect(screen.getByRole('combobox', { name: `Condition for ${FOCUSED_CARD.name}` })).toBeTruthy();
expect(screen.getByRole('switch', { name: 'Foil' })).toBeTruthy();
expect(screen.getByText('92%')).toBeTruthy();
expect(screen.getByText('Excellent match.')).toBeTruthy();
expect(screen.getByRole('button', { name: VOCAB.ADD_TO_MY_COLLECTION })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Rescan' })).toBeTruthy();
expect(screen.getByRole('button', { name: VOCAB.ADD_TO_LIST })).toBeTruthy();
expect(screen.queryByText(/wishlist/i)).toBeNull();
});
it('disables the primary add button while the card is adding', () => {
const queue = createQueueFixture({ addingCardIds: new Set(['card-1']) });
renderPanel({ focusedCard: FOCUSED_CARD, queue });
const primaryButton = screen.getByRole('button', { name: VOCAB.ADD_TO_MY_COLLECTION });
expect(primaryButton.disabled).toBe(true);
expect(primaryButton.getAttribute('aria-busy')).toBe('true');
});
it('calls queue.updateCardMetadata when condition changes', () => {
const queue = createQueueFixture();
renderPanel({ focusedCard: FOCUSED_CARD, queue });
fireEvent.change(screen.getByRole('combobox', { name: `Condition for ${FOCUSED_CARD.name}` }), {
target: { value: 'LP' },
});
expect(queue.updateCardMetadata).toHaveBeenCalledWith('card-1', { condition: 'LP' });
});
it('calls queue.updateCardMetadata when foil switch is toggled', () => {
const queue = createQueueFixture();
renderPanel({ focusedCard: FOCUSED_CARD, queue });
fireEvent.click(screen.getByRole('switch', { name: 'Foil' }));
expect(queue.updateCardMetadata).toHaveBeenCalledWith('card-1', { isFoil: true });
});
it('shows commit errors with role=alert', () => {
renderPanel({
focusedCard: FOCUSED_CARD,
commitError: 'Could not add card. Try again.',
});
const alert = screen.getByRole('alert');
expect(alert.textContent).toContain('Could not add card. Try again.');
});
it('includes reduced-motion CSS for the confidence bar', () => {
const { container } = renderPanel({ focusedCard: FOCUSED_CARD });
expect(container.innerHTML).toContain('scanner-result-confidence-bar-fill');
expect(container.innerHTML).toContain('prefers-reduced-motion: reduce');
expect(container.innerHTML).toContain('transition: none');
});
});