/** * A minimal in-memory DOM that satisfies the renderer's structural * {@link ElementLike} / {@link DocumentLike} types, for unit tests only. * * It is NOT part of the published build (excluded in `tsconfig.build.json`); it * lets the DOM renderer and the boot layer be exercised on Node with no DOM * library and no `as` cast — the fake is *structurally* the same subset the * browser's real `Document`/`Element` provide. */ import type { DocumentLike, ElementLike } from "./render.ts"; export class FakeElement implements ElementLike { className = ""; textContent: string | null = null; readonly tagName: string; readonly attributes = new Map(); readonly children: FakeElement[] = []; readonly #listeners = new Map void>>(); constructor(tagName: string) { this.tagName = tagName; } setAttribute(name: string, value: string): void { this.attributes.set(name, value); } getAttribute(name: string): string | undefined { return this.attributes.get(name); } appendChild(child: ElementLike): ElementLike { if (child instanceof FakeElement) this.children.push(child); return child; } replaceChildren(): void { this.children.length = 0; } addEventListener(type: string, handler: () => void): void { const list = this.#listeners.get(type) ?? []; list.push(handler); this.#listeners.set(type, list); } /** Fire every listener registered for `type` (test driver for clicks). */ dispatch(type: string): void { for (const handler of this.#listeners.get(type) ?? []) handler(); } /** Depth-first walk of this element and its descendants. */ *walk(): IterableIterator { yield this; for (const child of this.children) yield* child.walk(); } /** Every descendant (and self) whose className contains `cls`. */ byClass(cls: string): FakeElement[] { const out: FakeElement[] = []; for (const node of this.walk()) { if (node.className.split(/\s+/).includes(cls)) out.push(node); } return out; } /** Every descendant (and self) with `data-` equal to `value` (any value if omitted). */ byData(key: string, value?: string): FakeElement[] { const attr = `data-${key}`; const out: FakeElement[] = []; for (const node of this.walk()) { const got = node.attributes.get(attr); if (got !== undefined && (value === undefined || got === value)) out.push(node); } return out; } /** The concatenated text content of this subtree (own text then children). */ text(): string { let out = this.textContent ?? ""; for (const child of this.children) out += child.text(); return out; } } export class FakeDocument implements DocumentLike { createElement(tagName: string): ElementLike { return new FakeElement(tagName); } }