import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; import { formatTabTitle, quantiseRoute } from '@/journey/tab-feed'; import { renderTabIcon, tabIconHref, TAB_ICON } from '@/journey/tab-icon'; import { validateProductionInputs } from '@/content/schemas'; import { render } from '@/entry-server'; const base = { routeProgress: 0.47, label: 'Infrastructure', arrived: false, parked: false, dashcam: false }; describe('formatTabTitle', () => { it('reads the route in 5% steps with an uppercase chapter', () => { expect(formatTabTitle(base)).toBe('ROUTE 45% · INFRASTRUCTURE'); expect(formatTabTitle({ ...base, routeProgress: 0.48 })).toBe('ROUTE 50% · INFRASTRUCTURE'); expect(formatTabTitle({ ...base, routeProgress: 0, label: 'Start' })).toBe('ROUTE 0% · START'); expect(formatTabTitle({ ...base, label: ' Selected builds ' })).toBe('ROUTE 45% · SELECTED BUILDS'); }); it('clamps and survives non-finite progress', () => { expect(formatTabTitle({ ...base, routeProgress: 1.4 })).toBe('ROUTE 100% · INFRASTRUCTURE'); expect(formatTabTitle({ ...base, routeProgress: -1 })).toBe('ROUTE 0% · INFRASTRUCTURE'); expect(formatTabTitle({ ...base, routeProgress: Number.NaN })).toBe('ROUTE 0% · INFRASTRUCTURE'); }); it('parks, arrives, and yields to the dashcam', () => { expect(formatTabTitle({ ...base, parked: true })).toBe('PARKED · 45% · INFRASTRUCTURE'); expect(formatTabTitle({ ...base, arrived: true, label: 'Contact' })).toBe('ARRIVED · CONTACT'); expect(formatTabTitle({ ...base, arrived: true, parked: true, label: 'Contact' })).toBe('PARKED · 100% · CONTACT'); expect(formatTabTitle({ ...base, dashcam: true })).toBeNull(); expect(formatTabTitle({ ...base, dashcam: true, parked: true, arrived: true })).toBeNull(); }); it('quantises to stable steps', () => { expect(quantiseRoute(0.024)).toBe(0); expect(quantiseRoute(0.026)).toBe(0.05); expect(new Set([0.5, 0.51, 0.52, 0.49].map((r) => formatTabTitle({ ...base, routeProgress: r }))).size).toBe(1); }); }); const beamLengths = (svg: string): number[] => [...svg.matchAll(/ Number(m[1])); describe('renderTabIcon', () => { const icon = { route: 0, accent: '#8be9ed', parked: false, dawn: false }; it('keeps the dark plate and the L glyph', () => { const svg = renderTabIcon(icon); expect(svg.startsWith("")).toBe(true); expect(svg).toContain(`fill='${TAB_ICON.plate}'`); expect(svg).toContain(``); expect(svg).not.toMatch(/["\n]/u); }); it('lengthens and brightens two beams with the route, in the accent', () => { const at = (route: number) => renderTabIcon({ ...icon, route }); const [l0, l50, l100] = [at(0), at(0.5), at(1)].map(beamLengths); expect(l0).toEqual([4, 4]); expect(l50).toEqual([9, 9]); expect(l100).toEqual([14, 14]); expect(opacities(at(0))).toEqual([0.55, 0.55]); expect(opacities(at(0.5))).toEqual([0.75, 0.75]); expect(opacities(at(1))).toEqual([0.95, 0.95]); expect(at(0.5).match(/fill='#8be9ed'/gu)).toHaveLength(2); expect(at(0.51)).toBe(at(0.5)); }); it('drops to amber parking lights when parked', () => { const svg = renderTabIcon({ ...icon, route: 0.8, parked: true }); expect(beamLengths(svg)).toEqual([]); expect(svg.match(new RegExp(`fill='${TAB_ICON.parking}'`, 'gu'))).toHaveLength(2); expect(svg).not.toContain('#8be9ed'); }); it('turns gold on peach at day', () => { const svg = renderTabIcon({ ...icon, route: 1, dawn: true }); expect(svg).toContain(``); expect(svg).toContain(`stroke='${TAB_ICON.dawnGlyph}'`); expect(svg).not.toContain(TAB_ICON.plate); expect(svg).not.toContain('#8be9ed'); }); it('ignores an accent that is not a hex colour', () => { expect(renderTabIcon({ ...icon, accent: "red'/>