import { describe, expect, it } from 'vitest'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { formatDocumentDate } from './documentDate'; import { m } from '$lib/paraglide/messages.js'; // ─── Shared drift-guard fixture ───────────────────────────────────────────── // The same table is asserted by the Java DocumentTitleFormatter test so the two // label implementations cannot drift. Expected values are the German canonical // form (see docs/date-label-fixtures.json). type FixtureCase = { name: string; precision: string; anchor: string | null; end: string | null; raw: string | null; expected: string; }; type LocaleFixtureCase = FixtureCase & { locale: string }; const fixtures = JSON.parse( readFileSync(resolve(process.cwd(), '../docs/date-label-fixtures.json'), 'utf-8') ) as { cases: FixtureCase[]; localeCases: LocaleFixtureCase[] }; describe('formatDocumentDate – shared fixture table (de)', () => { for (const c of fixtures.cases) { it(c.name, () => { expect( formatDocumentDate( c.anchor, c.precision as Parameters[1], c.end, c.raw, 'de' ) ).toBe(c.expected); }); } }); // TS-only locale parity (the Java title formatter is German-only by design, so // localeCases are asserted here and never fed to DocumentTitleFormatterTest). describe('formatDocumentDate – shared fixture table (en/es locale parity)', () => { for (const c of fixtures.localeCases) { it(`${c.name} [${c.locale}]`, () => { expect( formatDocumentDate( c.anchor, c.precision as Parameters[1], c.end, c.raw, c.locale ) ).toBe(c.expected); }); } }); // ─── Anti-fabrication: suppressed components never leak ────────────────────── describe('formatDocumentDate – suppressed precision components', () => { it('YEAR of a June date renders the year only, never the month', () => { const label = formatDocumentDate('1916-06-15', 'YEAR'); expect(label).toBe('1916'); expect(label).not.toContain('Juni'); expect(label).not.toContain('15'); }); it('MONTH never renders the day-of-month', () => { const label = formatDocumentDate('1916-06-01', 'MONTH', null, 'Juni 1916'); expect(label).toBe('Juni 1916'); expect(label).not.toMatch(/\b1\.\s/); }); }); // ─── i18n: localized structured label ─────────────────────────────────────── describe('formatDocumentDate – localization', () => { it('localizes the UNKNOWN label per locale', () => { expect(formatDocumentDate(null, 'UNKNOWN', null, '?', 'en')).toBe( m.date_precision_unknown(undefined, { locale: 'en' }) ); }); it('localizes the APPROX prefix per locale', () => { expect(formatDocumentDate('1920-01-01', 'APPROX', null, null, 'en')).toBe( `${m.date_precision_approx_prefix(undefined, { locale: 'en' })} 1920` ); }); it('localizes the SEASON word per locale when raw is absent', () => { expect(formatDocumentDate('1916-07-01', 'SEASON', null, null, 'en')).toBe( `${m.date_season_summer(undefined, { locale: 'en' })} 1916` ); }); it('localizes the SEASON word even when the raw cell is verbatim German (Decision 4)', () => { expect(formatDocumentDate('1916-06-01', 'SEASON', null, 'Sommer 1916', 'en')).toBe( `${m.date_season_summer(undefined, { locale: 'en' })} 1916` ); }); // DAY precision must honour the active locale (regression: it was hard-wired // to de-DE, so an English/Spanish reader saw "24. Dezember 1943"). it('localizes the DAY month name in English', () => { expect(formatDocumentDate('1943-12-24', 'DAY', null, null, 'en')).toBe( new Intl.DateTimeFormat('en', { day: 'numeric', month: 'long', year: 'numeric' }).format( new Date('1943-12-24T12:00:00') ) ); }); it('localizes the DAY month name in Spanish', () => { expect(formatDocumentDate('1943-12-24', 'DAY', null, null, 'es')).toBe( new Intl.DateTimeFormat('es', { day: 'numeric', month: 'long', year: 'numeric' }).format( new Date('1943-12-24T12:00:00') ) ); }); it('localizes the MONTH month name in English', () => { expect(formatDocumentDate('1916-06-01', 'MONTH', null, 'Juni 1916', 'en')).toBe( new Intl.DateTimeFormat('en', { month: 'long', year: 'numeric' }).format( new Date('1916-06-01T12:00:00') ) ); }); it('localizes the MONTH month name in Spanish', () => { expect(formatDocumentDate('1916-06-01', 'MONTH', null, 'Juni 1916', 'es')).toBe( new Intl.DateTimeFormat('es', { month: 'long', year: 'numeric' }).format( new Date('1916-06-01T12:00:00') ) ); }); }); // ─── Security: untrusted raw must never influence the structured label ─────── describe('formatDocumentDate – security', () => { it('ignores a malicious raw value for the structured label (raw is rendered separately, escaped)', () => { const label = formatDocumentDate(null, 'UNKNOWN', null, ''); expect(label).toBe('Datum unbekannt'); expect(label).not.toContain(' { it('renders the unknown label when the anchor is null but precision is not UNKNOWN', () => { expect(formatDocumentDate(null, 'DAY')).toBe('Datum unbekannt'); }); it('falls back to start-day only for a RANGE whose end is null', () => { expect(formatDocumentDate('1917-01-10', 'RANGE', null)).toBe('ab 10. Jan. 1917'); }); });