// @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(), 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'); }); });