diff --git a/e2e/unit/db.index.spec.ts b/e2e/unit/db.index.spec.ts index c35eb5d..8f76107 100644 --- a/e2e/unit/db.index.spec.ts +++ b/e2e/unit/db.index.spec.ts @@ -59,7 +59,7 @@ describe('db/index initializeDatabase', () => { const { db } = await import('../../src/db'); expect(version).toHaveBeenCalled(); - expect((version as any).mock.calls.length).toBeGreaterThanOrEqual(1); + expect(version.mock.calls.length).toBeGreaterThanOrEqual(1); expect(db).toBeDefined(); }); }); diff --git a/e2e/unit/db.migrations.spec.ts b/e2e/unit/db.migrations.spec.ts index 8c53bc2..105180d 100644 --- a/e2e/unit/db.migrations.spec.ts +++ b/e2e/unit/db.migrations.spec.ts @@ -1,4 +1,5 @@ import { describe, expect, test, vi, beforeEach } from 'vitest'; +import type { StockFillDB } from '../../src/db'; import { makeMockDb } from '../utils/mockDb'; vi.mock('uuid', () => ({ v4: vi.fn(() => 'mock-uuid') })); @@ -11,15 +12,13 @@ describe('applyMigrations', () => { test('creates category for product name and updates product to use new id', async () => { const db = makeMockDb(); - (db.categories.toArray as any) + db.categories.toArray .mockResolvedValueOnce([]) .mockResolvedValueOnce([{ id: 'mock-uuid', name: 'Beverages', created_at: 1, updated_at: 1 }]); - (db.products.toArray as any).mockResolvedValueOnce([ - { id: 'p1', category: 'Beverages', name: 'Cola' }, - ]); + db.products.toArray.mockResolvedValueOnce([{ id: 'p1', category: 'Beverages', name: 'Cola' }]); const { applyMigrations } = await import('../../src/db/migrations'); - await applyMigrations(db as any); + await applyMigrations(db as unknown as StockFillDB); expect(db.categories.add).toHaveBeenCalledWith( expect.objectContaining({ id: 'mock-uuid', name: 'Beverages' }), @@ -29,15 +28,13 @@ describe('applyMigrations', () => { test('skips update when product category already matches existing id', async () => { const db = makeMockDb(); - (db.categories.toArray as any) + db.categories.toArray .mockResolvedValueOnce([{ id: 'cat-1', name: 'Snacks' }]) .mockResolvedValueOnce([{ id: 'cat-1', name: 'Snacks' }]); - (db.products.toArray as any).mockResolvedValueOnce([ - { id: 'p2', category: 'cat-1', name: 'Chips' }, - ]); + db.products.toArray.mockResolvedValueOnce([{ id: 'p2', category: 'cat-1', name: 'Chips' }]); const { applyMigrations } = await import('../../src/db/migrations'); - await applyMigrations(db as any); + await applyMigrations(db as unknown as StockFillDB); expect(db.categories.add).not.toHaveBeenCalled(); expect(db.products.update).not.toHaveBeenCalled(); @@ -45,16 +42,14 @@ describe('applyMigrations', () => { test('normalizes pickList categories names to ids', async () => { const db = makeMockDb(); - (db.categories.toArray as any) + db.categories.toArray .mockResolvedValueOnce([{ id: 'cat-fruit', name: 'Fruit' }]) .mockResolvedValueOnce([{ id: 'cat-fruit', name: 'Fruit' }]); - (db.products.toArray as any).mockResolvedValueOnce([]); - (db.pickLists.toArray as any).mockResolvedValueOnce([ - { id: 'pl-1', categories: ['Fruit'] }, - ]); + db.products.toArray.mockResolvedValueOnce([]); + db.pickLists.toArray.mockResolvedValueOnce([{ id: 'pl-1', categories: ['Fruit'] }]); const { applyMigrations } = await import('../../src/db/migrations'); - await applyMigrations(db as any); + await applyMigrations(db as unknown as StockFillDB); expect(db.pickLists.update).toHaveBeenCalledWith('pl-1', { categories: ['cat-fruit'] }); }); diff --git a/e2e/unit/useBarcodeScanner.spec.tsx b/e2e/unit/useBarcodeScanner.spec.tsx index ee16fe9..534b73f 100644 --- a/e2e/unit/useBarcodeScanner.spec.tsx +++ b/e2e/unit/useBarcodeScanner.spec.tsx @@ -3,11 +3,18 @@ import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'; vi.mock('@zxing/browser', () => { class MockReader { - decodeFromVideoDevice = vi.fn(async (_device: any, _video: any, callback: any) => { - callback({ getText: () => 'decoded-fallback' } as any); - return { stop: vi.fn() } as any; - }); + decodeFromVideoDevice = vi.fn( + async ( + _device: string | undefined, + _video: HTMLVideoElement, + callback: (result: { getText: () => string }) => void, + ) => { + callback({ getText: () => 'decoded-fallback' }); + return { stop: vi.fn() }; + }, + ); } + return { BrowserMultiFormatReader: MockReader }; }); @@ -15,11 +22,15 @@ describe('useBarcodeScanner', () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); - (globalThis as any).BarcodeDetector = undefined; + const globalWithBarcode = globalThis as typeof globalThis & { + BarcodeDetector?: new () => { detect: (source: ImageBitmap | HTMLCanvasElement) => Promise> }; + }; + globalWithBarcode.BarcodeDetector = undefined; }); afterEach(() => { - delete (globalThis as any).BarcodeDetector; + const globalWithBarcode = globalThis as typeof globalThis & { BarcodeDetector?: unknown }; + delete globalWithBarcode.BarcodeDetector; }); test('falls back to ZXing reader when BarcodeDetector is unavailable', async () => { @@ -35,16 +46,22 @@ describe('useBarcodeScanner', () => { class MockBarcodeDetector { detect = detect; } - (globalThis as any).BarcodeDetector = MockBarcodeDetector as any; + const globalWithBarcode = globalThis as typeof globalThis & { BarcodeDetector?: typeof MockBarcodeDetector }; + globalWithBarcode.BarcodeDetector = MockBarcodeDetector; const play = vi.fn().mockResolvedValue(undefined); const getVideoTracks = vi.fn().mockReturnValue([{ stop: vi.fn() }]); const getTracks = vi.fn().mockReturnValue([{ stop: vi.fn() }]); - const stream = { getVideoTracks, getTracks } as any; - navigator.mediaDevices = { - getUserMedia: vi.fn().mockResolvedValue(stream), - } as any; - (globalThis as any).createImageBitmap = vi.fn().mockResolvedValue({}); + const stream = { getVideoTracks, getTracks }; + Object.defineProperty(navigator, 'mediaDevices', { + value: { + getUserMedia: vi.fn().mockResolvedValue(stream), + }, + configurable: true, + }); + const createImageBitmap = vi.fn<[], Promise>().mockResolvedValue({} as ImageBitmap); + const globalWithBitmap = globalThis as typeof globalThis & { createImageBitmap?: typeof createImageBitmap }; + globalWithBitmap.createImageBitmap = createImageBitmap; const { useBarcodeScanner } = await import('../../src/hooks/useBarcodeScanner'); const { result } = renderHook(() => useBarcodeScanner()); diff --git a/e2e/utils/mockDb.ts b/e2e/utils/mockDb.ts index b83b172..252ae9a 100644 --- a/e2e/utils/mockDb.ts +++ b/e2e/utils/mockDb.ts @@ -1,45 +1,77 @@ import { vi } from 'vitest'; -export type TableMock = { - toArray: ReturnType; - add: ReturnType; - update: ReturnType; - where: ReturnType; - filter: ReturnType; - count: ReturnType; +type WhereClause = { + equals: ReturnType>> }>>; }; -const createWhere = () => ({ - equals: vi.fn().mockReturnValue({ first: vi.fn().mockResolvedValue(undefined) }), +type FilterClause = { + first: ReturnType>>; +}; + +export type TableMock = { + toArray: ReturnType>>; + add: ReturnType>>; + update: ReturnType], Promise>>; + where: ReturnType>>; + filter: ReturnType boolean], FilterClause>>; + count: ReturnType>>; +}; + +const createWhere = (): WhereClause => ({ + equals: vi.fn<[T], { first: ReturnType>> }>().mockReturnValue({ + first: vi.fn<[], Promise>().mockResolvedValue(undefined), + }), }); -export const makeTableMock = (items: T[] = []): TableMock => ({ - toArray: vi.fn().mockResolvedValue([...items]), - add: vi.fn().mockResolvedValue(undefined), - update: vi.fn().mockResolvedValue(undefined), - where: vi.fn().mockImplementation(() => createWhere()), - filter: vi.fn().mockReturnValue({ first: vi.fn().mockResolvedValue(undefined) }), - count: vi.fn().mockResolvedValue(0), +export const makeTableMock = (items: T[] = []): TableMock => ({ + toArray: vi.fn<[], Promise>().mockResolvedValue([...items]), + add: vi.fn<[T], Promise>().mockResolvedValue(undefined), + update: vi.fn<[string, Partial], Promise>().mockResolvedValue(undefined), + where: vi.fn<[keyof T], WhereClause>().mockImplementation(() => createWhere()), + filter: vi + .fn<[(item: T) => boolean], FilterClause>() + .mockReturnValue({ first: vi.fn<[], Promise>().mockResolvedValue(undefined) }), + count: vi.fn<[], Promise>().mockResolvedValue(0), }); -export const makeMockDb = (overrides: Partial> = {}) => { +type MockDbOverrides = { + products: unknown[]; + categories: unknown[]; + pickLists: unknown[]; + pickItems: unknown[]; + importExportLogs: unknown[]; +}; + +type TransactionMock = ReturnType>>; + +type MockDb = { + products: TableMock; + categories: TableMock; + pickLists: TableMock; + pickItems: TableMock; + importExportLogs: TableMock; + transaction: TransactionMock; + open: ReturnType>>; +}; + +export const makeMockDb = (overrides: Partial = {}): MockDb => { const products = makeTableMock(overrides.products ?? []); const categories = makeTableMock(overrides.categories ?? []); const pickLists = makeTableMock(overrides.pickLists ?? []); const pickItems = makeTableMock(overrides.pickItems ?? []); const importExportLogs = makeTableMock(overrides.importExportLogs ?? []); - const transaction = vi - .fn() - .mockImplementation(async (_mode: any, ...args: any[]) => { + const transaction = vi.fn<[string, ...unknown[]], Promise>().mockImplementation( + async (_mode, ...args) => { const cb = args[args.length - 1]; if (typeof cb === 'function') { return cb(); } return undefined; - }); + }, + ); - const open = vi.fn().mockResolvedValue(undefined); + const open = vi.fn<[], Promise>().mockResolvedValue(undefined); return { products,