feat(relationship): add formatRelationshipDateRange helper
Plain-text "from – to" range formatter for relationship dates, delegating all precision rendering to the shared formatDocumentDate (zero new precision logic). Lives in $lib/person next to personLifeDates and reuses the existing person → shared boundary. From-only renders just the start (no trailing dash), no dates renders nothing. REQ-015 Refs #837 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
65
frontend/src/lib/person/relationshipDates.spec.ts
Normal file
65
frontend/src/lib/person/relationshipDates.spec.ts
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
47
frontend/src/lib/person/relationshipDates.ts
Normal file
47
frontend/src/lib/person/relationshipDates.ts
Normal file
@@ -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 '';
|
||||
}
|
||||
Reference in New Issue
Block a user