const ELEMENT_NODE = 1;
const TEXT_NODE = 3;
const FRAGMENT_NODE = 11;
class FakeNode {
constructor(nodeType, nodeName) {
this.nodeType = nodeType;
this.nodeName = nodeName;
this.childNodes = [];
this.attributes = new Map();
this.className = '';
this.value = '';
this.dataset = {};
this.listeners = new Map();
this.open = false;
}
addEventListener(type, listener) {
const existing = this.listeners.get(type) || [];
existing.push(listener);
this.listeners.set(type, existing);
}
dispatch(type) {
for (const listener of this.listeners.get(type) || []) listener();
}
get tagName() {
return this.nodeType === ELEMENT_NODE ? this.nodeName : undefined;
}
get textContent() {
if (this.nodeType === TEXT_NODE) return this.value;
return this.childNodes.map(child => child.textContent).join('');
}
set textContent(value) {
if (this.nodeType === TEXT_NODE) {
this.value = String(value);
return;
}
this.childNodes = [];
this.append(new FakeNode(TEXT_NODE, '#text'));
this.childNodes[0].value = String(value);
}
append(...nodes) {
for (const node of nodes) this.appendChild(node);
}
appendChild(node) {
if (node.nodeType === FRAGMENT_NODE) {
this.childNodes.push(...node.childNodes);
node.childNodes = [];
return node;
}
this.childNodes.push(node);
return node;
}
setAttribute(name, value) {
this.attributes.set(name, String(value));
}
getAttribute(name) {
return this.attributes.has(name) ? this.attributes.get(name) : null;
}
}
export function installDocument() {
globalThis.document = {
createElement(name) {
return new FakeNode(ELEMENT_NODE, String(name).toUpperCase());
},
createTextNode(value) {
const node = new FakeNode(TEXT_NODE, '#text');
node.value = String(value);
return node;
},
createDocumentFragment() {
return new FakeNode(FRAGMENT_NODE, '#document-fragment');
},
};
}
export function elements(root, tagName) {
const wanted = tagName.toUpperCase();
const found = [];
const walk = node => {
if (node.nodeType === ELEMENT_NODE && node.nodeName === wanted) found.push(node);
for (const child of node.childNodes) walk(child);
};
walk(root);
return found;
}
export function only(root, tagName) {
const found = elements(root, tagName);
if (found.length !== 1) {
throw new Error(`expected exactly one <${tagName}>, found ${found.length}`);
}
return found[0];
}
export function openFold(details) {
details.open = true;
details.dispatch('toggle');
return details;
}
export function check(condition, message) {
if (!condition) throw new Error(message);
}
export function checkEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(
`${message}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
);
}
}