fix: dispatch ADD_START, correlate preview/lookup responses with the current attempt
This commit is contained in:
@@ -3,7 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import ScanPage from '../src/pages/ScanPage.js'
|
||||
import type { Candidate, ReleasePreview } from '../src/types.js'
|
||||
import type { Candidate, Item, ReleasePreview } from '../src/types.js'
|
||||
|
||||
vi.mock('../src/components/Scanner.js', () => ({
|
||||
default: ({ onDetect }: { onDetect: (code: string) => void }) => (
|
||||
@@ -48,6 +48,25 @@ const preview: ReleasePreview = {
|
||||
}
|
||||
|
||||
// The api module is mocked directly, so mocks resolve with parsed bodies.
|
||||
const addedItem: Item = {
|
||||
id: 1,
|
||||
discogsReleaseId: 1001,
|
||||
title: 'Motion',
|
||||
artist: 'The Cinematic Orchestra',
|
||||
year: 1999,
|
||||
formats: ['CD'],
|
||||
genres: [],
|
||||
labels: ['Ninja Tune'],
|
||||
tracklist: [],
|
||||
catno: 'ZENCD012',
|
||||
country: 'UK',
|
||||
artworkUrl: null,
|
||||
barcodes: ['5021592210629'],
|
||||
dateAdded: '2026-08-29',
|
||||
ripOverride: null,
|
||||
ripStatus: 'not_ripped',
|
||||
}
|
||||
|
||||
function jsonOk(body: unknown) {
|
||||
return Promise.resolve(body)
|
||||
}
|
||||
@@ -70,26 +89,7 @@ describe('ScanPage flow', () => {
|
||||
it('scan → candidates → confirm → added', async () => {
|
||||
vi.mocked(api.lookupBarcode).mockResolvedValue(jsonOk({ candidates: [candidate] }) as never)
|
||||
vi.mocked(api.getReleasePreview).mockResolvedValue(jsonOk(preview) as never)
|
||||
vi.mocked(api.addToCollection).mockResolvedValue(
|
||||
jsonOk({
|
||||
id: 1,
|
||||
discogsReleaseId: 1001,
|
||||
title: 'Motion',
|
||||
artist: 'The Cinematic Orchestra',
|
||||
year: 1999,
|
||||
formats: ['CD'],
|
||||
genres: [],
|
||||
labels: ['Ninja Tune'],
|
||||
tracklist: [],
|
||||
catno: 'ZENCD012',
|
||||
country: 'UK',
|
||||
artworkUrl: null,
|
||||
barcodes: ['5021592210629'],
|
||||
dateAdded: '2026-08-29',
|
||||
ripOverride: null,
|
||||
ripStatus: 'not_ripped',
|
||||
}) as never
|
||||
)
|
||||
vi.mocked(api.addToCollection).mockResolvedValue(jsonOk(addedItem) as never)
|
||||
|
||||
renderScan()
|
||||
await userEvent.click(screen.getByRole('button', { name: 'fake-scan' }))
|
||||
@@ -158,4 +158,23 @@ describe('ScanPage flow', () => {
|
||||
await waitFor(() => expect(screen.getByText(/discogs token/i)).toBeTruthy())
|
||||
expect(screen.getByRole('link', { name: /settings/i }).getAttribute('href')).toBe('/settings')
|
||||
})
|
||||
|
||||
it('does not add twice when the button is clicked rapidly', async () => {
|
||||
let resolveAdd: (v: unknown) => void = () => {}
|
||||
vi.mocked(api.lookupBarcode).mockResolvedValue(jsonOk({ candidates: [candidate] }) as never)
|
||||
vi.mocked(api.getReleasePreview).mockResolvedValue(jsonOk(preview) as never)
|
||||
vi.mocked(api.addToCollection).mockImplementation(
|
||||
() => new Promise((resolve) => { resolveAdd = resolve }) as never
|
||||
)
|
||||
renderScan()
|
||||
await userEvent.click(screen.getByRole('button', { name: 'fake-scan' }))
|
||||
await userEvent.click(await screen.findByRole('button', { name: /motion/i }))
|
||||
const addBtn = await screen.findByRole('button', { name: /add to collection/i })
|
||||
await userEvent.click(addBtn)
|
||||
// adding=true → button is disabled ('Adding…'); a second rapid click must not re-fire the request
|
||||
await userEvent.click(addBtn).catch(() => {})
|
||||
resolveAdd(addedItem)
|
||||
await waitFor(() => expect(screen.getByText(/added to collection/i)).toBeTruthy())
|
||||
expect(api.addToCollection).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user