import { installPointerEvent } from "@kobalte/tests";
import { render } from "@solidjs/testing-library";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import * as TextField from ".";
describe("TextField", () => {
installPointerEvent();
it("can have a default value", async () => {
const onChangeSpy = vi.fn();
const { getByRole } = render(() => (
Favorite Pet
));
const input = getByRole("textbox") as HTMLInputElement;
expect(onChangeSpy).not.toHaveBeenCalled();
expect(input.value).toBe("cat");
await userEvent.type(input, "s");
expect(onChangeSpy).toHaveBeenCalledWith("cats");
expect(input.value).toBe("cats");
});
it("value can be controlled", async () => {
const onChangeSpy = vi.fn();
const { getByRole } = render(() => (
Favorite Pet
));
const input = getByRole("textbox") as HTMLInputElement;
expect(onChangeSpy).not.toHaveBeenCalled();
expect(input.value).toBe("cat");
await userEvent.type(input, "s");
expect(onChangeSpy).toHaveBeenCalledWith("cats");
// "cat" because `value` is controlled.
expect(input.value).toBe("cat");
});
it("name can be controlled", async () => {
const { getByRole } = render(() => (
Favorite Pet
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("name", "favorite-pet");
});
it("supports visible label", async () => {
const { getByRole, getByText } = render(() => (
Favorite Pet
));
const input = getByRole("textbox") as HTMLInputElement;
const label = getByText("Favorite Pet");
expect(input).toHaveAttribute("aria-labelledby", label.id);
expect(label).toBeInstanceOf(HTMLLabelElement);
expect(label).toHaveAttribute("for", input.id);
});
it("supports 'aria-labelledby'", async () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("aria-labelledby", "foo");
});
it("should combine 'aria-labelledby' if visible label is also provided", async () => {
const { getByRole, getByText } = render(() => (
Favorite Pet
));
const input = getByRole("textbox") as HTMLInputElement;
const label = getByText("Favorite Pet");
expect(input).toHaveAttribute("aria-labelledby", `foo ${label.id}`);
});
it("supports 'aria-label'", async () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("aria-label", "My Favorite Pet");
});
it("should combine 'aria-labelledby' if visible label and 'aria-label' is also provided", async () => {
const { getByRole, getByText } = render(() => (
Favorite Pet
));
const input = getByRole("textbox") as HTMLInputElement;
const label = getByText("Favorite Pet");
expect(input).toHaveAttribute(
"aria-labelledby",
`foo ${label.id} ${input.id}`,
);
});
it("supports visible description", async () => {
const { getByRole, getByText } = render(() => (
Description
));
const input = getByRole("textbox") as HTMLInputElement;
const description = getByText("Description");
expect(description.id).toBeDefined();
expect(input.id).toBeDefined();
expect(input).toHaveAttribute("aria-describedby", description.id);
// check that generated ids are unique
expect(description.id).not.toBe(input.id);
});
it("supports 'aria-describedby'", async () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("aria-describedby", "foo");
});
it("should combine 'aria-describedby' if visible description", async () => {
const { getByRole, getByText } = render(() => (
Description
));
const input = getByRole("textbox") as HTMLInputElement;
const description = getByText("Description");
expect(input).toHaveAttribute("aria-describedby", `${description.id} foo`);
});
it("supports visible error message when invalid", async () => {
const { getByRole, getByText } = render(() => (
ErrorMessage
));
const input = getByRole("textbox") as HTMLInputElement;
const errorMessage = getByText("ErrorMessage");
expect(errorMessage.id).toBeDefined();
expect(input.id).toBeDefined();
expect(input).toHaveAttribute("aria-describedby", errorMessage.id);
// check that generated ids are unique
expect(errorMessage.id).not.toBe(input.id);
});
it("should not be described by error message when not invalid", async () => {
const { getByRole } = render(() => (
ErrorMessage
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).not.toHaveAttribute("aria-describedby");
});
it("should combine 'aria-describedby' if visible error message when invalid", () => {
const { getByRole, getByText } = render(() => (
ErrorMessage
));
const input = getByRole("textbox") as HTMLInputElement;
const errorMessage = getByText("ErrorMessage");
expect(input).toHaveAttribute("aria-describedby", `${errorMessage.id} foo`);
});
it("should combine 'aria-describedby' if visible description and error message when invalid", () => {
const { getByRole, getByText } = render(() => (
Description
ErrorMessage
));
const input = getByRole("textbox") as HTMLInputElement;
const description = getByText("Description");
const errorMessage = getByText("ErrorMessage");
expect(input).toHaveAttribute(
"aria-describedby",
`${description.id} ${errorMessage.id} foo`,
);
});
it("should not have form control 'data-*' attributes by default", () => {
const { getByRole, getByTestId } = render(() => (
));
const textField = getByTestId("textfield");
const input = getByRole("textbox") as HTMLInputElement;
for (const el of [textField, input]) {
expect(el).not.toHaveAttribute("data-valid");
expect(el).not.toHaveAttribute("data-invalid");
expect(el).not.toHaveAttribute("data-required");
expect(el).not.toHaveAttribute("data-disabled");
expect(el).not.toHaveAttribute("data-readonly");
}
});
it("should have 'data-valid' attribute when valid", () => {
const { getByRole, getByTestId } = render(() => (
));
const textField = getByTestId("textfield");
const input = getByRole("textbox") as HTMLInputElement;
for (const el of [textField, input]) {
expect(el).toHaveAttribute("data-valid");
}
});
it("should have 'data-invalid' attribute when invalid", () => {
const { getByRole, getByTestId } = render(() => (
));
const textField = getByTestId("textfield");
const input = getByRole("textbox") as HTMLInputElement;
for (const el of [textField, input]) {
expect(el).toHaveAttribute("data-invalid");
}
});
it("should have 'data-required' attribute when required", () => {
const { getByRole, getByTestId } = render(() => (
));
const textField = getByTestId("textfield");
const input = getByRole("textbox") as HTMLInputElement;
for (const el of [textField, input]) {
expect(el).toHaveAttribute("data-required");
}
});
it("should have 'data-disabled' attribute when disabled", () => {
const { getByRole, getByTestId } = render(() => (
));
const textField = getByTestId("textfield");
const input = getByRole("textbox") as HTMLInputElement;
for (const el of [textField, input]) {
expect(el).toHaveAttribute("data-disabled");
}
});
it("should have 'data-readonly' attribute when readonly", () => {
const { getByRole, getByTestId } = render(() => (
));
const textField = getByTestId("textfield");
const input = getByRole("textbox") as HTMLInputElement;
for (const el of [textField, input]) {
expect(el).toHaveAttribute("data-readonly");
}
});
it("sets 'aria-invalid' on input when 'validationState=invalid'", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("aria-invalid", "true");
});
it("input should not have 'required', 'disabled' or 'readonly' attributes by default", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).not.toHaveAttribute("required");
expect(input).not.toHaveAttribute("disabled");
expect(input).not.toHaveAttribute("readonly");
});
it("sets 'required' and 'aria-required' on input when 'isRequired' is true", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("required");
expect(input).toHaveAttribute("aria-required", "true");
});
it("sets 'disabled' and 'aria-disabled' on input when 'isDisabled' is true", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("disabled");
expect(input).toHaveAttribute("aria-disabled", "true");
});
it("sets 'readonly' and 'aria-readonly' on input when 'isReadOnly' is true", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("readonly");
expect(input).toHaveAttribute("aria-readonly", "true");
});
it("should have 'aria-multiline' set to false on textarea when 'submitOnEnter' is true", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).toHaveAttribute("aria-multiline", "false");
});
it("should not have 'aria-multiline' on textarea when 'submitOnEnter' is false", () => {
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
expect(input).not.toHaveAttribute("aria-multiline");
});
// Skipped: requestSubmit is not implemented
it.skip("form is submitted when 'submitOnEnter' is true and user presses the enter key", async () => {
const onSubmit = vi.fn();
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
await userEvent.type(input, "abc{enter}");
expect(onSubmit).toHaveBeenCalledTimes(1);
});
it("form is not submitted when 'submitOnEnter' is true and the user presses shift + enter at the same time", async () => {
const onSubmit = vi.fn();
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
await userEvent.type(input, "{Shift>}enter{/Shift}");
expect(onSubmit).not.toHaveBeenCalled();
});
it("form is not submitted when 'submitOnEnter' is not set and user presses the enter key", async () => {
const onSubmit = vi.fn();
const { getByRole } = render(() => (
));
const input = getByRole("textbox") as HTMLInputElement;
await userEvent.type(input, "{enter}");
expect(onSubmit).not.toHaveBeenCalled();
});
});