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(`
Exact date (optional)
`); }); it('should render custom prop', async () => { const page = await render(` `); expect(page.root).toEqualHtml(`
Enter Date (required)

Example 1990 12

`); }); 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); }); });