Files
babymonitor/frontend/tests/unit/utils/time.test.ts
T

229 lines
7.1 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import {
formatMinutesAfter,
adjustForTimezone,
timeToMinutes,
formatMinutes,
toLocalDatetimeInputValue,
formatElapsedTime,
formatDateTime,
toUTCFromLocalInput,
getDurationMinutes,
clampToDay,
parseTime,
getDaysAgo,
getToday,
getLocalOffsetFromReference,
parseBackendDateTime
} from '../../../src/utils/time';
describe('formatMinutesAfter', () => {
it('formats minutes correctly with default reference (04:00)', () => {
expect(formatMinutesAfter(15)).toBe('04:15');
expect(formatMinutesAfter(-15)).toBe('03:45');
});
it('formats minutes correctly with custom reference', () => {
expect(formatMinutesAfter(15, '07:00')).toBe('07:15');
expect(formatMinutesAfter(-15, '07:00')).toBe('06:45');
});
it('handles wrap around midnight', () => {
expect(formatMinutesAfter(1425, '04:00')).toBe('03:45');
expect(formatMinutesAfter(-240, '04:00')).toBe('00:00');
expect(formatMinutesAfter(-255, '04:00')).toBe('23:45');
});
it('returns N/A for null minutes', () => {
expect(formatMinutesAfter(null)).toBe('N/A');
});
it('handles large number of minutes', () => {
expect(formatMinutesAfter(1440, '04:00')).toBe('04:00');
});
});
describe('timeToMinutes', () => {
it('converts HH:MM to total minutes', () => {
expect(timeToMinutes('00:00')).toBe(0);
expect(timeToMinutes('04:00')).toBe(240);
expect(timeToMinutes('23:59')).toBe(1439);
});
});
describe('formatMinutes', () => {
it('formats minutes into h m string', () => {
expect(formatMinutes(90)).toBe('1h 30m');
expect(formatMinutes(45)).toBe('45m');
expect(formatMinutes(0)).toBe('0m');
});
});
describe('adjustForTimezone', () => {
it('adjusts minutes by timezone offset', () => {
const offset = new Date().getTimezoneOffset();
expect(adjustForTimezone(0)).toBe(-offset);
});
});
describe('toLocalDatetimeInputValue', () => {
it('formats date to YYYY-MM-DDTHH:mm', () => {
const date = new Date(2024, 5, 9, 14, 30);
const result = toLocalDatetimeInputValue(date);
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
});
});
describe('formatElapsedTime', () => {
it('formats elapsed time correctly', () => {
const now = new Date();
const then = new Date(now.getTime() - 90 * 60000); // 90 mins ago
const iso = then.toISOString();
expect(formatElapsedTime(iso)).toBe('1h 30m');
const then2 = new Date(now.getTime() - 45 * 60000); // 45 mins ago
const iso2 = then2.toISOString();
expect(formatElapsedTime(iso2)).toBe('45m');
});
it('handles ISO strings with spaces instead of T', () => {
const now = new Date();
const then = new Date(now.getTime() - 10 * 60000);
const iso = then.toISOString().replace('T', ' ');
expect(formatElapsedTime(iso)).toBe('10m');
});
});
describe('formatDateTime', () => {
it('formats date object to YYYY-MM-DD HH:mm', () => {
const date = new Date(2024, 5, 9, 14, 30);
const result = formatDateTime(date);
expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
});
it('formats ISO string to YYYY-MM-DD HH:mm', () => {
const iso = '2024-06-09T14:30:00Z';
const result = formatDateTime(iso);
expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
});
it('handles strings without Z by appending it', () => {
const iso = '2024-06-09T14:30:00';
const result = formatDateTime(iso);
expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/);
});
});
describe('toUTCFromLocalInput', () => {
it('converts local datetime input to ISO UTC string', () => {
const input = '2024-06-09T14:30';
const result = toUTCFromLocalInput(input);
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/);
});
it('returns empty string for invalid input', () => {
expect(toUTCFromLocalInput('invalid')).toBe('');
});
});
describe('getDurationMinutes', () => {
it('calculates duration correctly between two dates', () => {
const start = '2024-06-09T10:00:00Z';
const end = '2024-06-09T11:30:00Z';
expect(getDurationMinutes(start, end)).toBe(90);
});
it('calculates duration correctly between date objects', () => {
const start = new Date('2024-06-09T10:00:00Z');
const end = new Date('2024-06-09T11:30:00Z');
expect(getDurationMinutes(start, end)).toBe(90);
});
});
describe('clampToDay', () => {
it('clamps to start of day', () => {
const date = new Date(2024, 5, 9, 14, 30);
const clamped = clampToDay(date, 'start');
expect(clamped.getHours()).toBe(0);
expect(clamped.getMinutes()).toBe(0);
});
it('clamps to end of day', () => {
const date = new Date(2024, 5, 9, 14, 30);
const clamped = clampToDay(date, 'end');
expect(clamped.getHours()).toBe(23);
expect(clamped.getMinutes()).toBe(59);
});
});
describe('parseTime', () => {
it('parses HH:MM string to Date object on today', () => {
const timeStr = '14:30';
const parsed = parseTime(timeStr);
const now = new Date();
expect(parsed.getHours()).toBe(14);
expect(parsed.getMinutes()).toBe(30);
expect(parsed.getDate()).toBe(now.getDate());
});
it('parses HH:MM string to Date object on a specific date', () => {
const baseDate = new Date(2024, 5, 9);
const parsed = parseTime('14:30', baseDate);
expect(parsed.getFullYear()).toBe(2024);
expect(parsed.getMonth()).toBe(5); // June is 5 in JS Date
expect(parsed.getDate()).toBe(9);
expect(parsed.getHours()).toBe(14);
expect(parsed.getMinutes()).toBe(30);
});
});
describe('getDaysAgo', () => {
it('returns ISO date string for N days ago', () => {
const today = getToday();
expect(getDaysAgo(0)).toBe(today);
// Note: this depends on the current date, so we can only check format and relative-ish
expect(getDaysAgo(1)).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
});
describe('getToday', () => {
it('returns today in YYYY-MM-DD format', () => {
const today = getToday();
const expected = new Date().toISOString().split('T')[0];
expect(today).toBe(expected);
});
});
describe('getLocalOffsetFromReference', () => {
it('calculates local offset from reference', () => {
const utcMinutes = 1440 / 2; // Midday UTC (720 mins)
const reference = '04:00'; // 240 mins
// adjustForTimezone(720) = 720 - offset
// timeToMinutes('04:00') = 240
// result = (720 - offset) - 240 = 480 - offset
const offset = new Date().getTimezoneOffset();
expect(getLocalOffsetFromReference(utcMinutes, reference)).toBe(480 - offset);
});
});
describe('parseBackendDateTime', () => {
it('parses ISO string correctly', () => {
const iso = '2024-06-09T14:30:00Z';
const parsed = parseBackendDateTime(iso);
expect(parsed.toISOString()).toBe(new Date(iso).toISOString());
});
it('appends Z to strings without timezone', () => {
const iso = '2024-06-09T14:30:00';
const parsed = parseBackendDateTime(iso);
expect(parsed.toISOString()).toBe(new Date(iso + 'Z').toISOString());
});
it('handles already Z-terminated strings', () => {
const iso = '2024-06-09T14:30:00Z';
const parsed = parseBackendDateTime(iso);
expect(parsed.toISOString()).toBe(new Date(iso).toISOString());
});
});