Merge pull request #102 from beatz174-bit/codex/mock-barcode-scanner-dependencies-for-tests
Add unit tests for useBarcodeScanner hook
This commit is contained in:
1 file changed
+128
@@ -0,0 +1,128 @@
|
|||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import { describe, it, beforeEach, afterEach, expect, vi, type MockInstance } from 'vitest';
|
||||||
|
import { useBarcodeScanner } from '../useBarcodeScanner';
|
||||||
|
|
||||||
|
const decodeFromVideoDevice = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('@zxing/browser', () => ({
|
||||||
|
BrowserMultiFormatReader: vi.fn().mockImplementation(() => ({
|
||||||
|
decodeFromVideoDevice,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
BarcodeDetector?: new () => { detect: (source: ImageBitmapSource) => Promise<{ rawValue: string }[]> };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMediaStream = (stopSpy: MockInstance = vi.fn()) => {
|
||||||
|
const track = { stop: stopSpy } as MediaStreamTrack;
|
||||||
|
return {
|
||||||
|
getTracks: vi.fn(() => [track]),
|
||||||
|
getVideoTracks: vi.fn(() => [track]),
|
||||||
|
} as unknown as MediaStream;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createVideoElement = () => {
|
||||||
|
let srcObject: MediaStream | null = null;
|
||||||
|
return {
|
||||||
|
play: vi.fn(() => Promise.resolve()),
|
||||||
|
get srcObject() {
|
||||||
|
return srcObject;
|
||||||
|
},
|
||||||
|
set srcObject(value: MediaStream | null) {
|
||||||
|
srcObject = value;
|
||||||
|
},
|
||||||
|
} as unknown as HTMLVideoElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
decodeFromVideoDevice.mockReset();
|
||||||
|
(globalThis as unknown as { createImageBitmap: typeof createImageBitmap }).createImageBitmap = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({} as ImageBitmap);
|
||||||
|
Object.assign(navigator, {
|
||||||
|
mediaDevices: {
|
||||||
|
getUserMedia: vi.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
delete (window as Window & { BarcodeDetector?: unknown }).BarcodeDetector;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useBarcodeScanner', () => {
|
||||||
|
it('uses native BarcodeDetector when available and sets the detected code', async () => {
|
||||||
|
const trackStop = vi.fn();
|
||||||
|
const stream = createMediaStream(trackStop);
|
||||||
|
(navigator.mediaDevices.getUserMedia as unknown as vi.Mock).mockResolvedValue(stream);
|
||||||
|
|
||||||
|
class MockBarcodeDetector {
|
||||||
|
detect = vi.fn(async () => [{ rawValue: 'NATIVE-123' }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as Window).BarcodeDetector = MockBarcodeDetector;
|
||||||
|
const videoElement = createVideoElement();
|
||||||
|
|
||||||
|
const { result, unmount } = renderHook(() => {
|
||||||
|
const hook = useBarcodeScanner();
|
||||||
|
hook.videoRef.current = videoElement;
|
||||||
|
return hook;
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.result.code).toBe('NATIVE-123'));
|
||||||
|
|
||||||
|
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ video: { facingMode: 'environment' } });
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
expect(trackStop).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to BrowserMultiFormatReader and returns decoded text', async () => {
|
||||||
|
delete (window as Window).BarcodeDetector;
|
||||||
|
const stopControls = vi.fn();
|
||||||
|
|
||||||
|
decodeFromVideoDevice.mockImplementation(async (_deviceId, _video, callback) => {
|
||||||
|
callback({ getText: () => 'FALLBACK-789' } as unknown as { getText: () => string });
|
||||||
|
return { stop: stopControls };
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoElement = createVideoElement();
|
||||||
|
|
||||||
|
const { result, unmount } = renderHook(() => {
|
||||||
|
const hook = useBarcodeScanner();
|
||||||
|
hook.videoRef.current = videoElement;
|
||||||
|
return hook;
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(decodeFromVideoDevice).toHaveBeenCalled();
|
||||||
|
expect(result.current.result.code).toBe('FALLBACK-789');
|
||||||
|
});
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
expect(stopControls).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('surfaces errors encountered while starting the scanner', async () => {
|
||||||
|
(navigator.mediaDevices.getUserMedia as unknown as vi.Mock).mockRejectedValue(new Error('Camera denied'));
|
||||||
|
|
||||||
|
class MockBarcodeDetector {
|
||||||
|
detect = vi.fn(async () => []);
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as Window).BarcodeDetector = MockBarcodeDetector;
|
||||||
|
const videoElement = createVideoElement();
|
||||||
|
|
||||||
|
const { result } = renderHook(() => {
|
||||||
|
const hook = useBarcodeScanner();
|
||||||
|
hook.videoRef.current = videoElement;
|
||||||
|
return hook;
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.result.error).toBe('Camera denied'));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user