Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
3.5 kB · 100 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101/** * el() has to tell an attribute from a property, and there is nothing to catch * when it does not. * * It used to be one Object.assign, so `"aria-label": "Camo name"` put a * property literally called "aria-label" on the element. No error, no * attribute, and the camo name field reached the accessibility tree as an * unnamed textbox. The same key in the same shape sat in the rename field * beside it. Both looked right in the source and neither did anything. * * So the rule is the split itself: a hyphenated key goes through * setAttribute, everything else is assigned. The second test is the reason * the first one is enough — one el() in the app, not three. * * Run with `npm test`. */import { test } from "node:test";import assert from "node:assert/strict";import { readdir, readFile } from "node:fs/promises";import { join } from "node:path";import { fileURLToPath } from "node:url";
// Enough of an element for el(): a bag of attributes, a list of children, and// ordinary property assignment for everything else. Installed before el() is// called rather than before it is imported, which dom.ts allows because it// touches the document only inside a function.class FakeElement { constructor(tag) { this.tagName = tag.toUpperCase(); this.attributes = {}; this.children = []; } setAttribute(name, value) { this.attributes[name] = value; } append(...nodes) { this.children.push(...nodes); }}
globalThis.document = { createElement: (tag) => new FakeElement(tag) };
const { el } = await import("../src/dom.ts");
test("a hyphenated key becomes an attribute", () => { const input = el("input", { "aria-label": "Camo name", "data-testid": "camo-name", });
assert.equal(input.attributes["aria-label"], "Camo name"); assert.equal(input.attributes["data-testid"], "camo-name"); // The bug: set as a property, it is invisible to everything that reads the // element, and an assertion on the property alone would have passed anyway. assert.equal( Object.hasOwn(input, "aria-label"), false, "aria-label was assigned as a property instead of set as an attribute", );});
test("every other key stays a property", () => { const input = el( "input", { className: "camo-name", placeholder: "Name this camo", maxLength: 64 }, ["child"], );
assert.equal(input.className, "camo-name"); assert.equal(input.placeholder, "Name this camo"); assert.equal(input.maxLength, 64); assert.deepEqual(input.attributes, {}); assert.deepEqual(input.children, ["child"]);});
test("there is one el() in the app", async () => { const src = fileURLToPath(new URL("../src/", import.meta.url));
const files = []; for (const entry of await readdir(src, { recursive: true })) { // Generated by lexgen and gitignored; it has no el() and no reason to be // read on every test run. if (entry.startsWith("lexicons/")) continue; if (entry.endsWith(".ts")) files.push(entry); }
const defining = []; for (const file of files) { const source = await readFile(join(src, file), "utf8"); if (/function el</.test(source)) defining.push(file); }
// camo/editor.ts and camo/collection.ts each carried a private copy, so the // fix above reached neither of them until they were deleted. assert.deepEqual( defining, ["dom.ts"], "el() is defined more than once; a fix to one copy does not reach the others", );});