import { pipe } from "@hesxenon/prelude/Function.js"; import { describe, each, expect, getFormValues, it, queryByRole, render, renderAsync, select, selectAll, trapEvents, user, waitFor, waitForEvent, } from "../../utils/Test"; const getVisibleText = (input: UwcInputElement) => { const placeholder = select(input, "div[part=placeholder]"); const values = select(input, "ul[part=values]"); const element = select(input, "div[part=root]"); return placeholder.checkVisibility({ visibilityProperty: true }) ? placeholder.innerText : (values.checkVisibility() ? values.innerText : "") + element.innerText; }; const getInput = (within: Element) => select(within, "uwc-input"); const getSuggestion = (within: Element, count: number) => select( within, `dialog[popover] :where(li, [data-value]):nth-child(${count})`, ); const getCurrentSuggestion = (within: Element) => within.querySelector("[aria-current]"); const setup = ( inputs: Record, "name">>, ) => { if (Object.keys(inputs).length === 0) { Object.assign(inputs, { input: {} }); } type Inputs = Key extends never ? { input: UwcInputElement } : { [name in Key]: UwcInputElement }; const page = render(
{Object.entries(inputs).map(([name, input]) => ( )} > ))}
, ); const form = select(page, "form"); const inputElements = selectAll(page, "uwc-input").reduce((inputs, input) => { inputs[input.name as keyof Inputs] = input as Inputs[keyof Inputs]; return inputs; }, {} as Inputs); return { ...inputElements, form, page, }; }; describe("uwc-input[type=text]", () => { it("should start with an empty value", () => { const page = render(); expect(select(page, "uwc-input").value).to.eq(""); }); it("should render a placeholder when empty", () => { const page = render(); const placeholder = select(page, "[part=placeholder]"); expect(window.getComputedStyle(placeholder).visibility).to.eq("visible"); }); it("should not render a placeholder when filled out", () => { const page = render(); const placeholder = select(page, "[part=placeholder]"); expect(window.getComputedStyle(placeholder).visibility).to.eq("hidden"); }); it("should add all values to its formdata if [multiple]", async () => { const page = render(
  • A
, ); expect(getFormValues(select(page, "form"))).to.deep.eq({ foo: ["A", "foo"], }); }); it("should update formdata when IDL value is set", () => { const page = render(
, ); const input = select(page, "uwc-input"); input.value = "foo"; expect(getFormValues(select(page, "form"))).to.deep.eq({ foo: "foo" }); }); it("should sync formdata when the name changes", async () => { const page = render(
, ); const form = select(page, "form"); const input = select(form, "uwc-input"); input.name = "baz"; await waitFor(() => expect(getFormValues(form)).to.deep.eq({ baz: "bar" })); }); it("should focus on mousedown", async () => { const page = render(); const input = select(page, "uwc-input"); await user.pointer({ keys: "[MouseLeft>]", target: input }); await waitFor(() => expect(input).to.match(":focus-within")); }); it("should not focus when mousedown is prevented", async () => { const page = render( , ); const input = select(page, "uwc-input"); await user.click(input); expect(input).to.not.match(":focus-within"); }); it("should not be possible to focus when disabled", async () => { const page = render(); const input = select(page, "uwc-input"); await expect(user.click(input)).to.eventually.be.rejected; }); it("should be possible to change the placeholder", () => { const page = render(); const input = select(page, "uwc-input"); input.placeholder = "bar"; expect(getVisibleText(input)).to.eq("bar"); }); it("should not be possible to insert line breaks via enter key", async () => { const page = render(); const input = select(page, "uwc-input"); await user.type(input, "ab{Enter}cd"); expect(input.value).to.eq("abcd"); }); it("should be possible to change the value programmatically", () => { const page = render(); const input = select(page, "uwc-input"); input.value = "foo"; expect(getVisibleText(input)).to.eq("foo"); }); it("should sync an updated content value to its formdata", async () => { const page = render(
, ); const form = select(page, "form"); const input = select(form, "uwc-input"); await waitFor(() => expect(getFormValues(form)).to.deep.equal({ foo: "bar" }), ); input.setAttribute("value", "baz"); await waitFor(() => expect(getFormValues(form)).to.deep.equal({ foo: "baz" }), ); }); it("should sync an updated idl value to its formdata", async () => { const page = render(
, ); const form = select(page, "form"); const input = select(form, "uwc-input"); await waitFor(() => expect(getFormValues(form)).to.deep.eq({ foo: "bar" })); input.value = "baz"; await waitFor(() => expect(getFormValues(form)).to.deep.eq({ foo: "baz" })); }); it("should open the popover if focused", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const popover = select(page, "dialog"); expect(popover.checkVisibility()).to.eq(false); await user.click(input); expect(popover.checkVisibility()).to.eq(true); }); it("the popover should not close if clicked inside without selecting a ValueElement", async () => { const page = render(
Something to select that isn't a ValueElement
, ); const input = select(page, "uwc-input"); const popover = select(page, "dialog"); const nonValueElement = select(page, "div"); expect(popover.checkVisibility()).to.eq(false); await user.click(input); expect(popover.checkVisibility()).to.eq(true, "popover never opened"); await user.click(nonValueElement); expect(popover.checkVisibility()).to.eq( true, "popover was closed unexpectedly", ); }); it("the popover should close on selection if not [multiple]", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const popover = select(page, "dialog"); const firstOption = select(page, "li"); await user.click(input); expect(popover.checkVisibility()).to.eq(true); await user.click(firstOption); expect(popover.checkVisibility()).to.eq(false); }); it("should clear aria-current from the selected suggestion", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); await user.click(input); await user.click(firstOption); expect(firstOption).to.not.have.attribute("aria-current", "true"); expect(select(page, "ul[part=values] [data-value]")).to.not.have.attribute( "aria-current", "true", ); }); it("slotting the [data-value] attribute should fall back to the retrieved ValueElement.content", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); await user.click(input); await user.click(firstOption); expect(select(page, "ul[part=values] div.pill")).to.have.attribute( "data-value", "A", ); }); it("should reflect selected values in ul[part=values]", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); await user.click(input); await user.click(firstOption); expect(input.querySelector("ul[part=values] li")).to.exist.and.to.have.text( "A", ); }); it("should add ValueElements to the selected list if their [aria-selected] changes", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); firstOption.setAttribute("aria-selected", "true"); await waitFor(() => expect(input.selected).to.contain("A")); }); it("should not close if selecting something in the popover when [multiple] true", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const popover = select(page, "dialog"); const firstOption = select(page, "li"); await user.click(input); expect(popover.checkVisibility()).to.eq(true); await user.click(firstOption); expect(popover.checkVisibility()).to.eq(true); }); it("should be possible to select anything in the popover that matches :where(li, [data-value])", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); await user.click(input); await user.click(firstOption); expect(input.selected).to.deep.eq(["A"]); }); it("should add the value from the nearest click target that matches ':is(li, [data-value])'", async () => { const page = render(
  • List Element
  • Wrapped
, ); const input = select(page, "uwc-input"); await user.click(input); await user.click(select(input, "li")); expect(input.selected).to.deep.eq(["List Element"]); await user.click(input); await user.click(select(input, "span")); expect(input.selected).to.deep.eq(["wrapped"]); }); it("should replace the templates slots with the respective attributes", async () => { const page = render(
  • first
  • second
, ); await user.click(select(page, "uwc-input")); await user.click(select(page, "dialog ul li")); expect(select(page, "ul:not(template) li").textContent).to.eq("first"); }); it("should be possible to provide an initially selected value", () => { const page = render(
  • some value
, ); const input = select(page, "uwc-input"); expect(getVisibleText(input)).to.eq("some value"); expect(Array.from(input.selected)).to.deep.eq(["SOME_VALUE"]); }); it("should only include the selected value if not multiple", async () => { const page = render(
  • A
  • B
  • C
, ); const form = select(page, "form"); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); await user.click(input); await user.click(firstOption); expect(getVisibleText(input)).to.eq("A"); expect(input.selected).to.deep.eq(["a"]); expect(getFormValues(form)).to.deep.eq({ foo: "a" }); }); it("should have the typed text as value if typing after a selection and not [multiple]", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); const firstOption = select(page, "li"); await user.click(input); await user.click(firstOption); await user.type(input, "foo"); expect(getVisibleText(input)).to.eq("foo"); expect(input.value).to.eq("foo"); expect(input.selected).to.deep.eq([]); }); it("should have no .selected if :not([multiple]) and .value is not empty", () => { const page = render(
    • A
, ); expect(getVisibleText(getInput(page))).to.eq("foo"); expect(getInput(page)).to.deep.include({ value: "foo", selected: [], }); }); it("should not be possible to change the value through interaction when [readonly]", async () => { const page = render(
  • Foo
, ); const input = select(page, "uwc-input"); await user.type(input, "foo"); await user.click(input); await user.click(select(input, "li")); expect(getVisibleText(input)).to.eq(""); expect(input.value).to.eq(""); expect(input.selected).to.deep.eq([]); }); it("should be possible to clear the input", async () => { const page = render(
, ); const form = select(page, "form"); const input = select(page, "uwc-input"); expect(getFormValues(form)).to.deep.eq({ foo: "foo" }); expect(getVisibleText(input)).to.eq("foo"); expect(input.value).to.eq("foo"); expect(input.selected).to.deep.eq([]); input.clear(); expect(getFormValues(form)).to.deep.eq({ foo: "" }); expect(getVisibleText(input)).to.eq(""); expect(input.value).to.eq(""); expect(input.selected).to.deep.eq([]); }); it("should clear all values but preserve the template", async () => { const page = render(
  • first
  • second
  • first
  • second
, ); expect(select(page, "uwc-input").selected).to.deep.eq(["first", "second"]); select(page, "uwc-input").clear(); expect(select(page, "uwc-input").selected).to.deep.eq([]); expect(() => select(page, "template")).not.to.throw(); }); it("should add aria-selected to pre-selected suggestions", () => { const page = render(
  • A
  • A
, ); const suggestion = select(page, "dialog li"); expect(suggestion.getAttribute("aria-selected")).to.eq("true"); }); it("should add aria-selected to the suggestion whose value equals the given value", () => { const page = render(
  • A
, ); const suggestion = select(page, "dialog li"); expect(suggestion.getAttribute("aria-selected")).to.eq("true"); }); it("should remove aria-selected from de-selected suggestions", async () => { const page = render(
  • A
  • A
, ); const input = select(page, "uwc-input"); const suggestion = select(page, "dialog li"); await user.click(input); await user.click(suggestion); expect(suggestion.getAttribute("aria-selected")).to.eq("false"); }); it.todo("should set aria-current on the closest match", async () => { const page = render(
  • A
, ); const input = select(page, "uwc-input"); const suggestion = select(page, "dialog li"); await user.type(input, "A"); expect(suggestion.getAttribute("aria-current")).to.eq("true"); await user.type(input, "{Backspace}"); expect(suggestion.getAttribute("aria-selected")).to.eq("false"); }); it("clicking next to it should not focus it", async () => { const page = render(
Click me
, ); await user.click(select(page, "span")); expect(select(page, "uwc-input")).not.to.match(":focus-within"); }); it("should focus when wrapped label is clicked", async () => { const page = render( , ); await user.click(select(page, "label")); expect(select(page, "uwc-input")).to.match(":focus-within"); }); it("placeholder should not be visible when selected values exist", () => { const page = render(
  • A
, ); expect(select(page, "[part=placeholder]")).to.not.be.visible; }); it("should be possible to remove selected values with {Backspace}", async () => { const page = render(
  • foo
, ); const input = select(page, "uwc-input"); expect(input.selected).to.deep.eq(["foo"], "asserting test setup"); await user.type(input, "{Backspace}"); expect(input.selected).to.deep.eq([]); }); it.todo("should be possible to add values with {Enter} if [multiple]"); it("should not be possible to edit a readonly root element", async () => { const page = render(
, ); const input = select(page, "uwc-input"); await user.type(input, "foo"); expect(input.value).to.eq(""); }); it("should reset itself when the closest form is reset", async () => { const page = render(
, ); expect(getFormValues(page)).to.deep.eq({ foo: "foo", }); await user.type(select(page, "uwc-input [contenteditable]"), "bar"); expect(getFormValues(page)).to.deep.eq({ foo: "foobar", }); await user.click(select(page, "button[type=reset]")); expect(getFormValues(page)).to.deep.eq({ foo: "foo", }); }); it("should not reset itself if it has no name as it's not participating in the form", async () => { const page = render(
, ); await user.click(select(page, "uwc-input")); await user.keyboard("foobar"); expect(getInput(page)).to.include({ value: "foobar", }); await user.click(select(page, "button[type=reset]")); expect(getInput(page)).to.include({ value: "foobar" }); }); it("should be possible to omit the root elements value with [hidden]", () => { const page = render(
, ); expect(getFormValues(select(page, "form"))).to.deep.eq({}); }); it("should not be possible to type when [disabled]", async () => { const page = render(); const input = select(page, "uwc-input"); const content = select(input, "[contenteditable]"); await expect(user.type(content, "foo")).to.eventually.be.rejected; }); it("should be possible to hide only the textcontent by specifying [hidden] on [part=root]", () => { const page = render( , ); expect( select(page, "[part=placeholder]").checkVisibility({ opacityProperty: true, }), ).to.be.true; expect( select(page, "[contenteditable]").checkVisibility({ opacityProperty: true, }), ).to.be.false; }); it("should be possible to jump to a suggestions when [part=root][hidden] by typing", async () => { const page = render(
  • A
  • B
  • C
, ); const input = select(page, "uwc-input"); await user.type(input, "B"); expect(select(input, "li:nth-child(2)")).to.have.attribute( "aria-current", "true", ); }); it("should clear the typed value after some time when [part=root][hidden]", async () => { const page = render( , ); await user.type(select(page, "uwc-input"), "foo"); expect(select(page, "[contenteditable]")).to.have.text("foo"); await waitFor(() => { expect(select(page, "[contenteditable]")).to.have.text(""); }); }); it("should not clear selected values when typing in [part=root][hidden]", async () => { const page = render(
  • A
  • B
  • C
, ); await user.click(select(page, "uwc-input")); await user.click(select(page, "li")); expect(select(page, "uwc-input").selected).to.deep.eq(["A"]); await user.type(select(page, "uwc-input"), "abc"); expect(select(page, "uwc-input").selected).to.deep.eq(["A"]); }); it("should be possible to select suggestions with arrow keys", async () => { const page = render(
  • A
  • B
  • C
, ); await user.click(getInput(page)); await user.keyboard("{ArrowDown}"); await user.keyboard("{ArrowDown}"); expect(getSuggestion(page, 2)).to.have.attribute("aria-current", "true"); await user.keyboard("{ArrowUp}"); expect(getSuggestion(page, 1)).to.have.attribute("aria-current", "true"); await user.keyboard("{ArrowUp}"); expect(getCurrentSuggestion(page)).to.be.null; await user.keyboard("{ArrowUp}"); expect(getSuggestion(page, 3)).to.have.attribute("aria-current", "true"); }); it("should be possible to confirm a selected suggestion with {Enter}", async () => { const page = render(
  • A
  • B
  • C
, ); await user.click(getInput(page)); await user.keyboard("{ArrowDown}"); await user.keyboard("{Enter}"); expect(getInput(page).selected).to.deep.eq(["A"]); expect(getCurrentSuggestion(page)).to.be.null; }); it.todo( "should be possible to go to first element with {Home} and last with {End}", ); it("should not submit form through {Enter} when currently selecting a suggestion", async () => { const page = render(
  • A
  • B
  • C
{" "}
, ); await user.click(getInput(page)); await user.keyboard("{ArrowDown}"); const waitForSubmit = waitForEvent(select(page, "form"), "submit", { timeout: 10, }); await user.keyboard("{Enter}"); await expect(waitForSubmit).to.eventually.be.rejected; }); it("should add value elements in the dialog that have [aria-selected=true] when upgrading", () => { const page = render(
  • Foo
  • Bar
  • Bar
, ); expect(getInput(page).selected).to.deep.eq(["Foo", "Bar"]); }); it("should add only the first value element with [aria-select=true] if not [multiple]", () => { const page = render(
  • Foo
  • Bar
  • Bar
, ); expect(getInput(page).selected).to.deep.eq(["Foo"]); }); it("should not add the text areas value to the form if has [hidden]", () => { const { form } = setup({ input: { children: }, }); expect(getFormValues(form)).to.deep.eq({}); }); it("should not add the .value to the form if [omitValue=form] is set", () => { const { form } = setup({ input: { omitvalue: "form", value: "foo", }, }); expect(getFormValues(form)).to.deep.eq({}); }); it("should not add the .value to the form if [omitValue] is set", () => { const { form } = setup({ input: { omitvalue: true, value: "foo", }, }); expect(getFormValues(form)).to.deep.eq({}); }); it("should add the .value to the form if [omitValue] changes accordingly", () => { const { form, input } = setup({ input: { omitvalue: "form", value: "foo", }, }); input.omitvalue = undefined; expect(getFormValues(form)).to.deep.eq({ input: "foo" }); }); describe("custom input area", () => { it("should be possible to provide a root element in the first place", async () => { const page = render(
, ); expect(selectAll(page, "div[part=root]").length).to.eq(1); }); it("should be possible to place stuff before and after the root element", async () => { const page = render(