Give /scanner a md+ camera, live match inspector, and history strip (with device picker, batch scan, and tips) without regressing the mobile immersive checkout. Co-authored-by: Cursor <cursoragent@cursor.com>
184 lines
5.3 KiB
JavaScript
184 lines
5.3 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { cleanup, render, screen } from '@testing-library/react';
|
|
|
|
const mockRouterPush = vi.fn();
|
|
const mockRouterBack = vi.fn();
|
|
|
|
vi.mock('next/router', () => ({
|
|
useRouter: () => ({
|
|
push: mockRouterPush,
|
|
back: mockRouterBack,
|
|
pathname: '/scanner',
|
|
asPath: '/scanner',
|
|
query: {},
|
|
prefetch: vi.fn().mockResolvedValue(undefined),
|
|
events: { on: vi.fn(), off: vi.fn(), emit: vi.fn() },
|
|
}),
|
|
}));
|
|
|
|
vi.mock('next/link', () => ({
|
|
__esModule: true,
|
|
default: ({ href, children, ...rest }) => (
|
|
<a href={typeof href === 'string' ? href : ''} {...rest}>
|
|
{children}
|
|
</a>
|
|
),
|
|
}));
|
|
|
|
vi.mock('../../lib/theme-context', () => ({
|
|
useTheme: () => ({ theme: 'light', toggleTheme: vi.fn() }),
|
|
}));
|
|
|
|
const layoutPropsRef = { current: null };
|
|
|
|
vi.mock('../../components/Layout', () => ({
|
|
default: ({ children, chrome, user }) => {
|
|
layoutPropsRef.current = { chrome, user };
|
|
return (
|
|
<div data-testid="layout" data-chrome={chrome}>
|
|
{children}
|
|
</div>
|
|
);
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../components/scanner/ScannerCamera', () => ({
|
|
default: ({ variant }) => (
|
|
<div data-testid="scanner-camera" data-variant={variant} />
|
|
),
|
|
}));
|
|
|
|
vi.mock('../../components/scanner/ScannerCheckoutSheet', () => ({
|
|
default: () => <div data-testid="scanner-checkout-sheet" />,
|
|
}));
|
|
|
|
vi.mock('../../components/scanner/ScannerResultPanel', () => ({
|
|
default: () => <div data-testid="scanner-result-panel" />,
|
|
}));
|
|
|
|
vi.mock('../../components/scanner/ScannerHistoryStrip', () => ({
|
|
default: () => <div data-testid="scanner-history-strip" />,
|
|
TAB_RECENT: 'recent',
|
|
TAB_QUEUE: 'queue',
|
|
}));
|
|
|
|
vi.mock('../../components/scanner/ScannerTips', () => ({
|
|
default: () => <button type="button">Scanner Tips</button>,
|
|
}));
|
|
|
|
vi.mock('../../components/scanner/ScannerToast', () => ({
|
|
default: () => null,
|
|
}));
|
|
|
|
const queueFixture = {
|
|
scannedCards: [],
|
|
collections: [],
|
|
selectedCards: new Set(),
|
|
isProcessing: false,
|
|
addingCardIds: new Set(),
|
|
ownershipMap: {},
|
|
unprocessedCount: 0,
|
|
handleCardScanned: vi.fn(),
|
|
updateCardMetadata: vi.fn(),
|
|
addSingleCardToOwned: vi.fn(async () => true),
|
|
addSingleCardToCollection: vi.fn(async () => true),
|
|
commitSelectedToCollection: vi.fn(async () => []),
|
|
commitSelectedToOwned: vi.fn(async () => []),
|
|
clearScannedCards: vi.fn(),
|
|
};
|
|
|
|
const cameraFixture = {
|
|
videoRef: { current: null },
|
|
canvasRef: { current: null },
|
|
videoDevices: [{ deviceId: 'cam-1', label: 'FaceTime HD Camera' }],
|
|
selectedDeviceId: 'cam-1',
|
|
setSelectedDeviceId: vi.fn(),
|
|
devicePickerStatus: 'ready',
|
|
devicePickerMessage: '',
|
|
};
|
|
|
|
const identificationFixture = {
|
|
disambiguation: null,
|
|
isIdentifying: false,
|
|
identifyFromGalleryFile: vi.fn(async () => {}),
|
|
};
|
|
|
|
vi.mock('../../lib/use-auth', () => ({
|
|
useAuth: () => ({
|
|
user: { email: 'scanner@test.com', role: 'user' },
|
|
loading: false,
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../lib/use-scanner-session.js', () => ({
|
|
useScannerSession: () => ({
|
|
scanDefaults: { condition: 'NM', isFoil: false },
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../lib/use-scanner-queue.js', () => ({
|
|
useScannerQueue: () => queueFixture,
|
|
}));
|
|
|
|
vi.mock('../../lib/use-camera-scanner.js', () => ({
|
|
useCameraScanner: () => cameraFixture,
|
|
}));
|
|
|
|
vi.mock('../../lib/use-scanner-identification.js', () => ({
|
|
useScannerIdentification: () => identificationFixture,
|
|
}));
|
|
|
|
vi.mock('../../lib/scanner-session.js', () => ({
|
|
clearScannerCartStorage: vi.fn(),
|
|
}));
|
|
|
|
import Scanner from '../../pages/scanner.js';
|
|
|
|
function setMatchMedia(matches) {
|
|
window.matchMedia = vi.fn((query) => ({
|
|
matches,
|
|
media: query,
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
}));
|
|
}
|
|
|
|
describe('Scanner page viewport composition', () => {
|
|
beforeEach(() => {
|
|
layoutPropsRef.current = null;
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => cleanup());
|
|
|
|
it('uses immersive Layout chrome and hides workstation surfaces on mobile', () => {
|
|
setMatchMedia(false);
|
|
|
|
render(<Scanner />);
|
|
|
|
expect(layoutPropsRef.current?.chrome).toBe('immersive');
|
|
expect(screen.getByTestId('scanner-camera').getAttribute('data-variant')).toBe('default');
|
|
expect(screen.queryByTestId('scanner-result-panel')).toBeNull();
|
|
expect(screen.queryByTestId('scanner-history-strip')).toBeNull();
|
|
expect(screen.queryByTestId('scanner-checkout-sheet')).toBeNull();
|
|
expect(screen.queryByRole('button', { name: 'Scanner Tips' })).toBeNull();
|
|
});
|
|
|
|
it('uses default Layout chrome and mounts workstation surfaces at md+', () => {
|
|
setMatchMedia(true);
|
|
|
|
render(<Scanner />);
|
|
|
|
expect(layoutPropsRef.current?.chrome).toBe('default');
|
|
expect(screen.getByTestId('scanner-camera').getAttribute('data-variant')).toBe('workstation');
|
|
expect(screen.getByTestId('scanner-result-panel')).toBeTruthy();
|
|
expect(screen.getByTestId('scanner-history-strip')).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: 'Scanner Tips' })).toBeTruthy();
|
|
expect(screen.getByLabelText('Camera')).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: 'Upload Image' })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: 'Batch Scan' })).toBeTruthy();
|
|
expect(screen.getByRole('switch', { name: 'Auto-detect' })).toBeTruthy();
|
|
expect(screen.queryByTestId('scanner-checkout-sheet')).toBeNull();
|
|
});
|
|
});
|