deckhearth/test/lib/use-scanner-queue.test.js

226 lines
6.4 KiB
JavaScript
Raw Normal View History

// @vitest-environment jsdom
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
const routeApiMocks = vi.hoisted(() => ({
addScannedCardToOwned: vi.fn().mockResolvedValue(undefined),
addScannedCardToCollection: vi.fn().mockResolvedValue(undefined),
addScannedCardToDeck: vi.fn().mockResolvedValue(undefined),
routeScannedCardToDestination: vi.fn().mockResolvedValue(undefined),
fetchScannerCollections: vi.fn().mockResolvedValue([]),
fetchScannerDecks: vi.fn().mockResolvedValue([]),
fetchBatchOwnership: vi.fn().mockResolvedValue({}),
createScannerCollection: vi.fn(),
}));
vi.mock('../../lib/scanner-route-api.js', () => ({
addScannedCardToOwned: routeApiMocks.addScannedCardToOwned,
addScannedCardToCollection: routeApiMocks.addScannedCardToCollection,
addScannedCardToDeck: routeApiMocks.addScannedCardToDeck,
routeScannedCardToDestination: routeApiMocks.routeScannedCardToDestination,
fetchScannerCollections: routeApiMocks.fetchScannerCollections,
fetchScannerDecks: routeApiMocks.fetchScannerDecks,
fetchBatchOwnership: routeApiMocks.fetchBatchOwnership,
createScannerCollection: routeApiMocks.createScannerCollection,
}));
import { useScannerQueue } from '../../lib/use-scanner-queue.js';
import { clearScannerCartStorage } from '../../lib/scanner-session.js';
const {
addScannedCardToOwned,
routeScannedCardToDestination,
} = routeApiMocks;
const scanDefaults = { condition: 'NM', isFoil: false };
const user = { userId: 1, email: 'test@example.com', role: 'user' };
describe('useScannerQueue — cart model', () => {
beforeEach(() => {
vi.clearAllMocks();
sessionStorage.clear();
});
afterEach(() => {
clearScannerCartStorage();
});
it('handleCardScanned enqueues locally without route helpers', async () => {
const { result } = renderHook(() =>
useScannerQueue({
user,
sessionDestination: { type: 'owned', id: null, label: 'My Collection' },
scanDefaults,
deckMode: null,
})
);
await act(async () => {
result.current.handleCardScanned({
name: 'Pikachu',
set: 'Base',
databaseId: 101,
});
});
expect(routeScannedCardToDestination).not.toHaveBeenCalled();
expect(addScannedCardToOwned).not.toHaveBeenCalled();
expect(result.current.scannedCards).toHaveLength(1);
const card = result.current.scannedCards[0];
expect(card).toMatchObject({
name: 'Pikachu',
set: 'Base',
processed: false,
});
expect(card.id).toBeTruthy();
expect(result.current.selectedCards.has(card.id)).toBe(true);
});
it('handleBulkAction owned commits each selected card and removes them from the cart', async () => {
const { result } = renderHook(() =>
useScannerQueue({
user,
sessionDestination: null,
scanDefaults,
deckMode: null,
})
);
await act(async () => {
result.current.handleCardScanned({
name: 'Bolt',
set: 'Alpha',
databaseId: 201,
});
});
const cardId = result.current.scannedCards[0].id;
await act(async () => {
await result.current.handleBulkAction('owned');
});
expect(addScannedCardToOwned).toHaveBeenCalledTimes(1);
expect(addScannedCardToOwned).toHaveBeenCalledWith(
expect.objectContaining({ id: cardId, name: 'Bolt', set: 'Alpha' })
);
expect(result.current.scannedCards).toHaveLength(0);
expect(result.current.selectedCards.size).toBe(0);
});
it('commitSelectedToOwned returns successful ids instead of reading stale queue state', async () => {
const { result } = renderHook(() =>
useScannerQueue({
user,
sessionDestination: null,
scanDefaults,
deckMode: null,
})
);
await act(async () => {
result.current.handleCardScanned({
name: 'Jace',
set: 'Origins',
databaseId: 301,
});
});
const cardId = result.current.scannedCards[0].id;
let successfulIds;
await act(async () => {
successfulIds = await result.current.commitSelectedToOwned();
});
expect(successfulIds).toEqual([cardId]);
expect(addScannedCardToOwned).toHaveBeenCalledTimes(1);
expect(result.current.scannedCards).toHaveLength(0);
});
it('commitSelectedToOwned returns an empty array when the API fails', async () => {
addScannedCardToOwned.mockRejectedValueOnce(new Error('network'));
const { result } = renderHook(() =>
useScannerQueue({
user,
sessionDestination: null,
scanDefaults,
deckMode: null,
})
);
await act(async () => {
result.current.handleCardScanned({
name: 'Jace',
set: 'Origins',
databaseId: 301,
});
});
let successfulIds;
await act(async () => {
successfulIds = await result.current.commitSelectedToOwned();
});
expect(successfulIds).toEqual([]);
expect(result.current.scannedCards).toHaveLength(1);
});
it('commitSelectedToOwned routes through handleBulkAction owned', async () => {
const { result } = renderHook(() =>
useScannerQueue({
user,
sessionDestination: null,
scanDefaults,
deckMode: null,
})
);
await act(async () => {
result.current.handleCardScanned({
name: 'Jace',
set: 'Origins',
databaseId: 301,
});
});
await act(async () => {
await result.current.commitSelectedToOwned();
});
expect(addScannedCardToOwned).toHaveBeenCalledTimes(1);
expect(result.current.scannedCards).toHaveLength(0);
});
it('persists cart state to sessionStorage on scan', async () => {
const { result } = renderHook(() =>
useScannerQueue({
user,
sessionDestination: null,
scanDefaults,
deckMode: null,
})
);
await act(async () => {
result.current.handleCardScanned({
name: 'Liliana',
set: 'Midnight',
databaseId: 401,
});
});
const cardId = result.current.scannedCards[0].id;
await waitFor(() => {
const raw = sessionStorage.getItem('deckhearth:scanner-cart');
expect(raw).toBeTruthy();
const parsed = JSON.parse(raw);
expect(parsed.scannedCards).toHaveLength(1);
expect(parsed.selectedCardIds).toEqual([cardId]);
});
expect(result.current.unprocessedCount).toBe(1);
});
});