Bundles the prior autonomous-session output that was sitting unstaged: - Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances) - country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk after the per-subpath dynamic-import approach silently failed in webpack) - Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index, redirects (ocr to ai, reports to dashboard, invitations to users), docs/admin-ia-proposal.md - Per-template email tester (registry + endpoint + UI on Email admin page) - Cancel-document mode picker (delete-from-Documenso vs keep-for-audit) - Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers - Customize-widgets per-region sortables at xl+ (charts/rails/feed); single flat sortable below xl when the layout stacks; per-viewport saved orders - Audit doc updates capturing each shipped item - Lint fixes: react-compiler immutability in DonutChart (reduce instead of let-reassign), set-state-in-effect disables in CountryFlag and UploadForSigning preview-bytes effect, unused 'confirm' destructures in interest contract + reservation tabs, unescaped apostrophe in test-template card copy
113 lines
4.0 KiB
TypeScript
113 lines
4.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { formatDate, formatDateRange, formatRelative } from '@/lib/utils/format-date';
|
|
|
|
describe('formatDate', () => {
|
|
const REF = '2026-05-12T14:30:45.000Z';
|
|
|
|
it('returns fallback for null/undefined/invalid', () => {
|
|
expect(formatDate(null)).toBe('-');
|
|
expect(formatDate(undefined)).toBe('-');
|
|
expect(formatDate('not a date')).toBe('-');
|
|
expect(formatDate(NaN)).toBe('-');
|
|
expect(formatDate(null, 'date.medium', { fallback: 'N/A' })).toBe('N/A');
|
|
});
|
|
|
|
it('formats date.iso correctly in UTC', () => {
|
|
expect(formatDate(REF, 'date.iso', { tz: 'UTC' })).toBe('2026-05-12');
|
|
});
|
|
|
|
it('formats date.iso correctly in a different timezone', () => {
|
|
// 14:30 UTC = 00:30 next day in Sydney (UTC+10)
|
|
expect(formatDate(REF, 'date.iso', { tz: 'Australia/Sydney' })).toBe('2026-05-13');
|
|
});
|
|
|
|
it('formats datetime.iso as full ISO string', () => {
|
|
expect(formatDate(REF, 'datetime.iso')).toBe('2026-05-12T14:30:45.000Z');
|
|
});
|
|
|
|
it('formats date.medium in en-GB', () => {
|
|
// Intl output may vary slightly across Node versions; assert it
|
|
// contains the expected day + month + year and uses no "/".
|
|
const out = formatDate(REF, 'date.medium', { tz: 'UTC' });
|
|
expect(out).toMatch(/12\s/); // day
|
|
expect(out).toContain('2026');
|
|
expect(out.toLowerCase()).toContain('may');
|
|
});
|
|
|
|
it('respects timezone - datetime.short in different zones', () => {
|
|
const ny = formatDate(REF, 'datetime.short', { tz: 'America/New_York' });
|
|
const utc = formatDate(REF, 'datetime.short', { tz: 'UTC' });
|
|
expect(ny).not.toBe(utc);
|
|
expect(utc).toContain('14:30');
|
|
});
|
|
|
|
it('formats time-only correctly', () => {
|
|
expect(formatDate(REF, 'time', { tz: 'UTC' })).toBe('14:30');
|
|
});
|
|
|
|
it('accepts Date instances + epoch ms', () => {
|
|
const d = new Date(REF);
|
|
expect(formatDate(d, 'date.iso', { tz: 'UTC' })).toBe('2026-05-12');
|
|
expect(formatDate(d.getTime(), 'date.iso', { tz: 'UTC' })).toBe('2026-05-12');
|
|
});
|
|
});
|
|
|
|
describe('formatDateRange', () => {
|
|
it('handles missing start/end', () => {
|
|
expect(formatDateRange(null, null)).toBe('-');
|
|
expect(formatDateRange('2026-05-12', null)).toMatch(/→$/);
|
|
expect(formatDateRange(null, '2026-05-12')).toMatch(/^→/);
|
|
});
|
|
|
|
it('collapses year when start + end are in same year', () => {
|
|
const out = formatDateRange('2026-05-12', '2026-08-14', { tz: 'UTC' });
|
|
// Start should NOT include year; end should.
|
|
expect(out).toContain('→');
|
|
expect(out).toContain('2026');
|
|
// Crude check: there's only one "2026" in the output.
|
|
expect((out.match(/2026/g) ?? []).length).toBe(1);
|
|
});
|
|
|
|
it('keeps year on both when years differ', () => {
|
|
const out = formatDateRange('2025-12-20', '2026-01-05', { tz: 'UTC' });
|
|
expect((out.match(/202\d/g) ?? []).length).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('formatRelative', () => {
|
|
const NOW = new Date('2026-05-12T12:00:00.000Z');
|
|
|
|
it('returns "just now" for tiny deltas', () => {
|
|
expect(formatRelative(new Date(NOW.getTime() - 5_000), { now: NOW })).toBe('just now');
|
|
});
|
|
|
|
it('uses minutes for past deltas under an hour', () => {
|
|
const ten = new Date(NOW.getTime() - 10 * 60_000);
|
|
const out = formatRelative(ten, { now: NOW });
|
|
expect(out).toMatch(/minute/);
|
|
});
|
|
|
|
it('uses hours for past deltas in the same day', () => {
|
|
const three = new Date(NOW.getTime() - 3 * 3600_000);
|
|
const out = formatRelative(three, { now: NOW });
|
|
expect(out).toMatch(/hour/);
|
|
});
|
|
|
|
it('uses days for past deltas across days', () => {
|
|
const yesterday = new Date(NOW.getTime() - 86_400_000);
|
|
const out = formatRelative(yesterday, { now: NOW });
|
|
expect(out).toMatch(/day|yesterday/i);
|
|
});
|
|
|
|
it('uses future tense for future dates', () => {
|
|
const tomorrow = new Date(NOW.getTime() + 86_400_000);
|
|
const out = formatRelative(tomorrow, { now: NOW });
|
|
expect(out).toMatch(/day|tomorrow/i);
|
|
});
|
|
|
|
it('returns fallback for invalid input', () => {
|
|
expect(formatRelative(null)).toBe('-');
|
|
});
|
|
});
|