import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { createPhoto, getPhotosByItemId, getPhotoById, deletePhoto, deletePhotosByItemId, blobToDataURL, } from './photos'; import { getDB, resetDBPromise } from './db'; // Mock canvas for thumbnail generation beforeEach(() => { // Mock canvas context const mockContext = { drawImage: vi.fn(), }; // Mock HTMLCanvasElement // eslint-disable-next-line @typescript-eslint/no-explicit-any HTMLCanvasElement.prototype.getContext = vi.fn(() => mockContext) as any; HTMLCanvasElement.prototype.toBlob = vi.fn(function (callback) { callback?.(new Blob(['thumbnail'], { type: 'image/jpeg' })); }); // Mock HTMLImageElement global.Image = class { onload: (() => void) | null = null; onerror: (() => void) | null = null; src = ''; width = 1920; height = 1080; constructor() { setTimeout(() => { this.onload?.(); }, 0); } } as unknown as typeof Image; // Mock URL.createObjectURL global.URL.createObjectURL = vi.fn(() => 'blob:mock-url'); global.URL.revokeObjectURL = vi.fn(); }); afterEach(async () => { // Clean up database between tests const db = await getDB(); await db.clear('photos'); await resetDBPromise(); }); describe('photos', () => { describe('createPhoto', () => { it('creates a photo with thumbnail', async () => { const blob = new Blob(['test image'], { type: 'image/jpeg' }); const photo = await createPhoto('item_abc', blob); expect(photo.id).toMatch(/^photo_/); expect(photo.itemId).toBe('item_abc'); expect(photo.blob).toBe(blob); expect(photo.thumbnail).toBeInstanceOf(Blob); expect(photo.createdAt).toBeTruthy(); // Verify it was saved to DB const db = await getDB(); const saved = await db.get('photos', photo.id); expect(saved?.id).toBe(photo.id); expect(saved?.itemId).toBe(photo.itemId); expect(saved?.createdAt).toBe(photo.createdAt); }); it('generates unique IDs', async () => { const blob = new Blob(['test'], { type: 'image/jpeg' }); const photo1 = await createPhoto('item_abc', blob); const photo2 = await createPhoto('item_abc', blob); expect(photo1.id).not.toBe(photo2.id); }); }); describe('getPhotosByItemId', () => { it('returns all photos for an item', async () => { const blob = new Blob(['test'], { type: 'image/jpeg' }); const photo1 = await createPhoto('item_abc', blob); const photo2 = await createPhoto('item_abc', blob); await createPhoto('item_xyz', blob); // different item const photos = await getPhotosByItemId('item_abc'); expect(photos).toHaveLength(2); expect(photos.map((p) => p.id).sort()).toEqual([photo1.id, photo2.id].sort()); }); it('returns empty array if no photos', async () => { const photos = await getPhotosByItemId('nonexistent'); expect(photos).toEqual([]); }); }); describe('getPhotoById', () => { it('returns a photo by ID', async () => { const blob = new Blob(['test'], { type: 'image/jpeg' }); const photo = await createPhoto('item_abc', blob); const retrieved = await getPhotoById(photo.id); expect(retrieved?.id).toBe(photo.id); expect(retrieved?.itemId).toBe(photo.itemId); }); it('returns undefined if not found', async () => { const photo = await getPhotoById('nonexistent'); expect(photo).toBeUndefined(); }); }); describe('deletePhoto', () => { it('deletes a photo', async () => { const blob = new Blob(['test'], { type: 'image/jpeg' }); const photo = await createPhoto('item_abc', blob); await deletePhoto(photo.id); const retrieved = await getPhotoById(photo.id); expect(retrieved).toBeUndefined(); }); }); describe('deletePhotosByItemId', () => { it('deletes all photos for an item', async () => { const blob = new Blob(['test'], { type: 'image/jpeg' }); await createPhoto('item_abc', blob); await createPhoto('item_abc', blob); const photo3 = await createPhoto('item_xyz', blob); // different item await deletePhotosByItemId('item_abc'); const photos = await getPhotosByItemId('item_abc'); expect(photos).toEqual([]); // Other item's photos should remain const otherPhotos = await getPhotosByItemId('item_xyz'); expect(otherPhotos).toHaveLength(1); expect(otherPhotos[0].id).toBe(photo3.id); }); }); describe('blobToDataURL', () => { it('converts a blob to data URL', async () => { const blob = new Blob(['test'], { type: 'image/jpeg' }); const dataUrl = await blobToDataURL(blob); expect(dataUrl).toMatch(/^data:image\/jpeg;base64,/); }); }); });