@zeix/ui-element
Version:
UIElement - minimal reactive framework based on Web Components
244 lines (218 loc) • 7.83 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>UI Functions Tests</title>
</head>
<body>
<div class="test-element">Content</div>
<script type="module">
import { runTests } from "@web/test-runner-mocha";
import { assert } from "@esm-bundle/chai";
import { selection } from "../index.dev.js";
const microtask = async () => new Promise(queueMicrotask);
const normalizeText = (text) => text.replace(/\s+/g, " ").trim();
runTests(() => {
describe("selection()", () => {
it("should create a signal returning an empty array for no matching elements", () => {
const signal = selection(document, ".nonexistent");
assert.deepEqual(signal.get(), []);
});
it("should return an array of elements matching the selector", () => {
const signal = selection(document, ".test-element");
const elements = Array.from(
document.querySelectorAll(".test-element"),
);
assert.deepEqual(signal.get(), elements);
});
it("should update the signal when elements are added or removed", () => {
const signal = selection(document, ".test-element");
const newElement = document.createElement("div");
newElement.classList.add("test-element");
document.body.appendChild(newElement);
let elements = Array.from(
document.querySelectorAll(".test-element"),
);
assert.deepEqual(signal.get(), elements);
newElement.remove();
elements = Array.from(
document.querySelectorAll(".test-element"),
);
assert.deepEqual(signal.get(), elements);
});
it("should update the signal when matching class is added or removed", () => {
const signal = selection(
document.body,
".test-element",
);
const newElement = document.createElement("div");
document.body.appendChild(newElement);
let elements = Array.from(
document.querySelectorAll(".test-element"),
);
assert.deepEqual(signal.get(), elements);
newElement.classList.add("test-element");
elements = Array.from(
document.querySelectorAll(".test-element"),
);
assert.deepEqual(signal.get(), elements);
newElement.classList.remove("test-element");
elements = Array.from(
document.querySelectorAll(".test-element"),
);
assert.deepEqual(signal.get(), elements);
});
it("should update the signal when matching id is added or removed", () => {
const signal = selection(document, "#test-element");
const newElement = document.createElement("div");
document.body.appendChild(newElement);
let elements = Array.from(
document.querySelectorAll("#test-element"),
);
assert.deepEqual(signal.get(), elements);
newElement.id = "test-element";
elements = Array.from(
document.querySelectorAll("#test-element"),
);
assert.deepEqual(signal.get(), elements);
newElement.removeAttribute("id");
elements = Array.from(
document.querySelectorAll("#test-element"),
);
assert.deepEqual(signal.get(), elements);
});
it("should create a computed signal watching the element selection with .map()", () => {
const signal = selection(document, ".test-element");
const contents = signal.map((elements) =>
elements.map((element) => element.textContent),
);
assert.deepEqual(contents.get(), ["Content"]);
});
it("should update the computed signal watching the element selection when elements are added or removed", async () => {
const signal = selection(document, ".test-element");
const contents = signal.map((elements) =>
elements.map((element) => element.textContent),
);
assert.deepEqual(contents.get(), ["Content"]);
const newElement = document.createElement("div");
newElement.textContent = "New Content";
newElement.classList.add("test-element");
document.body.appendChild(newElement);
await microtask();
assert.deepEqual(contents.get(), [
"Content",
"New Content",
]);
newElement.remove();
await microtask();
assert.deepEqual(contents.get(), ["Content"]);
});
it("should create an effect watching the element selection with .tap()", () => {
const signal = selection(document, ".test-element");
signal.tap({
ok: (elements) =>
elements
.filter((element) => !element.hidden)
.map((element) => {
element.hidden = true;
}),
});
const expected = Array.from(
document.querySelectorAll(".test-element"),
).map((element) => element.hidden);
assert.deepEqual(expected, [true]);
document.querySelector(".test-element").hidden = false;
});
it("should apply the effect to an updated array of elements when elements are added or removed", async () => {
const signal = selection(document, ".test-element");
signal.tap({
ok: (elements) =>
elements
.filter((element) => !element.hidden)
.map((element) => {
element.hidden = true;
}),
});
const newElement = document.createElement("div");
newElement.textContent = "New Content";
newElement.classList.add("test-element");
document.body.appendChild(newElement);
await microtask();
let expected = Array.from(
document.querySelectorAll(".test-element"),
).map((element) => element.hidden);
assert.deepEqual(expected, [true, true]);
document
.querySelectorAll(".test-element")
.forEach((element) => {
element.hidden = false;
});
newElement.remove();
await microtask();
expected = Array.from(
document.querySelectorAll(".test-element"),
).map((element) => element.hidden);
assert.deepEqual(expected, [true]);
document
.querySelectorAll(".test-element")
.forEach((element) => {
element.hidden = false;
});
});
it("should detect circular mutations and throw CircularMutationError", () => {
// Create a container div
const container = document.createElement("div");
document.body.appendChild(container);
try {
// Create a selection signal watching for .circular-test elements
const signal = selection(
container,
".circular-test",
);
// Create a tracked count to ensure our effect runs
let effectRanCount = 0;
// Set up an effect that creates a circular dependency
// Each time it runs, it adds another element with the class
// that the selection is watching
const cleanup = signal.tap({
ok: (elements) => {
effectRanCount++;
elements.forEach((element) => {
const newElement =
document.createElement("div");
newElement.classList.add(
"circular-test",
);
newElement.textContent = `Element ${effectRanCount}`;
element.appendChild(newElement);
});
},
err: (error) => {
assert.equal(
error.name,
"CircularMutationError",
);
assert.include(
e.message,
"Circular mutation in element selection detected",
);
},
});
// If we reach here with no error and effectRanCount > 3,
// the circularity wasn't detected
if (effectRanCount > 3) {
cleanup();
assert.fail(
`Circular mutation not detected after ${effectRanCount} iterations`,
);
}
} finally {
// Clean up
container.remove();
}
});
});
});
</script>
</body>
</html>