* 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>
159 lines
4.2 KiB
JavaScript
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();
|
|
});
|
|
});
|