Fix lint issues in unit test helpers

This commit is contained in:
beatz174-bit
2025-12-02 18:41:17 +10:00
parent f807857bec
commit 1030476ed1
4 changed files with 95 additions and 51 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ describe('db/index initializeDatabase', () => {
const { db } = await import('../../src/db'); const { db } = await import('../../src/db');
expect(version).toHaveBeenCalled(); expect(version).toHaveBeenCalled();
expect((version as any).mock.calls.length).toBeGreaterThanOrEqual(1); expect(version.mock.calls.length).toBeGreaterThanOrEqual(1);
expect(db).toBeDefined(); expect(db).toBeDefined();
}); });
}); });
+11 -16
View File
@@ -1,4 +1,5 @@
import { describe, expect, test, vi, beforeEach } from 'vitest'; import { describe, expect, test, vi, beforeEach } from 'vitest';
import type { StockFillDB } from '../../src/db';
import { makeMockDb } from '../utils/mockDb'; import { makeMockDb } from '../utils/mockDb';
vi.mock('uuid', () => ({ v4: vi.fn(() => 'mock-uuid') })); 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 () => { test('creates category for product name and updates product to use new id', async () => {
const db = makeMockDb(); const db = makeMockDb();
(db.categories.toArray as any) db.categories.toArray
.mockResolvedValueOnce([]) .mockResolvedValueOnce([])
.mockResolvedValueOnce([{ id: 'mock-uuid', name: 'Beverages', created_at: 1, updated_at: 1 }]); .mockResolvedValueOnce([{ id: 'mock-uuid', name: 'Beverages', created_at: 1, updated_at: 1 }]);
(db.products.toArray as any).mockResolvedValueOnce([ db.products.toArray.mockResolvedValueOnce([{ id: 'p1', category: 'Beverages', name: 'Cola' }]);
{ id: 'p1', category: 'Beverages', name: 'Cola' },
]);
const { applyMigrations } = await import('../../src/db/migrations'); 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(db.categories.add).toHaveBeenCalledWith(
expect.objectContaining({ id: 'mock-uuid', name: 'Beverages' }), expect.objectContaining({ id: 'mock-uuid', name: 'Beverages' }),
@@ -29,15 +28,13 @@ describe('applyMigrations', () => {
test('skips update when product category already matches existing id', async () => { test('skips update when product category already matches existing id', async () => {
const db = makeMockDb(); const db = makeMockDb();
(db.categories.toArray as any) db.categories.toArray
.mockResolvedValueOnce([{ id: 'cat-1', name: 'Snacks' }]) .mockResolvedValueOnce([{ id: 'cat-1', name: 'Snacks' }])
.mockResolvedValueOnce([{ id: 'cat-1', name: 'Snacks' }]); .mockResolvedValueOnce([{ id: 'cat-1', name: 'Snacks' }]);
(db.products.toArray as any).mockResolvedValueOnce([ db.products.toArray.mockResolvedValueOnce([{ id: 'p2', category: 'cat-1', name: 'Chips' }]);
{ id: 'p2', category: 'cat-1', name: 'Chips' },
]);
const { applyMigrations } = await import('../../src/db/migrations'); 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.categories.add).not.toHaveBeenCalled();
expect(db.products.update).not.toHaveBeenCalled(); expect(db.products.update).not.toHaveBeenCalled();
@@ -45,16 +42,14 @@ describe('applyMigrations', () => {
test('normalizes pickList categories names to ids', async () => { test('normalizes pickList categories names to ids', async () => {
const db = makeMockDb(); const db = makeMockDb();
(db.categories.toArray as any) db.categories.toArray
.mockResolvedValueOnce([{ id: 'cat-fruit', name: 'Fruit' }]) .mockResolvedValueOnce([{ id: 'cat-fruit', name: 'Fruit' }])
.mockResolvedValueOnce([{ id: 'cat-fruit', name: 'Fruit' }]); .mockResolvedValueOnce([{ id: 'cat-fruit', name: 'Fruit' }]);
(db.products.toArray as any).mockResolvedValueOnce([]); db.products.toArray.mockResolvedValueOnce([]);
(db.pickLists.toArray as any).mockResolvedValueOnce([ db.pickLists.toArray.mockResolvedValueOnce([{ id: 'pl-1', categories: ['Fruit'] }]);
{ id: 'pl-1', categories: ['Fruit'] },
]);
const { applyMigrations } = await import('../../src/db/migrations'); 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'] }); expect(db.pickLists.update).toHaveBeenCalledWith('pl-1', { categories: ['cat-fruit'] });
}); });
+29 -12
View File
@@ -3,11 +3,18 @@ import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest';
vi.mock('@zxing/browser', () => { vi.mock('@zxing/browser', () => {
class MockReader { class MockReader {
decodeFromVideoDevice = vi.fn(async (_device: any, _video: any, callback: any) => { decodeFromVideoDevice = vi.fn(
callback({ getText: () => 'decoded-fallback' } as any); async (
return { stop: vi.fn() } as any; _device: string | undefined,
}); _video: HTMLVideoElement,
callback: (result: { getText: () => string }) => void,
) => {
callback({ getText: () => 'decoded-fallback' });
return { stop: vi.fn() };
},
);
} }
return { BrowserMultiFormatReader: MockReader }; return { BrowserMultiFormatReader: MockReader };
}); });
@@ -15,11 +22,15 @@ describe('useBarcodeScanner', () => {
beforeEach(() => { beforeEach(() => {
vi.resetModules(); vi.resetModules();
vi.clearAllMocks(); vi.clearAllMocks();
(globalThis as any).BarcodeDetector = undefined; const globalWithBarcode = globalThis as typeof globalThis & {
BarcodeDetector?: new () => { detect: (source: ImageBitmap | HTMLCanvasElement) => Promise<Array<{ rawValue: string }>> };
};
globalWithBarcode.BarcodeDetector = undefined;
}); });
afterEach(() => { 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 () => { test('falls back to ZXing reader when BarcodeDetector is unavailable', async () => {
@@ -35,16 +46,22 @@ describe('useBarcodeScanner', () => {
class MockBarcodeDetector { class MockBarcodeDetector {
detect = detect; 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 play = vi.fn().mockResolvedValue(undefined);
const getVideoTracks = vi.fn().mockReturnValue([{ stop: vi.fn() }]); const getVideoTracks = vi.fn().mockReturnValue([{ stop: vi.fn() }]);
const getTracks = vi.fn().mockReturnValue([{ stop: vi.fn() }]); const getTracks = vi.fn().mockReturnValue([{ stop: vi.fn() }]);
const stream = { getVideoTracks, getTracks } as any; const stream = { getVideoTracks, getTracks };
navigator.mediaDevices = { Object.defineProperty(navigator, 'mediaDevices', {
getUserMedia: vi.fn().mockResolvedValue(stream), value: {
} as any; getUserMedia: vi.fn().mockResolvedValue(stream),
(globalThis as any).createImageBitmap = vi.fn().mockResolvedValue({}); },
configurable: true,
});
const createImageBitmap = vi.fn<[], Promise<ImageBitmap>>().mockResolvedValue({} as ImageBitmap);
const globalWithBitmap = globalThis as typeof globalThis & { createImageBitmap?: typeof createImageBitmap };
globalWithBitmap.createImageBitmap = createImageBitmap;
const { useBarcodeScanner } = await import('../../src/hooks/useBarcodeScanner'); const { useBarcodeScanner } = await import('../../src/hooks/useBarcodeScanner');
const { result } = renderHook(() => useBarcodeScanner()); const { result } = renderHook(() => useBarcodeScanner());
+54 -22
View File
@@ -1,45 +1,77 @@
import { vi } from 'vitest'; import { vi } from 'vitest';
export type TableMock<T = any> = { type WhereClause<T> = {
toArray: ReturnType<typeof vi.fn>; equals: ReturnType<typeof vi.fn<[T], { first: ReturnType<typeof vi.fn<[], Promise<T | undefined>>> }>>;
add: ReturnType<typeof vi.fn>;
update: ReturnType<typeof vi.fn>;
where: ReturnType<typeof vi.fn>;
filter: ReturnType<typeof vi.fn>;
count: ReturnType<typeof vi.fn>;
}; };
const createWhere = () => ({ type FilterClause<T> = {
equals: vi.fn().mockReturnValue({ first: vi.fn().mockResolvedValue(undefined) }), first: ReturnType<typeof vi.fn<[], Promise<T | undefined>>>;
};
export type TableMock<T = unknown> = {
toArray: ReturnType<typeof vi.fn<[], Promise<T[]>>>;
add: ReturnType<typeof vi.fn<[T], Promise<unknown>>>;
update: ReturnType<typeof vi.fn<[string, Partial<T>], Promise<unknown>>>;
where: ReturnType<typeof vi.fn<[keyof T], WhereClause<T[keyof T]>>>;
filter: ReturnType<typeof vi.fn<[(item: T) => boolean], FilterClause<T>>>;
count: ReturnType<typeof vi.fn<[], Promise<number>>>;
};
const createWhere = <T>(): WhereClause<T> => ({
equals: vi.fn<[T], { first: ReturnType<typeof vi.fn<[], Promise<T | undefined>>> }>().mockReturnValue({
first: vi.fn<[], Promise<T | undefined>>().mockResolvedValue(undefined),
}),
}); });
export const makeTableMock = <T = any>(items: T[] = []): TableMock<T> => ({ export const makeTableMock = <T = unknown>(items: T[] = []): TableMock<T> => ({
toArray: vi.fn().mockResolvedValue([...items]), toArray: vi.fn<[], Promise<T[]>>().mockResolvedValue([...items]),
add: vi.fn().mockResolvedValue(undefined), add: vi.fn<[T], Promise<unknown>>().mockResolvedValue(undefined),
update: vi.fn().mockResolvedValue(undefined), update: vi.fn<[string, Partial<T>], Promise<unknown>>().mockResolvedValue(undefined),
where: vi.fn().mockImplementation(() => createWhere()), where: vi.fn<[keyof T], WhereClause<T[keyof T]>>().mockImplementation(() => createWhere<T[keyof T]>()),
filter: vi.fn().mockReturnValue({ first: vi.fn().mockResolvedValue(undefined) }), filter: vi
count: vi.fn().mockResolvedValue(0), .fn<[(item: T) => boolean], FilterClause<T>>()
.mockReturnValue({ first: vi.fn<[], Promise<T | undefined>>().mockResolvedValue(undefined) }),
count: vi.fn<[], Promise<number>>().mockResolvedValue(0),
}); });
export const makeMockDb = (overrides: Partial<Record<string, any>> = {}) => { type MockDbOverrides = {
products: unknown[];
categories: unknown[];
pickLists: unknown[];
pickItems: unknown[];
importExportLogs: unknown[];
};
type TransactionMock = ReturnType<typeof vi.fn<[string, ...unknown[]], Promise<unknown>>>;
type MockDb = {
products: TableMock;
categories: TableMock;
pickLists: TableMock;
pickItems: TableMock;
importExportLogs: TableMock;
transaction: TransactionMock;
open: ReturnType<typeof vi.fn<[], Promise<unknown>>>;
};
export const makeMockDb = (overrides: Partial<MockDbOverrides> = {}): MockDb => {
const products = makeTableMock(overrides.products ?? []); const products = makeTableMock(overrides.products ?? []);
const categories = makeTableMock(overrides.categories ?? []); const categories = makeTableMock(overrides.categories ?? []);
const pickLists = makeTableMock(overrides.pickLists ?? []); const pickLists = makeTableMock(overrides.pickLists ?? []);
const pickItems = makeTableMock(overrides.pickItems ?? []); const pickItems = makeTableMock(overrides.pickItems ?? []);
const importExportLogs = makeTableMock(overrides.importExportLogs ?? []); const importExportLogs = makeTableMock(overrides.importExportLogs ?? []);
const transaction = vi const transaction = vi.fn<[string, ...unknown[]], Promise<unknown>>().mockImplementation(
.fn() async (_mode, ...args) => {
.mockImplementation(async (_mode: any, ...args: any[]) => {
const cb = args[args.length - 1]; const cb = args[args.length - 1];
if (typeof cb === 'function') { if (typeof cb === 'function') {
return cb(); return cb();
} }
return undefined; return undefined;
}); },
);
const open = vi.fn().mockResolvedValue(undefined); const open = vi.fn<[], Promise<unknown>>().mockResolvedValue(undefined);
return { return {
products, products,