UNPKG

@zeix/ui-element

Version:

UIElement - minimal reactive framework based on Web Components

313 lines (275 loc) 8.45 kB
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>UI Functions Tests</title> </head> <body> <div id="host"> <div id="test-on-function"></div> <div id="test-on-provider"></div> <div id="test-on-invalid"></div> </div> <child-component id="orphan"> <h1>Hello from Server</h1> <p>Text from Server</p> </child-component> <parent-component id="parent" heading="Hello from Attribute"> <child-component id="child"> <h1>Hello from Server</h1> <p>Text from Server</p> </child-component> <invalid-component id="invalid"></invalid-component> </parent-component> <script type="module"> import { runTests } from "@web/test-runner-mocha"; import { assert } from "@esm-bundle/chai"; import { first, on, emit, pass, component, asString, setText, RESET, } from "../index.dev.js"; const animationFrame = async () => new Promise(requestAnimationFrame); const normalizeText = (text) => text.replace(/\s+/g, " ").trim(); component( "child-component", { heading: asString(RESET), text: asString(RESET), }, () => [ first("h1", setText("heading")), first("p", setText("text")), ], ); component( "parent-component", { heading: asString(RESET), }, (el) => [ first( "child-component", pass({ heading: "heading", text: () => el.heading.toUpperCase(), }), ), ], ); class InvalidComponent extends HTMLElement { constructor() { super(); throw new Error("Invalid component"); } } customElements.define("invalid-component", InvalidComponent); runTests(() => { describe("on()", () => { it("should attach and remove an event listener", async () => { const div = document.getElementById("test-on-function"); let called = false; const off = on("click", () => { called = true; })({}, div); div.click(); const wasCalled = called; off(); // Remove the event listener called = false; div.click(); assert.equal(wasCalled, true); assert.equal(called, false); }); it("should throw TypeError for invalid handler", async () => { const div = document.getElementById("test-on-invalid"); assert.throws( () => on("click", {})({}, div), TypeError, ); }); it("should use host as default target", async () => { const host = document.getElementById("host"); let called = false; const off = on("click", () => { called = true; })(host); host.click(); const wasCalled = called; off(); // Remove the event listener called = false; host.click(); assert.equal(wasCalled, true); assert.equal(called, false); }); }); describe("emit()", () => { it("should emit an event", async () => { const div = document.getElementById("test-on-function"); let called = false; const handler = () => { called = true; }; div.addEventListener("custom-event", handler); emit("custom-event")(div); assert.equal(called, true); div.removeEventListener("custom-event", handler); }); it("should emit an event with detail", async () => { const div = document.getElementById("test-on-function"); let detailReceived = null; const handler = (event) => { detailReceived = event.detail; }; div.addEventListener("custom-event", handler); emit("custom-event", { foo: "bar" })(div); assert.deepEqual(detailReceived, { foo: "bar" }); div.removeEventListener("custom-event", handler); }); }); describe("Orphan child component", function () { it("should do nothing at all", async function () { const orphanComponent = document.getElementById("orphan"); await animationFrame(); const headingContent = normalizeText( orphanComponent.querySelector("h1").textContent, ); const textContent = normalizeText( orphanComponent.querySelector("p").textContent, ); assert.equal( headingContent, "Hello from Server", "Should not change server-side rendered heading", ); assert.equal( textContent, "Text from Server", "Should not change server-side rendered text", ); }); }); describe("Child component", function () { it("should receive state from attribute of parent component", async function () { const childComponent = document.getElementById("child"); await customElements.whenDefined( childComponent.localName, ); const headingContent = childComponent.querySelector("h1").textContent; assert.equal( headingContent, "Hello from Attribute", "Should have initial heading from attribute of parent component", ); }); it("should receive derived state from attribute of parent component", async function () { const childComponent = document.getElementById("child"); await customElements.whenDefined( childComponent.localName, ); const textContent = normalizeText( childComponent.querySelector("p").textContent, ); assert.equal( textContent, "Hello from Attribute".toUpperCase(), "Should have initial text derived from attribute of parent component", ); }); it("should receive passed and derived states from changed attribute of parent component", async function () { const parentComponent = document.getElementById("parent"); const childComponent = document.getElementById("child"); parentComponent.setAttribute( "heading", "Hello from Changed Attribute", ); await animationFrame(); const headingContent = normalizeText( childComponent.querySelector("h1").textContent, ); const textContent = normalizeText( childComponent.querySelector("p").textContent, ); assert.equal( headingContent, "Hello from Changed Attribute", "Should have changed heading from attribute of parent component", ); assert.equal( textContent, "Hello from Changed Attribute".toUpperCase(), "Should have changed text derived from attribute of parent component", ); }); it("should change heading if inherited state is set", async function () { const parentComponent = document.getElementById("parent"); const childComponent = document.getElementById("child"); parentComponent.heading = "Hello from State on Parent"; await animationFrame(); const headingContent = childComponent.querySelector("h1").textContent; assert.equal( headingContent, "Hello from State on Parent", "Should have changed heading from state of parent component", ); }); it("should throw TypeError if target is not a custom element", async function () { const parentComponent = document.getElementById("parent"); const invalidTarget = document.createElement("div"); assert.throws( () => { pass({ heading: "heading" })( parentComponent, invalidTarget, ); }, TypeError, "Target element must be a custom element", ); }); it("should throw TypeError if signals are not an object or provider", async function () { const parentComponent = document.getElementById("parent"); const childComponent = document.getElementById("child"); assert.throws( () => { pass(null)(parentComponent, childComponent); }, TypeError, "Passed signals must be an object or a provider function", ); }); it("should throw TypeError if signals are not a function", async function () { const parentComponent = document.getElementById("parent"); const invalidComponent = document.getElementById("invalid"); try { pass({ heading: "heading" })( parentComponent, invalidComponent, ); } catch (error) { assert.equal( error instanceof TypeError, true, "Should throw TypeError", ); } }); }); }); </script> </body> </html>