import { describe, each, expect, getFormValues, it, render, select, selectAll, trapEvents, user, waitFor, waitForEvent, } from "../../utils/Test"; describe("uwc-input[type=date]", () => { const getVisibleText = (input: UwcInputElement) => input.querySelector("[part=root]")?.textContent ?? ""; const setup = < InputProps extends Record< string, JSX.Attributes & { type?: "date" } >, >( props: InputProps, ) => { const page = render(
{Object.entries(props).map(([name, props]) => ( ))}
, ); const inputs = selectAll(page, "uwc-input").reduce( (dict, input) => { dict[input.name as keyof typeof dict] = input; return dict; }, {} as Record, ); const form = select(page, "form"); return { page, form, ...inputs }; }; it("should render a locale-aware default if no pattern is specified", async () => { const page = render(); const input = select(page, "uwc-input"); expect(getVisibleText(input)).to.eq("mm/dd/yyyy"); }); it("should start with a given content value", () => { const page = render( , ); const input = select(page, "uwc-input"); expect(getVisibleText(input)).to.eq("10/30/2024"); expect(input.value).to.be.instanceof(Date); expect((input.value as Date).toISOString()).to.eq( new Date(2024, 9, 30).toISOString(), ); }); it("should start with an undefined value", () => { const page = render(); const input = select(page, "uwc-input"); expect(input.value).to.eq(undefined); }); it("should be possible to override the default placeholders globally", async () => { const originalPlaceholder = UwcInputElement.config.date.defaults["de-DE"]; UwcInputElement.config.date.defaults["de-DE"] = { pattern: "dd.MM.yyyy", placeholder: "TT.MM.JJJJ", }; const page = render(); const input = select(page, "uwc-input"); try { expect(getVisibleText(input)).to.eq("TT.MM.JJJJ"); } finally { UwcInputElement.config.date.defaults["de-DE"] = originalPlaceholder!; } }); it("should be possible to override the default placeholders by specifying a placeholder", () => { const page = render( , ); const input = select(page, "uwc-input"); expect(getVisibleText(input)).to.eq("tt.mm.jjjj"); }); it("should be possible to change a part when specifying a custom placeholder", async () => { const page = render( , ); const input = select(page, "uwc-input"); const years = select(input, "div[part=year]"); await user.type(years, "1"); expect(getVisibleText(input)).to.eq("tt.mm.0001"); }); it("should not become invalid when not required and pressing an invalid key", async () => { const page = render(); const input = select(page, "uwc-input"); const years = select(input, "div[part=year]"); await user.type(years, "a"); expect(getVisibleText(input)).to.eq("mm/dd/yyyy"); expect(input.validity.valid).to.eq(true); }); it("should ignore placeholders that don't match the pattern", () => { const page = render( , ); const input = select(page, "uwc-input"); expect(getVisibleText(input)).to.eq("yyyy-MM-dd"); }); it("should select the first non-literal part on click", async () => { const page = render( , ); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); await user.click(input); expect(year).to.match(":focus-within"); }); it("should be possible to tab forwards through the parts", async () => { const page = render(); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); const month = select(input, "div[part=month]"); const day = select(input, "div[part=day]"); await user.tab(); expect(document.activeElement).to.eq(month); await user.tab(); expect(document.activeElement).to.eq(day); await user.tab(); expect(document.activeElement).to.eq(year); }); it("should be possible to tab backwards through the parts", async () => { const page = render(); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); const month = select(input, "div[part=month]"); const day = select(input, "div[part=day]"); await user.click(year); expect(document.activeElement).to.eq(year); await user.tab({ shift: true }); expect(document.activeElement).to.eq(day); await user.tab({ shift: true }); expect(document.activeElement).to.eq(month); }); it("should be possible to increment and decrement years with arrow keys", async () => { const page = render(); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); await user.click(year); await user.keyboard("{ArrowUp}"); expect(year.textContent).to.eq("0001"); await user.keyboard("{ArrowUp}"); expect(year.textContent).to.eq("0002"); await user.keyboard("{ArrowDown}"); expect(year.textContent).to.eq("0001"); }); it("should be possible to increment and decrement months with arrow keys", async () => { const page = render(); const input = select(page, "uwc-input"); const month = select(input, "div[part=month]"); await user.click(month); await user.keyboard("{ArrowUp}"); expect(month.textContent).to.eq("01"); await user.keyboard("{ArrowUp}"); expect(month.textContent).to.eq("02"); await user.keyboard("{ArrowDown}"); expect(month.textContent).to.eq("01"); }); it("should be possible to increment and decrement days with arrow keys", async () => { const page = render(); const input = select(page, "uwc-input"); const day = select(input, "div[part=day]"); await user.click(day); await user.keyboard("{ArrowUp}"); expect(day.textContent).to.eq("01"); await user.keyboard("{ArrowUp}"); expect(day.textContent).to.eq("02"); await user.keyboard("{ArrowDown}"); expect(day.textContent).to.eq("01"); }); it("should automatically select the next part if the current is considered 'full'", async () => { const page = render( , ); const input = select(page, "uwc-input"); const month = select(input, "div[part=month]"); const day = select(input, "div[part=day]"); await user.type(month, "12"); await waitFor(() => { expect(document.activeElement).to.eq(day); }); }); it("should clear a part if delete or backspace is pressed", async () => { const page = render( , ); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); const month = select(input, "div[part=month]"); const day = select(input, "div[part=day]"); await user.type(year, "1{Backspace}"); expect(year.textContent).to.eq("yyyy"); await user.type(month, "1{Backspace}"); expect(month.textContent).to.eq("MM"); await user.type(day, "1{Backspace}"); expect(day.textContent).to.eq("dd"); await user.type(year, "1{Delete}"); expect(year.textContent).to.eq("yyyy"); await user.type(month, "1{Delete}"); expect(month.textContent).to.eq("MM"); await user.type(day, "1{Delete}"); expect(day.textContent).to.eq("dd"); }); it("should report bad input if incomplete", async () => { const page = render( , ); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); await user.type(year, "1"); expect(input.validity.badInput).to.eq(true); }); it("should report value missing if required and nothing is filled out", async () => { const page = render( , ); const input = select(page, "uwc-input"); expect(input.validity.valueMissing).to.eq(true); }); it("should move from bad input to value missing if every part is cleared", async () => { const page = render( , ); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); await user.type(year, "1"); expect(input.validity.badInput).to.eq(true); await user.type(year, "{Backspace}"); expect(input.validity.badInput).to.eq(false); expect(input.validity.valueMissing).to.eq(true); }); it("should include a datepicker in the content part", async () => { const page = render(); expect(() => select(page, "uwc-datepicker")).not.to.throw(); }); it("should be possible to customize the dialogs datepicker", async () => { const page = render( , ); expect(select(page, "uwc-datepicker")).to.include({ order: "month day year", }); }); it("should be possible to select a date from the datepicker", async () => { const page = render(); const input = select(page, "uwc-input"); const firstValue = select(input, "li[data-value]"); await user.click(input); await user.click(firstValue); expect((input.value as Date).toISOString()).to.eq( firstValue.getAttribute("data-value"), ); }); it("should not emit input events for invalid changes", async () => { const page = render(); const input = select(page, "uwc-input"); const year = select(input, "div[part=year]"); const waitForInput = waitForEvent(input, "input", { timeout: 10 }); await user.type(year, "a"); await expect(waitForInput).to.eventually.be.rejected; }); it("should render the value according to a given locale", async () => { const page = render( , ); expect(getVisibleText(select(page, "uwc-input"))).to.eq("01.10.2024"); }); it("should align the nested datepicker to the initial value", () => { const page = render(); expect(select(page, "uwc-input").value).to.deep.eq( select(page, "uwc-datepicker")["selected-date"], ); }); it("should emit a change event on selection", async () => { const page = render(); const input = select(page, "uwc-input"); const waitForChange = waitForEvent(input, "change"); await user.click(input); await user.click(select(input, "li[data-value]")); await expect(waitForChange).to.eventually.be.fulfilled; }); it("should close the datepicker on selection", async () => { const page = render(); const input = select(page, "uwc-input"); await user.click(input); await user.click(select(input, "li[data-value]")); expect(select(input, "dialog").checkVisibility()).to.be.false; }); it("should forward its min and max constraints", () => { const page = render( , ); const input = select(page, "uwc-input"); const datepicker = select(page, "uwc-datepicker"); expect(datepicker.getAttribute("min")).to.eq(input.getAttribute("min")); expect(datepicker.getAttribute("max")).to.eq(input.getAttribute("max")); }); it("should be possible to change the min and max values", () => { const page = render(); const input = select(page, "uwc-input"); input.setAttribute("min", "2024-01-01"); const datepicker = select(input, "uwc-datepicker"); expect(datepicker.getAttribute("min")).to.eq( new Date(2024, 0, 1).toISOString(), ); }); it("should be possible to .clear() it", () => { const page = render(
, ); const form = select(page, "form"); const input = select(page, "uwc-input"); input.clear(); expect(input.value).to.eq(undefined); expect(getFormValues(form)).to.deep.eq({}); }); it("should not change TZ offsets", () => { const now = new Date(); const { input } = setup({ input: { value: now.toISOString() }, }); expect(input.value).to.be.instanceof(Date); expect((input.value as Date).toISOString()).to.eq(now.toISOString()); }); describe("events", () => { it("should not emit a change event if the same date is selected again", async () => { const page = render(); const events = trapEvents(page, ["change"]); await user.click(select(page, "uwc-input")); await user.click( select( page, `uwc-input dialog ul li[data-value="${new Date(2024, 8, 9).toISOString()}"]`, ), ); expect(events.get()).to.deep.eq([]); }); }); describe("formdata", () => { it("should add its value to the current form", () => { const page = render(
, ); expect(getFormValues(select(page, "form"))).to.deep.eq({ foo: "2024-10-09T22:00:00.000Z", }); }); }); describe("validation", () => { each< Partial> & { type: "date"; message: string; expectedInvalidState?: boolean; expectedErrormessages?: string[]; } >( [ { message: "should be valid if date is between min and max", type: "date", max: "2024-01-01", min: "2023-01-01", value: "2023-06-01", expectedInvalidState: false, }, { message: "should be invalid if date is below min", type: "date", min: "2023-01-01", value: "2022-06-01", expectedInvalidState: true, }, { message: "should be invalid if date is above max", type: "date", max: "2024-01-01", value: "2024-06-01", expectedInvalidState: true, }, ], "$message", async ({ message: _, expectedInvalidState, expectedErrormessages, ...props }) => { const page = render(); const input = select(page, "uwc-input"); if (expectedInvalidState != null) { expect(input.matches(":invalid")).to.eq(expectedInvalidState); } expectedErrormessages?.forEach((message) => expect( Array.from(input.querySelectorAll("small.validation-message")).some( (element) => element.textContent === message, ), ).to.eq( true, `expected to find a small.validation-message with '${message}'`, ), ); }, ); it("should revalidate if min changes", () => { const page = render( , ); const input = select(page, "uwc-input"); expect(input.validity).to.include({ valid: true, rangeUnderflow: false }); input.min = "2024-10-11"; expect(input.validity).to.include({ valid: false, rangeUnderflow: true }); }); it("should revalidate if max changes", () => { const page = render( , ); const input = select(page, "uwc-input"); expect(input.validity).to.include({ valid: true, rangeOverflow: false }); input.max = "2024-10-09"; expect(input.validity).to.include({ valid: false, rangeOverflow: true }); }); it("should revalidate if required changes", () => { const page = render(); const input = select(page, "uwc-input"); expect(input.validity).to.include({ valid: true, valueMissing: false }); input.required = true; expect(input.validity).to.include({ valid: false, valueMissing: true }); }); }); describe("a11y", () => { it.todo("should have aria-describedby which describes the date format"); it.todo("should have accessible name 'choose date'"); it.todo( "should change its accessible name to 'change date, ${datestring}' once a date is chosen", ); }); });