feat(timeline): add precision-aware date-label facade
timelineDateLabel delegates to the shared formatDocumentDate so a timeline chip renders identically to the same date on a document, in the active locale (REQ-001/REQ-002). UNKNOWN precision and null/undefined/'' eventDate short-circuit to null with no formatter call (REQ-003/REQ-004); raw is always null since timeline events carry no verbatim spreadsheet cell. The facade owns no precision logic of its own (REQ-005). Register the new `timeline` frontend domain in the eslint boundaries config (allowed to import only `shared`) and add src/lib/timeline/** to the vitest coverage include (REQ-006). The spec partially mocks the paraglide runtime via importOriginal so getLocale is stubbed while the formatter still resolves real season/range message exports. Refs #778 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,7 @@ export default defineConfig(
|
||||
{ type: 'ocr', pattern: 'src/lib/ocr/**' },
|
||||
{ type: 'activity', pattern: 'src/lib/activity/**' },
|
||||
{ type: 'conversation', pattern: 'src/lib/conversation/**' },
|
||||
{ type: 'timeline', pattern: 'src/lib/timeline/**' },
|
||||
{ type: 'shared', pattern: 'src/lib/shared/**' },
|
||||
{ type: 'routes', pattern: 'src/routes/**' }
|
||||
]
|
||||
@@ -198,6 +199,7 @@ export default defineConfig(
|
||||
{ from: { type: 'user' }, allow: { to: { type: ['shared'] } } },
|
||||
{ from: { type: 'notification' }, allow: { to: { type: ['shared'] } } },
|
||||
{ from: { type: 'conversation' }, allow: { to: { type: ['shared'] } } },
|
||||
{ from: { type: 'timeline' }, allow: { to: { type: ['shared'] } } },
|
||||
{ from: { type: 'shared' }, allow: { to: { type: ['shared'] } } },
|
||||
{
|
||||
from: { type: 'routes' },
|
||||
|
||||
@@ -5,8 +5,13 @@ import { timelineDateLabel } from './dateLabel';
|
||||
|
||||
// The mocked path MUST include the `.js` suffix to match the import in
|
||||
// dateLabel.ts — a specifier mismatch silently skips the mock and the helper
|
||||
// would resolve the real locale.
|
||||
vi.mock('$lib/paraglide/runtime.js', () => ({ getLocale: vi.fn(() => 'de') }));
|
||||
// would resolve the real locale. We partially mock via importOriginal so only
|
||||
// getLocale is stubbed: the shared formatter still pulls real Paraglide message
|
||||
// exports (season words, range prefixes) off the same runtime module.
|
||||
vi.mock('$lib/paraglide/runtime.js', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('$lib/paraglide/runtime.js')>()),
|
||||
getLocale: vi.fn(() => 'de')
|
||||
}));
|
||||
|
||||
describe('timelineDateLabel', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
30
frontend/src/lib/timeline/dateLabel.ts
Normal file
30
frontend/src/lib/timeline/dateLabel.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { formatDocumentDate, type DatePrecision } from '$lib/shared/utils/documentDate';
|
||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||
|
||||
/**
|
||||
* Renders a timeline event's date by delegating to the shared
|
||||
* {@link formatDocumentDate} — so a timeline chip reads identically to the same
|
||||
* date on a document, in whichever locale is active. This module is a thin
|
||||
* façade: it owns NO precision or rendering logic (REQ-005), only the two
|
||||
* timeline-specific decisions below.
|
||||
*
|
||||
* @param eventDate the event's anchor day (`YYYY-MM-DD`). `null`, `undefined`
|
||||
* and `''` are equivalent — all mean "undated" and yield `null` WITHOUT
|
||||
* calling the formatter (REQ-004).
|
||||
* @param precision the event's precision metadata; `'UNKNOWN'` yields `null`
|
||||
* (no chip) even when a date is present (REQ-003).
|
||||
* @param eventDateEnd the RANGE end day; `undefined` and `null` are equivalent
|
||||
* and both mean an open-ended range.
|
||||
* @returns the localized label, or `null` for an UNKNOWN/undated event.
|
||||
*/
|
||||
export function timelineDateLabel(
|
||||
eventDate: string | null | undefined,
|
||||
precision: DatePrecision,
|
||||
eventDateEnd?: string | null
|
||||
): string | null {
|
||||
if (precision === 'UNKNOWN' || !eventDate) return null;
|
||||
// raw is always null for timeline events — there is no verbatim spreadsheet
|
||||
// cell to interpolate; season words derive from the structured anchor month
|
||||
// (never from untrusted text). See documentDate.ts for the raw contract.
|
||||
return formatDocumentDate(eventDate, precision, eventDateEnd ?? null, null, getLocale());
|
||||
}
|
||||
@@ -72,6 +72,7 @@ export default defineConfig({
|
||||
'src/lib/shared/server/**',
|
||||
'src/lib/shared/discussion/**',
|
||||
'src/lib/document/**',
|
||||
'src/lib/timeline/**',
|
||||
'src/hooks.server.ts'
|
||||
],
|
||||
exclude: [
|
||||
|
||||
Reference in New Issue
Block a user