import { toBasicISOString, toLocalISOString } from '@douglasneuroinformatics/libjs';
import { getByText, render, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DateField } from './DateField.tsx';
describe('DateField', () => {
const setError = vi.fn();
const setValue = vi.fn();
beforeEach(() => {
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(new Date(2025, 0, 1, 22));
});
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
});
it('should render an input with an empty string value ', () => {
render();
const input: HTMLInputElement = screen.getByRole('textbox');
expect(input).toBeInTheDocument();
expect(input.value).toBe('');
});
it('should allow typing arbitrary text while focused', async () => {
render();
const input: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(input, 'foo');
expect(input.value).toBe('foo');
});
it('should not initially show the datepicker', () => {
render();
expect(() => screen.getByTestId('datepicker')).toThrow();
});
it('should show the datepicker when the input is clicked', async () => {
render();
const input = screen.getByRole('textbox');
expect(() => screen.getByTestId('datepicker')).toThrow();
await userEvent.click(input);
expect(screen.getByTestId('datepicker')).toBeInTheDocument();
});
it('should discard invalid text when unfocused, once the date picker is closed', async () => {
render();
const input: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(input, 'foo');
expect(input.value).toBe('foo');
const datepicker = screen.getByTestId('datepicker');
await userEvent.click(datepicker);
expect(datepicker).toBeInTheDocument();
await userEvent.type(input, 'foo');
await userEvent.click(screen.getByText('Date'));
expect(() => screen.getByTestId('datepicker')).toThrow();
expect(input.value).toBe('');
});
it('should not attempt to set the value if the date is invalid', async () => {
render();
const input: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(input, 'foo');
expect(input.value).toBe('foo');
await userEvent.click(screen.getByText('Date'));
expect(input.value).toBe('');
expect(setValue).not.toHaveBeenCalled();
});
it('should attempt to set the value if the user enters a correct date', async () => {
render();
const input: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(input, '2000-01-01');
await userEvent.click(screen.getByText('Date'));
expect(input.value).toBe('2000-01-01');
expect(setValue).toHaveBeenCalledOnce();
expect(setValue).toHaveBeenCalledWith(new Date('2000-01-01'));
});
it('should allow setting the date using the date picker', async () => {
render();
const input: HTMLInputElement = screen.getByRole('textbox');
let datepicker: HTMLElement;
let expectedDate: Date;
let expectedDateString: string;
await userEvent.click(input);
datepicker = screen.getByTestId('datepicker');
await userEvent.click(getByText(datepicker, '1'));
expectedDate = new Date(new Date().setDate(1));
expectedDateString = toLocalISOString(expectedDate).split('T')[0]!;
expect(toBasicISOString(setValue.mock.lastCall?.[0])).toBe(expectedDateString);
await userEvent.click(input);
datepicker = screen.getByTestId('datepicker');
await userEvent.click(getByText(datepicker, '2'));
expectedDate = new Date(new Date().setDate(2));
expectedDateString = toLocalISOString(expectedDate).split('T')[0]!;
expect(toBasicISOString(setValue.mock.lastCall?.[0])).toBe(expectedDateString);
});
it('should render the value provided as a prop', () => {
const today = new Date();
render();
const input: HTMLInputElement = screen.getByRole('textbox');
expect(input.value).toBe(toBasicISOString(today));
});
});