@joist/element
Version:
Intelligently apply styles to WebComponents
244 lines (185 loc) • 5.03 kB
text/typescript
import { expect, assert } from "chai";
import { effect, observe } from "@joist/observable";
import { attr } from "./attr.js";
import { element } from "./element.js";
it("should read and parse the correct values", () => {
class MyElement extends HTMLElement {
accessor value1 = 100; // no attribute
accessor value2 = 0; // number
accessor value3 = false; // boolean
accessor value4 = "hello"; // string
}
const container = document.createElement("div");
container.innerHTML = /*html*/ `
<attr-test-1 value2="2" value3 value4="world"></attr-test-1>
`;
document.body.append(container);
const el = document.querySelector("attr-test-1") as MyElement;
expect(el.value1).to.equal(100);
expect(el.value2).to.equal(2);
expect(el.value3).to.equal(true);
expect(el.value4).to.equal("world");
container.remove();
});
it("should not write falsy props to attributes", async () => {
class MyElement extends HTMLElement {
accessor value1 = undefined;
accessor value2 = null;
accessor value3 = "";
}
const el = new MyElement();
expect(el.hasAttribute("value1")).to.be.false;
expect(el.hasAttribute("value2")).to.be.false;
expect(el.hasAttribute("value3")).to.be.false;
});
it("should update attributes when props are changed", async () => {
class MyElement extends HTMLElement {
accessor value1 = "hello"; // no attribute
accessor value2 = 0; // number
accessor value3 = true; // boolean
accessor value4 = false; // boolean
}
const el = new MyElement();
el.value1 = "world";
el.value2 = 100;
el.value3 = false;
el.value4 = true;
expect(el.getAttribute("value1")).to.equal("world");
expect(el.getAttribute("value2")).to.equal("100");
expect(el.hasAttribute("value3")).to.be.false;
expect(el.hasAttribute("value4")).to.be.true;
});
it("should normalize attribute names", async () => {
const value2 = "Value 2";
const value3 = Symbol("Value from SYMBOL");
class MyElement extends HTMLElement {
accessor Value1 = "hello";
accessor [value2] = 0;
accessor [value3] = true;
}
const el = new MyElement();
document.body.append(el);
expect([...el.attributes].map((attr) => attr.name)).to.deep.equal([
"value1",
"value-2",
"value-from-symbol",
]);
el.remove();
});
it("should throw an error for symbols with no description", async () => {
expect(() => {
const value = Symbol();
class MyElement extends HTMLElement {
accessor [value] = true;
}
new MyElement();
}).to.throw("Cannot handle Symbol property without description");
});
it("non reflective attributes should still read new attribute values", async () => {
class MyElement extends HTMLElement {
accessor value = "foo";
}
const el = new MyElement();
el.setAttribute("value", "bar");
expect(el.value).to.equal("bar");
});
it("should allow a manually defined attribute name", async () => {
class MyElement extends HTMLElement {
accessor value = "";
}
const el = new MyElement();
el.setAttribute("aria-label", "TEST");
document.body.append(el);
expect(el.value).to.equal("TEST");
el.remove();
});
it("should update property when attribute changes", async () => {
class MyElement extends HTMLElement {
accessor value = "foo";
accessor count = 0;
accessor enabled = false;
}
const el = new MyElement();
document.body.append(el);
// Test string property
el.setAttribute("value", "bar");
expect(el.value).to.equal("bar");
// Test number property
el.setAttribute("count", "42");
expect(el.count).to.equal(42);
// Test boolean property
el.setAttribute("enabled", "");
expect(el.enabled).to.equal(true);
el.removeAttribute("enabled");
expect(el.enabled).to.equal(false);
el.remove();
});
it("setters should be called when attributes change", async () => {
let callCount = 0;
class MyElement extends HTMLElement {
accessor value = "foo";
onValueChange() {
callCount++;
}
}
const el = new MyElement();
document.body.append(el);
el.setAttribute("value", "bar");
// needs to wait for the mutation observer to run
await Promise.resolve();
await Promise.resolve();
assert.equal(callCount, 1);
el.remove();
});