mirror of
https://github.com/home-assistant/frontend.git
synced 2026-09-28 16:04:00 +01:00
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
|
import "../../../src/components/input/ha-input";
|
|
import type { HaInput } from "../../../src/components/input/ha-input";
|
|
|
|
class MockResizeObserver {
|
|
observe = vi.fn();
|
|
|
|
unobserve = vi.fn();
|
|
|
|
disconnect = vi.fn();
|
|
}
|
|
|
|
vi.stubGlobal("ResizeObserver", MockResizeObserver);
|
|
|
|
beforeAll(() => {
|
|
// jsdom's ElementInternals lacks the validity API used by wa-input.
|
|
const internalsProto = window.ElementInternals.prototype as any;
|
|
internalsProto.setValidity = vi.fn();
|
|
internalsProto.setFormValue = vi.fn();
|
|
internalsProto.checkValidity = () => true;
|
|
internalsProto.reportValidity = () => true;
|
|
internalsProto.states = new Set();
|
|
Object.defineProperty(internalsProto, "validity", {
|
|
get: () => ({ valid: true }),
|
|
configurable: true,
|
|
});
|
|
});
|
|
|
|
let inputs: HaInput[] = [];
|
|
|
|
const mountInput = async (props: Partial<HaInput> = {}): Promise<HaInput> => {
|
|
const el = document.createElement("ha-input");
|
|
Object.assign(el, props);
|
|
document.body.append(el);
|
|
inputs.push(el);
|
|
await el.updateComplete;
|
|
const waInput = el.shadowRoot!.querySelector("wa-input");
|
|
await waInput?.updateComplete;
|
|
return el;
|
|
};
|
|
|
|
const nativeInput = (el: HaInput): HTMLInputElement => {
|
|
const waInput = el.shadowRoot!.querySelector("wa-input")!;
|
|
return waInput.shadowRoot!.querySelector('[part~="input"]')!;
|
|
};
|
|
|
|
const nativeLabel = (el: HaInput): HTMLLabelElement => {
|
|
const waInput = el.shadowRoot!.querySelector("wa-input")!;
|
|
return waInput.shadowRoot!.querySelector("label")!;
|
|
};
|
|
|
|
afterEach(() => {
|
|
inputs.forEach((el) => el.remove());
|
|
inputs = [];
|
|
});
|
|
|
|
describe("ha-input password-manager autofill", () => {
|
|
// Password managers often write the nested native input without firing
|
|
// input/change, so login used to submit empty credentials (#51620).
|
|
it("picks up a silent native fill on reportValidity", async () => {
|
|
const el = await mountInput({ label: "Username", required: true });
|
|
const received: string[] = [];
|
|
el.addEventListener("input", () => {
|
|
received.push(el.value ?? "");
|
|
});
|
|
|
|
nativeInput(el).value = "admin";
|
|
|
|
expect(el.value).toBeUndefined();
|
|
expect(el.reportValidity()).toBe(true);
|
|
expect(el.value).toBe("admin");
|
|
expect(received).toEqual(["admin"]);
|
|
});
|
|
|
|
it("does not emit when the native value already matches", async () => {
|
|
const el = await mountInput({ label: "Username", value: "admin" });
|
|
const listener = vi.fn();
|
|
el.addEventListener("input", listener);
|
|
|
|
expect(el.syncFromNativeInput()).toBe(false);
|
|
expect(listener).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("ha-input native ids", () => {
|
|
// Unique native ids help password managers tell login fields apart (#51620 /
|
|
// home-assistant/webawesome#52).
|
|
it("remaps the native input id and label for from input-id", async () => {
|
|
const el = await mountInput({
|
|
label: "Username",
|
|
inputId: "username",
|
|
});
|
|
|
|
const waInput = el.shadowRoot!.querySelector("wa-input") as HTMLElement & {
|
|
inputId?: string;
|
|
};
|
|
expect(waInput.inputId).toBe("username");
|
|
expect(nativeInput(el).id).toBe("username");
|
|
expect(nativeLabel(el).htmlFor).toBe("username");
|
|
});
|
|
|
|
it("keeps the default native id when input-id is omitted", async () => {
|
|
const el = await mountInput({ label: "Username" });
|
|
|
|
expect(nativeInput(el).id).toBe("input");
|
|
expect(nativeLabel(el).htmlFor).toBe("input");
|
|
});
|
|
});
|