deckhearth/test/components/ScannerCheckoutSheet.test.js
varutasu 73424aae59
Mobile scanner checkout: scan first, commit later (#157)
* Start scanner-mobile-checkout convoy for the cart-then-commit phone flow.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Ship a cart-then-commit mobile scanner so phone sessions stay on the camera.

Scan matches enqueue locally instead of auto-writing ownership, checkout happens in a sheet, and audit fixes cover stale commit detection, returnUrl open redirects, nested Escape, and ember detection chrome.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 20:20:43 -05:00

159 lines
4.2 KiB
JavaScript

// @vitest-environment jsdom
import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
import ScannerCheckoutSheet, { ScannerCheckoutContent } from '../../components/scanner/ScannerCheckoutSheet';
import { VOCAB } from '../../lib/collection-vocabulary.js';
function createQueueFixture({
scannedCards = [
{
id: 'card-1',
name: 'Lightning Bolt',
set: 'Alpha',
cardNumber: '161',
quantity: 1,
condition: 'NM',
isFoil: false,
processed: false,
confidence: 0.92,
},
{
id: 'card-2',
name: 'Counterspell',
set: 'Beta',
cardNumber: '54',
quantity: 1,
condition: 'NM',
isFoil: false,
processed: false,
confidence: 0.55,
},
],
selectedCardIds = [],
} = {}) {
const selectedCards = new Set(selectedCardIds);
return {
scannedCards,
collections: [{ id: 1, name: 'Trade Binder', is_system_collection: false }],
selectedCards,
isProcessing: false,
addingCardIds: new Set(),
toggleCardSelection: vi.fn((cardId) => {
if (selectedCards.has(cardId)) {
selectedCards.delete(cardId);
} else {
selectedCards.add(cardId);
}
}),
incrementCardQuantity: vi.fn(),
decrementCardQuantity: vi.fn(),
updateCardMetadata: vi.fn(),
removeScannedCard: vi.fn(),
commitSelectedToOwned: vi.fn(async () => {}),
commitSelectedToCollection: vi.fn(async () => {}),
};
}
describe('ScannerCheckoutSheet', () => {
afterEach(() => cleanup());
it('renders header count for scanned cards', () => {
const queue = createQueueFixture();
render(
<ScannerCheckoutContent
queue={queue}
onOpenListPicker={vi.fn()}
/>
);
expect(screen.getByText('2 cards scanned')).toBeTruthy();
});
it('disables primary action when nothing is selected', () => {
const queue = createQueueFixture({ selectedCardIds: [] });
queue.toggleCardSelection = vi.fn();
render(
<ScannerCheckoutContent
queue={queue}
onOpenListPicker={vi.fn()}
/>
);
const primaryButton = screen.getByRole('button', {
name: VOCAB.ADD_TO_MY_COLLECTION,
});
expect(primaryButton.disabled).toBe(true);
});
it('shows vocab labels on checkout actions', () => {
const queue = createQueueFixture({ selectedCardIds: ['card-1'] });
render(
<ScannerCheckoutSheet
queue={queue}
onClose={vi.fn()}
onOpenListPicker={vi.fn()}
/>
);
expect(
screen.getByRole('button', { name: new RegExp(VOCAB.ADD_TO_MY_COLLECTION) })
).toBeTruthy();
expect(screen.getByRole('button', { name: VOCAB.ADD_TO_LIST })).toBeTruthy();
});
it('renders checkout sheet dialog without crashing', () => {
const queue = createQueueFixture({ selectedCardIds: ['card-1'] });
expect(() => {
render(
<ScannerCheckoutSheet
queue={queue}
onClose={vi.fn()}
onOpenListPicker={vi.fn()}
/>
);
}).not.toThrow();
expect(screen.getByRole('dialog')).toBeTruthy();
});
it('does not show an error banner when commit returns successful ids', async () => {
const queue = createQueueFixture({ selectedCardIds: ['card-1'] });
queue.commitSelectedToOwned = vi.fn(async () => ['card-1']);
render(
<ScannerCheckoutContent
queue={queue}
onOpenListPicker={vi.fn()}
/>
);
fireEvent.click(
screen.getByRole('button', { name: new RegExp(VOCAB.ADD_TO_MY_COLLECTION) })
);
await waitFor(() => {
expect(queue.commitSelectedToOwned).toHaveBeenCalledTimes(1);
});
expect(screen.queryByRole('alert')).toBeNull();
});
it('shows an error banner when commit returns no successful ids', async () => {
const queue = createQueueFixture({ selectedCardIds: ['card-1'] });
queue.commitSelectedToOwned = vi.fn(async () => []);
render(
<ScannerCheckoutContent
queue={queue}
onOpenListPicker={vi.fn()}
/>
);
fireEvent.click(
screen.getByRole('button', { name: new RegExp(VOCAB.ADD_TO_MY_COLLECTION) })
);
expect(await screen.findByRole('alert')).toBeTruthy();
});
});