import { parseServerTime, relativeTime, relativeSince, absoluteLocal } from '../src/lib/time'; const NOW = Date.parse('2026-10-18T20:00:00Z'); const at = (msAgo: number) => new Date(NOW - msAgo); describe('parseServerTime', () => { it('parses RFC3339', () => { expect(parseServerTime('2026-10-18T19:59:00Z')?.getTime()).toBe( Date.parse('2026-10-18T19:59:00Z'), ); }); it('returns null for absent or garbage values', () => { expect(parseServerTime(undefined)).toBeNull(); expect(parseServerTime(null)).toBeNull(); expect(parseServerTime('')).toBeNull(); expect(parseServerTime('not-a-date')).toBeNull(); }); }); describe('relativeTime', () => { it('says "just now" under a second, including for clock skew', () => { expect(relativeTime(at(0), NOW)).toBe('just now'); expect(relativeTime(at(500), NOW)).toBe('just now'); // A phone clock ahead of the server must never render "in 3 seconds". expect(relativeTime(at(-5000), NOW)).toBe('just now'); }); it('counts seconds under a minute', () => { expect(relativeTime(at(1000), NOW)).toBe('1s ago'); expect(relativeTime(at(2000), NOW)).toBe('2s ago'); expect(relativeTime(at(59000), NOW)).toBe('59s ago'); }); it('counts minutes, hours and days above that', () => { expect(relativeTime(at(60000), NOW)).toBe('1m ago'); expect(relativeTime(at(13 * 60000 + 42000), NOW)).toBe('13m ago'); expect(relativeTime(at(3600000), NOW)).toBe('1h ago'); expect(relativeTime(at(47 * 3600000), NOW)).toBe('1d ago'); expect(relativeTime(at(3 * 86400000), NOW)).toBe('3d ago'); }); }); describe('relativeSince', () => { it('formats a server string', () => { const iso = new Date(NOW - 2000).toISOString(); expect(relativeSince(iso, NOW)).toBe('2s ago'); }); it('returns null when there is nothing to format', () => { expect(relativeSince(undefined, NOW)).toBeNull(); expect(relativeSince('nope', NOW)).toBeNull(); }); }); describe('absoluteLocal', () => { it('renders zero-padded local hh:mm:ss', () => { const d = new Date(2026, 9, 18, 8, 5, 3); expect(absoluteLocal(d)).toBe('08:05:03'); }); });