import { describe, expect, it, vi } from 'vitest'; import { runSequentialGalleryIdentify } from '../../lib/scanner-batch-identify.js'; function makeFile(name) { return new File(['x'], name, { type: 'image/jpeg' }); } describe('runSequentialGalleryIdentify', () => { it('processes files one-at-a-time in order on success', async () => { const files = [makeFile('a.jpg'), makeFile('b.jpg'), makeFile('c.jpg')]; const callOrder = []; const progressEvents = []; const successEvents = []; const identifyFn = vi.fn(async (file) => { callOrder.push(file.name); }); const { results, cancelled } = await runSequentialGalleryIdentify(files, identifyFn, { onProgress: (p) => progressEvents.push({ current: p.current, total: p.total, name: p.file.name }), onFileSuccess: ({ file, index }) => successEvents.push({ name: file.name, index }), }); expect(cancelled).toBe(false); expect(callOrder).toEqual(['a.jpg', 'b.jpg', 'c.jpg']); expect(identifyFn).toHaveBeenCalledTimes(3); expect(progressEvents).toEqual([ { current: 1, total: 3, name: 'a.jpg' }, { current: 2, total: 3, name: 'b.jpg' }, { current: 3, total: 3, name: 'c.jpg' }, ]); expect(successEvents).toEqual([ { name: 'a.jpg', index: 0 }, { name: 'b.jpg', index: 1 }, { name: 'c.jpg', index: 2 }, ]); expect(results).toHaveLength(3); expect(results.every((r) => r.ok === true)).toBe(true); expect(results.map((r) => r.file.name)).toEqual(['a.jpg', 'b.jpg', 'c.jpg']); }); it('stops remaining files when cancelRef is set mid-batch', async () => { const files = [makeFile('a.jpg'), makeFile('b.jpg'), makeFile('c.jpg')]; const cancelRef = { current: false }; const identifyFn = vi.fn(async (file) => { if (file.name === 'a.jpg') { cancelRef.current = true; } }); const { results, cancelled } = await runSequentialGalleryIdentify(files, identifyFn, { cancelRef, }); expect(cancelled).toBe(true); expect(identifyFn).toHaveBeenCalledTimes(1); expect(results).toEqual([{ file: files[0], ok: true }]); }); it('invokes onFileError and continues after a per-file failure', async () => { const files = [makeFile('a.jpg'), makeFile('b.jpg'), makeFile('c.jpg')]; const err = new Error('identify failed'); const errorEvents = []; const callOrder = []; const identifyFn = vi.fn(async (file) => { callOrder.push(file.name); if (file.name === 'b.jpg') { throw err; } }); const { results, cancelled } = await runSequentialGalleryIdentify(files, identifyFn, { onFileError: ({ file, index, error }) => { errorEvents.push({ name: file.name, index, error }); }, }); expect(cancelled).toBe(false); expect(callOrder).toEqual(['a.jpg', 'b.jpg', 'c.jpg']); expect(identifyFn).toHaveBeenCalledTimes(3); expect(errorEvents).toEqual([{ name: 'b.jpg', index: 1, error: err }]); expect(results).toHaveLength(3); expect(results[0]).toMatchObject({ file: files[0], ok: true }); expect(results[1]).toMatchObject({ file: files[1], ok: false, error: err }); expect(results[2]).toMatchObject({ file: files[2], ok: true }); }); it('accepts FileList-like iterables via Array.from', async () => { const files = [makeFile('solo.jpg')]; const fileList = { length: files.length, 0: files[0], [Symbol.iterator]() { let i = 0; return { next: () => { if (i < files.length) { return { value: files[i++], done: false }; } return { done: true }; }, }; }, }; const identifyFn = vi.fn(async () => {}); const { results } = await runSequentialGalleryIdentify(fileList, identifyFn); expect(identifyFn).toHaveBeenCalledTimes(1); expect(results).toEqual([{ file: files[0], ok: true }]); }); it('returns empty results for null/undefined files', async () => { const identifyFn = vi.fn(async () => {}); const { results, cancelled } = await runSequentialGalleryIdentify(null, identifyFn); expect(cancelled).toBe(false); expect(results).toEqual([]); expect(identifyFn).not.toHaveBeenCalled(); }); });