@zeix/ui-element
Version:
UIElement - minimal reactive framework based on Web Components
313 lines (275 loc) • 8.45 kB
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>