diff --git a/frontend/src/lib/person/relationshipDates.spec.ts b/frontend/src/lib/person/relationshipDates.spec.ts new file mode 100644 index 00000000..de990bcb --- /dev/null +++ b/frontend/src/lib/person/relationshipDates.spec.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest'; +import { formatRelationshipDateRange } from './relationshipDates'; + +// Delegates all precision rendering to formatDocumentDate — these tests pin the +// composition (dash, single sides, empty state) and one rendering per precision, +// plus en/es for DAY/MONTH so a German-month leak is caught here, not on a card. +describe('formatRelationshipDateRange', () => { + describe('both dates (de default)', () => { + it('renders DAY precision as full dates', () => { + expect(formatRelationshipDateRange('1923-05-12', 'DAY', '1958-06-13', 'DAY')).toBe( + '12. Mai 1923 – 13. Juni 1958' + ); + }); + + it('renders MONTH precision as month + year', () => { + expect(formatRelationshipDateRange('1923-05-01', 'MONTH', '1958-06-01', 'MONTH')).toBe( + 'Mai 1923 – Juni 1958' + ); + }); + + it('renders YEAR precision as bare years', () => { + expect(formatRelationshipDateRange('1923-01-01', 'YEAR', '1958-01-01', 'YEAR')).toBe( + '1923 – 1958' + ); + }); + + it('renders mixed precisions per side', () => { + expect(formatRelationshipDateRange('1923-05-12', 'DAY', '1958-01-01', 'YEAR')).toBe( + '12. Mai 1923 – 1958' + ); + }); + }); + + describe('single sides and empty states', () => { + it('renders from only without a trailing dash', () => { + expect(formatRelationshipDateRange('1923-05-12', 'DAY', null, null)).toBe('12. Mai 1923'); + }); + + it('renders to only with a leading dash', () => { + expect(formatRelationshipDateRange(null, null, '1958-06-13', 'DAY')).toBe('– 13. Juni 1958'); + }); + + it('renders nothing when both dates are missing (UNKNOWN)', () => { + expect(formatRelationshipDateRange(null, 'UNKNOWN', null, 'UNKNOWN')).toBe(''); + }); + + it('renders nothing for a from-only with a null date', () => { + expect(formatRelationshipDateRange(null, null, null, null)).toBe(''); + }); + }); + + describe('localized months (catch German-month leak)', () => { + it('renders DAY in English with no German month name', () => { + const out = formatRelationshipDateRange('1923-05-12', 'DAY', null, null, 'en'); + expect(out).toContain('May'); + expect(out).not.toContain('Mai'); + expect(out).toContain('1923'); + }); + + it('renders MONTH in Spanish', () => { + const out = formatRelationshipDateRange('1923-05-01', 'MONTH', null, null, 'es'); + expect(out.toLowerCase()).toContain('mayo'); + }); + }); +}); diff --git a/frontend/src/lib/person/relationshipDates.ts b/frontend/src/lib/person/relationshipDates.ts new file mode 100644 index 00000000..8d2a74a0 --- /dev/null +++ b/frontend/src/lib/person/relationshipDates.ts @@ -0,0 +1,47 @@ +import { formatDocumentDate, type DatePrecision } from '$lib/shared/utils/documentDate'; + +/** + * Formats one relationship endpoint (start or end) at the precision the data + * claims, delegating all rendering to {@link formatDocumentDate}. Returns '' for + * a missing date. A missing precision falls back to YEAR — pre-V78 rows only knew + * a year. Mirrors {@link formatLifeDate} for the person life-date pattern. + */ +function formatEnd( + date: string | null | undefined, + precision: DatePrecision | null | undefined, + locale?: string +): string { + if (!date) { + return ''; + } + return formatDocumentDate(date, precision ?? 'YEAR', null, null, locale); +} + +/** + * Formats a relationship's start–end range as plain text, e.g. for a marriage row. + * Examples (de): + * 12. Mai 1923 – 13. Juni 1958 (both) + * 12. Mai 1923 (start only — no trailing dash) + * – 13. Juni 1958 (end only) + * "" (neither — the caller renders no date line) + */ +export function formatRelationshipDateRange( + fromDate: string | null | undefined, + fromDatePrecision: DatePrecision | null | undefined, + toDate: string | null | undefined, + toDatePrecision: DatePrecision | null | undefined, + locale?: string +): string { + const from = formatEnd(fromDate, fromDatePrecision, locale); + const to = formatEnd(toDate, toDatePrecision, locale); + if (from && to) { + return `${from} – ${to}`; + } + if (from) { + return from; + } + if (to) { + return `– ${to}`; + } + return ''; +}