import { render } from '@stencil/vitest';
import { isInvalidYear } from '../utils';
describe('ontario-date-input', () => {
const dispatchInputEvent = (input: HTMLInputElement) => {
const event =
typeof InputEvent === 'function'
? new InputEvent('input', { bubbles: true, composed: true })
: new Event('input', { bubbles: true, composed: true });
input.dispatchEvent(event);
};
it('renders deafult state', async () => {
const page = await render(``);
expect(page.root).toEqualHtml(`
`);
});
it('should render custom prop', async () => {
const page = await render(`
`);
expect(page.root).toEqualHtml(`
`);
});
it('hydrates the internal fields from a plain ISO value', async () => {
const page = await render(``);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
expect(inputs?.[0].value).toBe('2024');
expect(inputs?.[1].value).toBe('02');
expect(inputs?.[2].value).toBe('20');
expect((page.root as HTMLOntarioDateInputElement).value).toBe('2024-02-20T00:00:00.000Z');
});
it('hydrates the internal fields from a full ISO value and normalizes the host value', async () => {
const page = await render(``);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
expect(inputs?.[0].value).toBe('2024');
expect(inputs?.[1].value).toBe('02');
expect(inputs?.[2].value).toBe('20');
expect((page.root as HTMLOntarioDateInputElement).value).toBe('2024-02-20T00:00:00.000Z');
});
it('updates the aggregate host value after field input completes a valid date', async () => {
const page = await render(``);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput, monthInput, dayInput] = Array.from(inputs ?? []);
yearInput.value = '2024';
dispatchInputEvent(yearInput);
monthInput.value = '02';
dispatchInputEvent(monthInput);
dayInput.value = '20';
dispatchInputEvent(dayInput);
await page.waitForChanges();
expect((page.root as HTMLOntarioDateInputElement).value).toBe('2024-02-20T00:00:00.000Z');
});
it('emits a host `input` event when the aggregate value changes', async () => {
const page = await render(``);
const emitSpy = vi.fn();
page.root?.addEventListener('input', emitSpy);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput, monthInput, dayInput] = Array.from(inputs ?? []);
yearInput.value = '2024';
dispatchInputEvent(yearInput);
monthInput.value = '02';
dispatchInputEvent(monthInput);
dayInput.value = '20';
dispatchInputEvent(dayInput);
await page.waitForChanges();
expect(emitSpy).toHaveBeenCalledTimes(1);
expect((emitSpy.mock.calls[0][0].target as HTMLOntarioDateInputElement).value).toBe('2024-02-20T00:00:00.000Z');
expect(emitSpy.mock.calls[0][0].detail).toEqual({ value: '2024-02-20T00:00:00.000Z' });
});
it('preserves field-level input events while only exposing one aggregate host input event', async () => {
const page = await render(``);
const hostInputSpy = vi.fn();
const fieldInputSpy = vi.fn();
page.root?.addEventListener('input', hostInputSpy);
document.addEventListener('inputOnInput', fieldInputSpy);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput, monthInput, dayInput] = Array.from(inputs ?? []);
yearInput.value = '2024';
dispatchInputEvent(yearInput);
monthInput.value = '02';
dispatchInputEvent(monthInput);
dayInput.value = '20';
dispatchInputEvent(dayInput);
await page.waitForChanges();
expect(fieldInputSpy).toHaveBeenCalledTimes(3);
expect(fieldInputSpy.mock.calls[0][0].detail).toEqual({ value: '2024', fieldType: 'year' });
expect(fieldInputSpy.mock.calls[1][0].detail).toEqual({ value: '02', fieldType: 'month' });
expect(fieldInputSpy.mock.calls[2][0].detail).toEqual({ value: '20', fieldType: 'day' });
expect(hostInputSpy).toHaveBeenCalledTimes(1);
expect(hostInputSpy.mock.calls[0][0].target).toBe(page.root);
});
it('emits a host `change` event when the aggregate value changes', async () => {
const page = await render(``);
const emitSpy = vi.fn();
page.root?.addEventListener('change', emitSpy);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput, monthInput, dayInput] = Array.from(inputs ?? []);
yearInput.value = '2024';
yearInput.dispatchEvent(new Event('change'));
monthInput.value = '02';
monthInput.dispatchEvent(new Event('change'));
dayInput.value = '20';
dayInput.dispatchEvent(new Event('change'));
await page.waitForChanges();
expect(emitSpy).toHaveBeenCalledTimes(1);
expect((emitSpy.mock.calls[0][0].target as HTMLOntarioDateInputElement).value).toBe('2024-02-20T00:00:00.000Z');
expect(emitSpy.mock.calls[0][0].detail).toEqual({ value: '2024-02-20T00:00:00.000Z' });
});
it('emits a host `change` event after the aggregate value was already updated by `input` events', async () => {
const page = await render(``);
const emitSpy = vi.fn();
page.root?.addEventListener('change', emitSpy);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput, monthInput, dayInput] = Array.from(inputs ?? []);
yearInput.value = '2024';
dispatchInputEvent(yearInput);
monthInput.value = '02';
dispatchInputEvent(monthInput);
dayInput.value = '20';
dispatchInputEvent(dayInput);
await page.waitForChanges();
dayInput.dispatchEvent(new Event('change'));
await page.waitForChanges();
expect(emitSpy).toHaveBeenCalledTimes(1);
expect((emitSpy.mock.calls[0][0].target as HTMLOntarioDateInputElement).value).toBe('2024-02-20T00:00:00.000Z');
expect(emitSpy.mock.calls[0][0].detail).toEqual({ value: '2024-02-20T00:00:00.000Z' });
});
it('emits a host `input` event when the aggregate value is cleared', async () => {
const page = await render(``);
const emitSpy = vi.fn();
page.root?.addEventListener('input', emitSpy);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput] = Array.from(inputs ?? []);
yearInput.value = '';
dispatchInputEvent(yearInput);
await page.waitForChanges();
expect((page.root as HTMLOntarioDateInputElement).value).toBe('');
expect(emitSpy).toHaveBeenCalledTimes(1);
expect((emitSpy.mock.calls[0][0].target as HTMLOntarioDateInputElement).value).toBe('');
expect(emitSpy.mock.calls[0][0].detail).toEqual({ value: '' });
});
it('emits a host `change` event when the aggregate value is cleared', async () => {
const page = await render(``);
const emitSpy = vi.fn();
page.root?.addEventListener('change', emitSpy);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
const [yearInput] = Array.from(inputs ?? []);
yearInput.value = '';
yearInput.dispatchEvent(new Event('change'));
await page.waitForChanges();
expect((page.root as HTMLOntarioDateInputElement).value).toBe('');
expect(emitSpy).toHaveBeenCalledTimes(1);
expect((emitSpy.mock.calls[0][0].target as HTMLOntarioDateInputElement).value).toBe('');
expect(emitSpy.mock.calls[0][0].detail).toEqual({ value: '' });
});
it('reports an error and ignores invalid aggregate values', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const page = await render(``);
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
expect(inputs?.[0].value).toBe('');
expect(inputs?.[1].value).toBe('');
expect(inputs?.[2].value).toBe('');
expect((page.root as HTMLOntarioDateInputElement).value).toBe('2024-99-20');
expect(consoleErrorSpy).toHaveBeenCalled();
consoleErrorSpy.mockRestore();
});
it('reports an error and ignores array aggregate values passed at runtime', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const page = await render(``);
(page.root as HTMLOntarioDateInputElement).value = ['2024-02-20'] as unknown as string;
await page.waitForChanges();
const inputs = page.root?.shadowRoot?.querySelectorAll('input');
expect(inputs?.[0].value).toBe('2024');
expect(inputs?.[1].value).toBe('02');
expect(inputs?.[2].value).toBe('20');
expect((page.root as HTMLOntarioDateInputElement).value).toEqual(['2024-02-20']);
expect(consoleErrorSpy).toHaveBeenCalled();
consoleErrorSpy.mockRestore();
});
});
describe('date-validation-utils', () => {
it('is invalid year value - undefined string', () => {
// Note: Type system doesn't like `undefined` as `any` lets us force it for testing purposes
const value: any = undefined;
const isInvalidYearResult = isInvalidYear(value);
expect(isInvalidYearResult).toEqual(true);
});
it('is invalid year value - null string', () => {
// Note: Type system doesn't like `null` as `any` lets us force it for testing purposes
const value: any = null;
const isInvalidYearResult = isInvalidYear(value);
expect(isInvalidYearResult).toEqual(true);
});
it('is invalid year value - empty string', () => {
const value = ''; // Empty string
const isInvalidYearResult = isInvalidYear(value);
expect(isInvalidYearResult).toEqual(true);
});
it('is invalid year value - written out number', () => {
const value = 'two-thousand';
const isInvalidYearResult = isInvalidYear(value);
expect(isInvalidYearResult).toEqual(true);
});
it('is valid year value', () => {
const value = '2000';
const isValidYear = !isInvalidYear(value);
expect(isValidYear).toEqual(true);
});
it('is valid year when in range', () => {
const value = '2000';
const minYear = 1;
const maxYear = 9999;
const isValidYear = !isInvalidYear(value, minYear, maxYear);
expect(isValidYear).toEqual(true);
});
it('is valid year when in range but is minYear', () => {
const value = '2000';
const minYear = 2000;
const maxYear = 9999;
const isValidYear = !isInvalidYear(value, minYear, maxYear);
expect(isValidYear).toEqual(true);
});
it('is valid year when in range but is maxYear', () => {
const value = '2000';
const minYear = 1;
const maxYear = 2000;
const isValidYear = !isInvalidYear(value, minYear, maxYear);
expect(isValidYear).toEqual(true);
});
it('is invalid year when out of range', () => {
const value = '2000';
const minYear = 1;
const maxYear = 1999;
const isValidYear = !isInvalidYear(value, minYear, maxYear);
expect(isValidYear).toEqual(false);
});
});