import { afterEach, describe, expect, it, vi } from 'vitest'; import { formatDate } from '../utils'; import { useSettingsStore } from '@/stores/settings-store'; // The list formats every row's date on every render. formatDate keeps its // Intl.DateTimeFormat instances instead of building one per call (which each // toLocale*String call does), without changing a single output. describe('formatDate formatters', () => { afterEach(() => { vi.restoreAllMocks(); useSettingsStore.setState({ dateFormat: 'smart', timeFormat: '24h', dateLocale: 'auto' }); }); it('builds a formatter once per locale and option set, not once per row', () => { useSettingsStore.setState({ dateFormat: 'smart', timeFormat: '12h', dateLocale: 'auto', timeZone: 'Europe/Berlin' }); const old = new Date(Date.now() - 40 * 86_400_000); formatDate(old); // warm const Real = Intl.DateTimeFormat; let built = 0; vi.spyOn(Intl, 'DateTimeFormat').mockImplementation(((...args: ConstructorParameters) => { built++; return new Real(...args); }) as never); vi.spyOn(Date.prototype, 'toLocaleDateString').mockImplementation(function (this: Date, ...args) { built++; return Real.prototype.format.call(new Real(...args), this); }); vi.spyOn(Date.prototype, 'toLocaleTimeString').mockImplementation(function (this: Date, ...args) { built++; return Real.prototype.format.call(new Real(...args), this); }); vi.spyOn(Date.prototype, 'toLocaleString').mockImplementation(function (this: Date, ...args) { built++; return Real.prototype.format.call(new Real(...args), this); }); for (let i = 0; i < 50; i++) formatDate(new Date(old.getTime() - i * 60_000)); expect(built).toBe(0); }); it.each([ ['smart', '24h', 'auto'], ['smart', '12h', 'en-GB'], ['smart', '24h', 'iso'], ['full', '12h', 'auto'], ['full', '24h', 'en-US'], ['relative', '24h', 'auto'], ] as const)('formats as the toLocale* calls did (%s, %s, %s)', (dateFormat, timeFormat, dateLocale) => { useSettingsStore.setState({ dateFormat, timeFormat, dateLocale, timeZone: 'America/New_York' }); const now = Date.now(); const zone = 'America/New_York'; const hour12 = timeFormat === '12h'; const numeric = dateLocale === 'auto' ? 'en-US' : dateLocale === 'iso' ? 'en-CA' : dateLocale; for (const ago of [5 * 60_000, 3 * 86_400_000, 40 * 86_400_000, 400 * 86_400_000]) { const d = new Date(now - ago); const got = formatDate(d); if (dateFormat === 'full') { expect(got).toBe(d.toLocaleString(numeric, { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12 })); } else if (dateFormat === 'smart' && ago > 7 * 86_400_000) { expect(got).toBe(d.toLocaleDateString(numeric, { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit' })); } else if (dateFormat === 'smart' && ago > 86_400_000) { const weekday = d.toLocaleDateString('en-US', { timeZone: zone, weekday: 'short' }); expect(got).toBe(`${weekday} ${d.toLocaleTimeString('en-US', { timeZone: zone, hour: '2-digit', minute: '2-digit', hour12 })}`); } else { expect(got.length).toBeGreaterThan(0); } } }); });