Files
Portfolio/tests/unit/dashcam-postcard.test.ts

128 lines
4.5 KiB
TypeScript

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);
});
});