import { formatRelativeTime } from './formatRelativeTime' // Date strings deliberately omit the trailing 'Z' so they parse in the // runner's local timezone — formatRelativeTime buckets by local calendar // days, so this keeps assertions deterministic regardless of ambient TZ. describe('formatRelativeTime', () => { let originalDate: typeof Date beforeEach(() => { // Save the original Date originalDate = global.Date }) afterEach(() => { // Restore the original Date global.Date = originalDate }) const mockDate = (isoString: string) => { const mockNow = new Date(isoString) // @ts-expect-error - need to override Date constructor for testing global.Date = class extends originalDate { // @ts-expect-error - need to override Date constructor for testing constructor(...args) { if (args.length === 0) { super(isoString) } else { // @ts-expect-error - need to override Date constructor for testing super(...args) } } static now() { return mockNow.getTime() } } } describe('Just now', () => { it('should return "Just now" for messages less than 1 minute ago', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2024-01-15T11:59:30') // 30 seconds ago expect(formatRelativeTime(date)).toBe('Just now') }) }) describe('Today', () => { it('should return time in 12-hour format for messages from earlier today', () => { mockDate('2024-01-15T14:08:00') const date = new Date('2024-01-15T09:30:00') const result = formatRelativeTime(date) // Should be "9:30 AM" format expect(result).toMatch(/^\d{1,2}:\d{2} (AM|PM)$/i) }) }) describe('Yesterday', () => { it('should return "Yesterday" for messages from the previous calendar day', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2024-01-14T12:00:00') expect(formatRelativeTime(date)).toBe('Yesterday') }) it('should return "Yesterday" even if less than 24 hours ago but previous day', () => { mockDate('2024-01-15T01:00:00') const date = new Date('2024-01-14T23:00:00') // Only 2 hours ago expect(formatRelativeTime(date)).toBe('Yesterday') }) it('should NOT return "Yesterday" for messages from 2 days ago', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2024-01-13T12:00:00') expect(formatRelativeTime(date)).not.toBe('Yesterday') }) }) describe('2-6 days ago (short weekday)', () => { // The weekday branch uses the runtime locale // (toLocaleDateString(undefined, …)), so assertions compare against // the same locale-aware formatter rather than fixed English strings. it('should return a short weekday name for messages 2 days ago', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2024-01-13T12:00:00') // Saturday expect(formatRelativeTime(date)).toBe( date.toLocaleDateString(undefined, { weekday: 'short' }) ) }) it('should return a short weekday name for messages 6 days ago (boundary before dates)', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2024-01-09T12:00:00') // Tuesday expect(formatRelativeTime(date)).toBe( date.toLocaleDateString(undefined, { weekday: 'short' }) ) }) }) describe('7+ days ago, same year (month and day)', () => { // Compare against the same locale-aware formatter rather than // hard-coded digits, which would fail under non-Latin numbering // systems (e.g. ar_EG renders "٨ يناير"). it('should return month + day without year for messages 7 days ago', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2024-01-08T12:00:00') expect(formatRelativeTime(date)).toBe( date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) ) }) it('should return month + day without year for messages more than 4 weeks ago in the same year', () => { mockDate('2024-02-15T12:00:00') const date = new Date('2024-01-01T12:00:00') expect(formatRelativeTime(date)).toBe( date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) ) }) }) describe('Previous years (month, day and year)', () => { it('should include the year for messages from a previous year', () => { mockDate('2024-01-15T12:00:00') const date = new Date('2023-12-01T12:00:00') expect(formatRelativeTime(date)).toBe( date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', }) ) }) }) describe('Edge cases', () => { it('should handle month boundaries correctly', () => { mockDate('2024-02-01T12:00:00') const date = new Date('2024-01-31T12:00:00') expect(formatRelativeTime(date)).toBe('Yesterday') }) it('should handle year boundaries correctly', () => { mockDate('2024-01-01T12:00:00') const date = new Date('2023-12-31T12:00:00') expect(formatRelativeTime(date)).toBe('Yesterday') }) it('should handle leap year correctly', () => { mockDate('2024-03-01T12:00:00') const date = new Date('2024-02-29T12:00:00') expect(formatRelativeTime(date)).toBe('Yesterday') }) }) })