import dayjs from 'dayjs'; import { parseDoormileTimestamp, formatDoormileTimestamp, doormileDay } from '@/lib/doormileTimestamp'; /** * Doormile timestamps are IST wall-clock in naive Postgres columns, and some * responses carry a FALSE trailing `Z` (a Go+pgx marshalling footgun). Parsed * naively, dayjs reads that Z as a real UTC instant and adds +5:30 to digits * that were already correct. * * These tests protect the one property the whole app depends on: whatever zone * marker a timestamp arrives with, the rendered and bucketed result is the raw * wall-clock digits — asserted without depending on the machine's own timezone. */ describe('doormileTimestamp', () => { describe('parseDoormileTimestamp', () => { it('should ignore a false trailing Z rather than shifting the clock by +5:30', () => { const parsed = parseDoormileTimestamp('2026-08-27T14:30:00Z'); expect(parsed.isValid()).toBe(true); expect(parsed.hour()).toBe(14); expect(parsed.minute()).toBe(30); expect(parsed.date()).toBe(27); }); it('should strip a +05:30 offset and keep the same wall-clock digits', () => { const parsed = parseDoormileTimestamp('2026-08-27T14:30:00+05:30'); expect(parsed.hour()).toBe(14); expect(parsed.minute()).toBe(30); }); it('should strip a colon-less offset such as +0530', () => { expect(parseDoormileTimestamp('2026-08-27T14:30:00+0530').hour()).toBe(14); }); it('should strip a negative offset too', () => { expect(parseDoormileTimestamp('2026-08-27T14:30:00-04:00').hour()).toBe(14); }); it('should parse an already-naive timestamp unchanged', () => { const parsed = parseDoormileTimestamp('2026-08-27T14:30:00'); expect(parsed.hour()).toBe(14); expect(parsed.date()).toBe(27); }); it('should produce the identical instant for the naive and the false-Z form', () => { const naive = parseDoormileTimestamp('2026-08-27T23:45:00'); const falseZ = parseDoormileTimestamp('2026-08-27T23:45:00Z'); expect(falseZ.valueOf()).toBe(naive.valueOf()); }); it.each([null, undefined, '', 0, false])( 'should return an invalid dayjs (not throw) for the blank value %p', (raw) => { expect(parseDoormileTimestamp(raw).isValid()).toBe(false); } ); it('should return an invalid dayjs for unparseable text', () => { expect(parseDoormileTimestamp('not-a-timestamp').isValid()).toBe(false); }); it('should accept a bare date with no time component', () => { const parsed = parseDoormileTimestamp('2026-08-27'); expect(parsed.isValid()).toBe(true); expect(parsed.format('YYYY-MM-DD')).toBe('2026-08-27'); }); it('should accept a Date instance without throwing', () => { expect(parseDoormileTimestamp(new Date('2026-08-27T10:00:00Z')).isValid()).toBe(true); }); }); describe('formatDoormileTimestamp', () => { it('should render the default operator-facing format', () => { expect(formatDoormileTimestamp('2026-08-27T14:30:00Z')).toBe('27 Aug 2026, 02:30 PM'); }); it('should honour a caller-supplied format', () => { expect(formatDoormileTimestamp('2026-08-27T14:30:00Z', 'YYYY-MM-DD HH:mm')).toBe('2026-08-27 14:30'); }); it('should return the em-dash fallback for an unusable timestamp', () => { expect(formatDoormileTimestamp(null)).toBe('—'); expect(formatDoormileTimestamp('garbage')).toBe('—'); }); it('should return a caller-supplied fallback instead of the em dash', () => { expect(formatDoormileTimestamp(undefined, 'DD MMM', 'Not set')).toBe('Not set'); }); }); describe('doormileDay', () => { it('should return the calendar day the wall-clock reading falls on', () => { expect(doormileDay('2026-08-27T14:30:00Z')).toBe('2026-08-27'); }); it('should keep a late-evening row on its own day rather than rolling it forward', () => { expect(doormileDay('2026-08-27T23:45:00Z')).toBe('2026-08-27'); }); it('should keep an early-morning row on its own day', () => { expect(doormileDay('2026-08-27T00:15:00Z')).toBe('2026-08-27'); }); it('should return undefined — not a string — for an unusable timestamp', () => { // Callers compare this against a selected date; a formatted "Invalid Date" // would silently never match. expect(doormileDay(null)).toBeUndefined(); expect(doormileDay('garbage')).toBeUndefined(); }); it('should agree across every zone-marker spelling of the same reading', () => { const forms = [ '2026-08-27T23:45:00', '2026-08-27T23:45:00Z', '2026-08-27T23:45:00+05:30', '2026-08-27T23:45:00+0000' ]; const days = forms.map(doormileDay); expect(new Set(days).size).toBe(1); expect(days[0]).toBe('2026-08-27'); }); it('should differ from a bare dayjs() parse whenever the runner is not on UTC', () => { // Guards the regression directly: bare dayjs() on a Z-suffixed IST reading // lands on a different day in any non-UTC zone. const raw = '2026-08-27T23:45:00Z'; expect(doormileDay(raw)).toBe('2026-08-27'); if (dayjs(raw).utcOffset() > 15) { expect(dayjs(raw).format('YYYY-MM-DD')).toBe('2026-08-28'); } }); }); });