import { describe, expect, test } from 'vitest' import { combineDateTime, extractTime, formatIsoDate, formatIsoDateTime, localTzOffset, nowIsoLocal, parseFormDate, } from './dates' describe('parseFormDate', () => { test('возвращает null для не-строки и пустой строки', () => { expect(parseFormDate(null)).toBeNull() expect(parseFormDate(undefined)).toBeNull() expect(parseFormDate(123)).toBeNull() expect(parseFormDate('')).toBeNull() }) test('pure-date YYYY-MM-DD парсится как локальная полуночь (без TZ-сдвига)', () => { const d = parseFormDate('2026-05-08')! expect(d.getFullYear()).toBe(2026) expect(d.getMonth()).toBe(4) // май = 4 (zero-indexed) expect(d.getDate()).toBe(8) expect(d.getHours()).toBe(0) expect(d.getMinutes()).toBe(0) }) test('ISO datetime с offset парсится корректно', () => { const d = parseFormDate('2026-05-08T14:30:00+03:00')! expect(d).toBeInstanceOf(Date) expect(Number.isNaN(d.getTime())).toBe(false) }) test('far-future (year >= 9999) возвращает null', () => { expect(parseFormDate('9999-12-31T23:59:59Z')).toBeNull() expect(parseFormDate('10000-01-01')).toBeNull() }) test('невалидная строка → null', () => { expect(parseFormDate('not-a-date')).toBeNull() }) }) describe('formatIsoDate', () => { test('форматирует Date как YYYY-MM-DD в local TZ', () => { const d = new Date(2026, 4, 8) // 8 мая 2026 local expect(formatIsoDate(d)).toBe('2026-05-08') }) test('паддит month/day двумя цифрами', () => { const d = new Date(2026, 0, 5) expect(formatIsoDate(d)).toBe('2026-01-05') }) }) describe('localTzOffset', () => { test('возвращает offset в формате ±HH:mm', () => { const off = localTzOffset(new Date()) expect(off).toMatch(/^[+-]\d{2}:\d{2}$/) }) }) describe('formatIsoDateTime', () => { test('собирает ISO с временем по умолчанию 00:00', () => { const d = new Date(2026, 4, 8) // 8 мая 2026 local const iso = formatIsoDateTime(d) expect(iso).toMatch(/^2026-05-08T00:00:00[+-]\d{2}:\d{2}$/) }) test('применяет переданное время', () => { const d = new Date(2026, 4, 8) const iso = formatIsoDateTime(d, '14:30') expect(iso).toMatch(/^2026-05-08T14:30:00[+-]\d{2}:\d{2}$/) }) test('round-trip: parse → format не меняет дату/время', () => { const orig = formatIsoDateTime(new Date(2026, 4, 8), '14:30') const parsed = parseFormDate(orig)! expect(parsed.getFullYear()).toBe(2026) expect(parsed.getMonth()).toBe(4) expect(parsed.getDate()).toBe(8) expect(parsed.getHours()).toBe(14) expect(parsed.getMinutes()).toBe(30) }) }) describe('extractTime', () => { test('возвращает HH:mm из ISO datetime', () => { // 14:30 в local TZ — формируем явно через formatIsoDateTime const iso = formatIsoDateTime(new Date(2026, 4, 8), '14:30') expect(extractTime(iso, '00:00')).toBe('14:30') }) test('возвращает fallback для пустой строки', () => { expect(extractTime(undefined, '23:59')).toBe('23:59') expect(extractTime('', '00:00')).toBe('00:00') }) test('возвращает fallback для невалидной строки', () => { expect(extractTime('garbage', '12:34')).toBe('12:34') }) test('паддит часы и минуты двумя цифрами', () => { const iso = formatIsoDateTime(new Date(2026, 4, 8), '05:07') expect(extractTime(iso, '00:00')).toBe('05:07') }) }) describe('combineDateTime', () => { test('null Date → пустая строка (для clear button)', () => { expect(combineDateTime(null, '14:30', 'previous')).toBe('') }) test('собирает ISO datetime из Date + HH:mm', () => { const d = new Date(2026, 4, 8) const iso = combineDateTime(d, '14:30', '') expect(iso).toMatch(/^2026-05-08T14:30:00[+-]\d{2}:\d{2}$/) }) test('защитный fallback на 00:00 при невалидном time', () => { const d = new Date(2026, 4, 8) const iso = combineDateTime(d, 'invalid', '') expect(iso).toMatch(/^2026-05-08T00:00:00[+-]\d{2}:\d{2}$/) }) }) describe('nowIsoLocal', () => { test('возвращает ISO datetime в local TZ', () => { const iso = nowIsoLocal() expect(iso).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:00[+-]\d{2}:\d{2}$/) }) test('parsable обратно в Date', () => { const iso = nowIsoLocal() const d = parseFormDate(iso) expect(d).not.toBeNull() }) })