Files
F-r-ldraledigplannerare/frontend/tests/unit/utils/dates.test.ts
T
jacobh2 f2b3920cbc Share one date formatter, and stop describing files that are gone
Two components each carried the same four lines turning a date into an ISO
string from local parts. They now share one helper, deliberately separate
from the one that formats in UTC: substituting one for the other shifts the
day by one anywhere east of Greenwich, Sweden included, so a test pins the
difference rather than a comment asking nicely.

Creating a period ran its own copy of the sync-and-report sequence purely so
it could name a wish the lookup could not yet see; it now passes that wish to
the ordinary path instead.

Two comments still explained their relationship to components deleted some
time ago, which would send the next reader looking for files that are not
there, and some styling outlived the markup it described.
2026-09-18 01:12:58 +02:00

257 lines
8.1 KiB
TypeScript

import { describe, it, expect, afterEach, vi } from 'vitest'
import {
formatDateForDisplay,
formatDateForInput,
formatDateLocalISO,
parseInputDate,
isValidDate,
getDayName,
getMonthName,
getISOWeekNumber,
formatHolidayName,
datesInRange,
} from '@/utils/dates'
describe('dates.ts utilities', () => {
describe('formatDateForDisplay', () => {
it('formats ISO string to Swedish display format', () => {
const result = formatDateForDisplay('2026-06-15')
expect(result).toBe('15 juni 2026')
})
it('formats Date object to Swedish display format', () => {
const date = new Date('2026-06-15T00:00:00Z')
const result = formatDateForDisplay(date)
expect(result).toBe('15 juni 2026')
})
it('handles January correctly', () => {
const result = formatDateForDisplay('2026-01-01')
expect(result).toBe('1 januari 2026')
})
it('handles December correctly', () => {
const result = formatDateForDisplay('2026-12-25')
expect(result).toBe('25 december 2026')
})
it('returns empty string for invalid date', () => {
const result = formatDateForDisplay('invalid')
expect(result).toBe('')
})
it('defaults to Swedish when no locale is passed', () => {
expect(formatDateForDisplay('2026-06-15')).toBe(formatDateForDisplay('2026-06-15', 'sv'))
})
it('formats in English when the "en" locale is passed', () => {
const result = formatDateForDisplay('2026-06-15', 'en')
expect(result).toBe('June 15, 2026')
})
it('formats in Swedish when the "sv" locale is explicitly passed', () => {
const result = formatDateForDisplay('2026-06-15', 'sv')
expect(result).toBe('15 juni 2026')
})
it('returns empty string for invalid date in the "en" locale too', () => {
expect(formatDateForDisplay('invalid', 'en')).toBe('')
})
})
describe('formatDateForInput', () => {
it('formats ISO string to input format', () => {
const result = formatDateForInput('2026-06-15')
expect(result).toBe('2026-06-15')
})
it('formats Date object to input format', () => {
const date = new Date('2026-06-15T00:00:00Z')
const result = formatDateForInput(date)
expect(result).toBe('2026-06-15')
})
it('pads month and day with zeros', () => {
const result = formatDateForInput('2026-01-05')
expect(result).toBe('2026-01-05')
})
it('returns empty string for invalid date', () => {
const result = formatDateForInput('invalid')
expect(result).toBe('')
})
})
describe('formatDateLocalISO', () => {
afterEach(() => {
vi.unstubAllEnvs()
})
it('formats a Date built from local year/month/day components', () => {
const date = new Date(2026, 5, 15) // local June 15, 2026
expect(formatDateLocalISO(date)).toBe('2026-06-15')
})
it('pads single-digit month and day', () => {
const date = new Date(2026, 0, 5) // local January 5, 2026
expect(formatDateLocalISO(date)).toBe('2026-01-05')
})
it('reads the local calendar date, not the UTC one — this is the whole point of the function', () => {
// Pacific/Kiritimati is UTC+14. A moment at 23:30 UTC on Jan 1 is
// already local Jan 2 there. `formatDateForInput` (UTC getters) must
// report Jan 1; `formatDateLocalISO` (local getters) must report
// Jan 2. If this ever collapses to the same value, someone has
// swapped in the UTC variant and reintroduced the off-by-one-day bug
// this function exists to avoid.
vi.stubEnv('TZ', 'Pacific/Kiritimati')
const date = new Date(Date.UTC(2026, 0, 1, 23, 30))
expect(formatDateForInput(date)).toBe('2026-01-01')
expect(formatDateLocalISO(date)).toBe('2026-01-02')
})
})
describe('parseInputDate', () => {
it('parses ISO date string to Date object', () => {
const result = parseInputDate('2026-06-15')
expect(result).toBeInstanceOf(Date)
expect(result.toISOString()).toContain('2026-06-15')
})
it('creates valid Date for valid input', () => {
const result = parseInputDate('2026-06-15')
expect(isValidDate(result)).toBe(true)
})
})
describe('isValidDate', () => {
it('returns true for valid Date object', () => {
const date = new Date('2026-06-15')
expect(isValidDate(date)).toBe(true)
})
it('returns false for invalid Date', () => {
const date = new Date('invalid')
expect(isValidDate(date)).toBe(false)
})
it('returns false for non-Date values', () => {
expect(isValidDate('2026-06-15')).toBe(false)
expect(isValidDate(20260615)).toBe(false)
expect(isValidDate(null)).toBe(false)
expect(isValidDate(undefined)).toBe(false)
})
})
describe('getDayName', () => {
it('returns Swedish day name', () => {
const date = new Date('2026-06-15T00:00:00Z') // Monday
const result = getDayName(date)
expect(result.toLowerCase()).toBe('måndag')
})
it('returns empty string for invalid date', () => {
const result = getDayName(new Date('invalid'))
expect(result).toBe('')
})
})
describe('getMonthName', () => {
it('returns Swedish month name', () => {
const date = new Date('2026-06-15T00:00:00Z')
const result = getMonthName(date)
expect(result).toBe('juni')
})
it('returns correct month names for all months', () => {
const monthNames = [
'januari',
'februari',
'mars',
'april',
'maj',
'juni',
'juli',
'augusti',
'september',
'oktober',
'november',
'december',
]
monthNames.forEach((monthName, index) => {
const date = new Date(`2026-${String(index + 1).padStart(2, '0')}-15T00:00:00Z`)
const result = getMonthName(date)
expect(result).toBe(monthName)
})
})
it('returns empty string for invalid date', () => {
const result = getMonthName(new Date('invalid'))
expect(result).toBe('')
})
})
describe('getISOWeekNumber', () => {
it('returns 0 for an invalid date', () => {
expect(getISOWeekNumber(new Date('invalid'))).toBe(0)
})
it('computes the ISO week number for a mid-week date', () => {
// 2026-06-15 is a Monday.
expect(getISOWeekNumber(new Date('2026-06-15T00:00:00Z'))).toBe(25)
})
it('handles a Sunday correctly (getUTCDay() === 0 falls back to ISO day 7)', () => {
// 2026-01-11 is a Sunday, closing out ISO week 2 of 2026.
expect(getISOWeekNumber(new Date('2026-01-11T00:00:00Z'))).toBe(2)
})
})
describe('formatHolidayName', () => {
it('returns the name unchanged when it has no ";" segments', () => {
expect(formatHolidayName('Midsommardagen')).toBe('Midsommardagen')
})
it('strips a trailing weekday segment', () => {
expect(formatHolidayName('Påskdagen; Söndag')).toBe('Påskdagen')
})
it('falls back to the first segment when every segment is a weekday name', () => {
// Contrived input where nothing survives the weekday filter — the
// function must not return an empty string in that case.
expect(formatHolidayName('Söndag; Måndag')).toBe('Söndag')
})
})
describe('datesInRange', () => {
it('returns an empty array when the start date is invalid', () => {
expect(datesInRange('invalid', '2026-06-15')).toEqual([])
})
it('returns an empty array when the end date is invalid', () => {
expect(datesInRange('2026-06-15', 'invalid')).toEqual([])
})
it('returns the inclusive range in ascending order for start before end', () => {
expect(datesInRange('2026-06-01', '2026-06-03')).toEqual([
'2026-06-01',
'2026-06-02',
'2026-06-03',
])
})
it('returns the same range regardless of argument order (reversed input)', () => {
expect(datesInRange('2026-06-03', '2026-06-01')).toEqual([
'2026-06-01',
'2026-06-02',
'2026-06-03',
])
})
it('returns a single date when start equals end', () => {
expect(datesInRange('2026-06-01', '2026-06-01')).toEqual(['2026-06-01'])
})
})
})