import { describe, expect, it, vi } from 'vitest'; import { DASHCAM_COORDINATES, POSTCARD_HEIGHT, POSTCARD_WIDTH, coverCrop, drawPostcard, postcardLayout, type DashcamTelemetry, type PostcardContext, } from '@/lib/dashcam-postcard'; const telemetry: DashcamTelemetry = { sectionId: 'infrastructure', sectionLabel: 'Infrastructure', chapter: 'pre-dawn', exit: '04', routePercent: 47, clock: '03:51', name: 'Luca Pîslaru', city: 'Bucharest', }; interface TextCall { text: string; x: number; y: number; align: CanvasTextAlign; font: string } function stubContext() { const texts: TextCall[] = []; const images: number[][] = []; const ctx = { fillStyle: '' as unknown, strokeStyle: '' as unknown, lineWidth: 1, font: '', textAlign: 'start' as CanvasTextAlign, textBaseline: 'alphabetic' as CanvasTextBaseline, drawImage: vi.fn((_source: unknown, ...rest: number[]) => { images.push(rest); }), fillRect: vi.fn(), fillText: vi.fn((text: string, x: number, y: number) => { texts.push({ text, x, y, align: ctx.textAlign, font: ctx.font }); }), beginPath: vi.fn(), moveTo: vi.fn(), lineTo: vi.fn(), stroke: vi.fn(), arc: vi.fn(), fill: vi.fn(), createLinearGradient: vi.fn(() => ({ addColorStop: vi.fn() })), save: vi.fn(), restore: vi.fn(), }; return { ctx: ctx as unknown as PostcardContext & typeof ctx, texts, images }; } const source = (width: number, height: number) => ({ width, height }) as unknown as HTMLCanvasElement; describe('coverCrop', () => { it('crops a wider source to the 16:9 centre', () => { expect(coverCrop(2400, 900, 1600, 900)).toEqual({ sx: 400, sy: 0, sw: 1600, sh: 900 }); }); it('crops a taller source to the 16:9 centre', () => { const crop = coverCrop(1440, 1080, 1600, 900); expect(crop.sx).toBe(0); expect(crop.sw).toBe(1440); expect(crop.sh).toBeCloseTo(810, 6); expect(crop.sy).toBeCloseTo(135, 6); }); it('keeps an exact-ratio source whole and tolerates an empty one', () => { expect(coverCrop(3200, 1800, 1600, 900)).toEqual({ sx: 0, sy: 0, sw: 3200, sh: 1800 }); expect(coverCrop(0, 0, 1600, 900)).toEqual({ sx: 0, sy: 0, sw: 0, sh: 0 }); }); }); describe('postcardLayout', () => { it('keeps every plate inside the frame at 1600 x 900', () => { const l = postcardLayout(); expect(l.inset).toBe(44); expect(l.left).toBeGreaterThan(l.inset); expect(l.right).toBeLessThan(POSTCARD_WIDTH - l.inset); expect(l.top - l.font).toBeGreaterThan(l.inset); expect(l.bottom - l.lineGap - l.font).toBeGreaterThan(POSTCARD_HEIGHT / 2); expect(l.bottom).toBeLessThan(POSTCARD_HEIGHT - l.inset); expect(l.credit).toBeGreaterThan(l.bottom); expect(l.credit).toBeLessThan(POSTCARD_HEIGHT); }); it('scales with the smaller axis', () => { expect(postcardLayout(800, 450).inset).toBe(22); expect(postcardLayout(800, 900).inset).toBe(22); }); }); describe('drawPostcard', () => { it('covers the frame with the scene and writes the telemetry and credit', () => { const { ctx, texts, images } = stubContext(); drawPostcard(ctx, source(2880, 1800), telemetry); const l = postcardLayout(); expect(images).toEqual([[0, 90, 2880, 1620, 0, 0, POSTCARD_WIDTH, POSTCARD_HEIGHT]]); expect(texts.map((call) => call.text)).toEqual([ 'REC DASHCAM · PRE DAWN · EXIT 04', 'ROUTE 47% · INFRASTRUCTURE', '03:51', 'LUCA PÎSLARU', DASHCAM_COORDINATES, 'NIGHT SHIFT · LUCA PÎSLARU · BUCHAREST', ]); const byText = Object.fromEntries(texts.map((call) => [call.text, call])); expect(byText['ROUTE 47% · INFRASTRUCTURE']).toMatchObject({ x: l.left, y: l.bottom, align: 'left' }); expect(byText['03:51']).toMatchObject({ x: l.right, y: l.top, align: 'right' }); expect(byText[DASHCAM_COORDINATES]).toMatchObject({ x: l.right, y: l.bottom - l.lineGap, align: 'right' }); expect(byText['NIGHT SHIFT · LUCA PÎSLARU · BUCHAREST']).toMatchObject({ x: 800, y: l.credit, align: 'center' }); expect(texts.every((call) => call.font.includes('Geist Mono'))).toBe(true); // Four corner brackets. expect(ctx.stroke).toHaveBeenCalledTimes(4); expect(ctx.save).toHaveBeenCalledTimes(1); expect(ctx.restore).toHaveBeenCalledTimes(1); }); it('still draws the chrome over an empty source', () => { const { ctx, texts, images } = stubContext(); drawPostcard(ctx, source(0, 0), telemetry); expect(images).toEqual([]); expect(texts).toHaveLength(6); }); });