Packages
noora
0.85.0
0.85.0
0.84.1
0.84.0
0.83.2
0.83.1
0.83.0
0.82.7
0.82.6
0.82.5
0.82.4
0.82.3
0.82.2
0.82.1
0.82.0
0.81.4
0.81.3
0.81.2
0.81.1
0.81.0
0.80.1
0.80.0
0.79.1
0.79.0
0.78.1
0.78.0
0.77.4
0.77.3
0.77.2
0.77.1
0.77.0
0.76.0
0.75.0
0.74.0
0.73.0
0.72.0
0.71.0
0.70.0
0.69.1
0.69.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.2
0.64.1
0.64.0
0.63.2
0.63.1
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.1
0.53.0
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.3
0.44.2
0.44.1
0.44.0
0.43.0
0.42.0
0.41.0
0.40.6
0.40.5
0.40.4
0.40.3
0.40.2
0.40.1
0.40.0
0.39.1
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.1
0.32.0
0.31.0
0.30.0
0.29.2
0.29.1
0.29.0
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.0
0.1.0
0.1.0-rc.2
0.1.0-rc.1
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
A component library for Phoenix LiveView applications
Current section
Files
Jump to
Current section
Files
js/web-components/Button.test.js
// @vitest-environment happy-dom
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { registerNooraButton } from "./Button.js";
beforeAll(() => {
for (const property of [
"ariaDescribedByElements",
"ariaLabelledByElements",
]) {
if (!(property in HTMLElement.prototype)) {
Object.defineProperty(HTMLElement.prototype, property, {
configurable: true,
writable: true,
});
}
}
registerNooraButton();
});
afterEach(() => {
document.body.replaceChildren();
});
describe("NooraButton", () => {
it("renders the shared defaults and label", async () => {
const button = document.createElement("noora-button");
button.label = "Create project";
document.body.append(button);
await button.updateComplete;
const control = button.shadowRoot.querySelector("button");
expect(control.dataset.variant).toBe("primary");
expect(control.dataset.size).toBe("large");
expect(control.textContent.trim()).toBe("Create project");
});
it("projects icons and a text label into the native control", async () => {
const button = document.createElement("noora-button");
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
icon.slot = "icon-left";
button.append(icon, "Previous");
document.body.append(button);
await button.updateComplete;
const control = button.shadowRoot.querySelector("button");
const iconSlot = control.querySelector('slot[name="icon-left"]');
const labelSlot = control.querySelector("span slot");
expect(iconSlot.assignedElements()).toEqual([icon]);
expect(
labelSlot
.assignedNodes()
.map((node) => node.textContent)
.join(""),
).toBe("Previous");
});
it("updates variants, sizes, and disabled state", async () => {
const button = document.createElement("noora-button");
document.body.append(button);
await button.updateComplete;
button.variant = "destructive";
button.size = "small";
button.disabled = true;
await button.updateComplete;
const control = button.shadowRoot.querySelector("button");
expect(control.dataset.variant).toBe("destructive");
expect(control.dataset.size).toBe("small");
expect(control.disabled).toBe(true);
});
it("falls back to the shared contract for unsupported presentation values", async () => {
const button = document.createElement("noora-button");
button.setAttribute("variant", "unsupported");
button.setAttribute("size", "unsupported");
document.body.append(button);
await button.updateComplete;
const control = button.shadowRoot.querySelector("button");
expect(control.dataset.variant).toBe("primary");
expect(control.dataset.size).toBe("large");
});
it("renders links unless the component is disabled", async () => {
const button = document.createElement("noora-button");
button.href = "/projects";
document.body.append(button);
await button.updateComplete;
expect(button.shadowRoot.querySelector("a").getAttribute("href")).toBe(
"/projects",
);
button.disabled = true;
await button.updateComplete;
expect(button.shadowRoot.querySelector("a")).toBeNull();
expect(button.shadowRoot.querySelector("button").disabled).toBe(true);
});
it("forwards programmatic clicks through the native control", async () => {
const button = document.createElement("noora-button");
const listener = vi.fn();
button.addEventListener("click", listener);
document.body.append(button);
await button.updateComplete;
button.click();
expect(listener).toHaveBeenCalledOnce();
expect(listener.mock.calls[0][0].composed).toBe(true);
});
it("associates external accessible labels and descriptions with the native control", async () => {
const label = document.createElement("span");
const description = document.createElement("span");
const button = document.createElement("noora-button");
label.id = "button-label";
description.id = "button-description";
button.setAttribute("aria-labelledby", label.id);
button.setAttribute("aria-describedby", description.id);
document.body.append(label, description, button);
await button.updateComplete;
const control = button.shadowRoot.querySelector("button");
expect(control.ariaLabelledByElements).toEqual([label]);
expect(control.ariaDescribedByElements).toEqual([description]);
});
it("submits a parent form with its name and value", async () => {
const form = document.createElement("form");
const button = document.createElement("noora-button");
const listener = vi.fn((event) => event.preventDefault());
button.name = "intent";
button.value = "create";
const requestSubmit = vi.spyOn(form, "requestSubmit");
form.addEventListener("submit", listener);
form.append(button);
document.body.append(form);
await button.updateComplete;
expect(button.type).toBe("submit");
expect(button.nextElementSibling.form).toBe(form);
button.click();
await Promise.resolve();
expect(requestSubmit).toHaveBeenCalledOnce();
expect(listener).toHaveBeenCalledOnce();
expect(listener.mock.calls[0][0].submitter.name).toBe("intent");
expect(listener.mock.calls[0][0].submitter.value).toBe("create");
});
it("provides a native default submitter for implicit form submission", async () => {
const form = document.createElement("form");
const input = document.createElement("input");
const button = document.createElement("noora-button");
const listener = vi.fn((event) => event.preventDefault());
button.name = "intent";
button.value = "create";
button.formAction = "/projects";
button.formEnctype = "multipart/form-data";
button.formMethod = "post";
button.formNoValidate = true;
button.formTarget = "_blank";
form.addEventListener("submit", listener);
form.append(input, button);
document.body.append(form);
await button.updateComplete;
const submitter = button.nextElementSibling;
expect(submitter).toBeInstanceOf(HTMLButtonElement);
expect(submitter.hidden).toBe(true);
expect(submitter.form).toBe(form);
expect(submitter.name).toBe("intent");
expect(submitter.value).toBe("create");
expect(submitter.getAttribute("formaction")).toBe("/projects");
expect(submitter.getAttribute("formenctype")).toBe("multipart/form-data");
expect(submitter.getAttribute("formmethod")).toBe("post");
expect(submitter.formNoValidate).toBe(true);
expect(submitter.getAttribute("formtarget")).toBe("_blank");
submitter.click();
expect(listener).toHaveBeenCalledOnce();
expect(listener.mock.calls[0][0].submitter).toBe(submitter);
});
it("does not submit when the click is prevented", async () => {
const form = document.createElement("form");
const button = document.createElement("noora-button");
const listener = vi.fn();
button.addEventListener("click", (event) => event.preventDefault());
form.addEventListener("submit", listener);
form.append(button);
document.body.append(form);
await button.updateComplete;
button.click();
await Promise.resolve();
expect(listener).not.toHaveBeenCalled();
});
});