updated the test
This commit is contained in:
133
tests/lib/doormileTimestamp.test.js
Normal file
133
tests/lib/doormileTimestamp.test.js
Normal file
@@ -0,0 +1,133 @@
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user