Files
vacuum-wall/tests/test-reactive-dom.js
T

121 lines
3.7 KiB
JavaScript

import { reactive, h, html, render, Router, Link } from '../webui/static/reactive-dom.js';
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`${name}`);
passed++;
} catch (e) {
console.error(`${name}: ${e.message}`);
failed++;
}
}
function assert(cond, msg) {
if (!cond) throw new Error(msg || 'Assertion failed');
}
console.log('Testing reactive-dom.js\n');
// === Reactive ===
test('reactive() returns proxy', () => {
const s = reactive({ x: 1 });
assert(s.x === 1);
});
test('reactive() mutation triggers render callback', () => {
const s = reactive({ x: 1 });
let fired = false;
// We can't easily test the render callback without a DOM, but we can check the proxy works
s.x = 2;
assert(s.x === 2);
});
// === h() ===
test('h() creates element VNode', () => {
const v = h('div', { class: 'foo' });
assert(v.tag === 'div' && v.props.class === 'foo');
});
test('h() flattens children array', () => {
const v = h('div', null, h('span', null, 'hi'));
assert(v.ch.length === 1 && v.ch[0].tag === 'span');
});
test('h() converts strings to text nodes', () => {
const v = h('div', null, 'hello', 42);
assert(v.ch.length === 2 && v.ch[0].tag === '#text' && v.ch[0].text === 'hello');
assert(v.ch[1].text === '42');
});
test('h() drops null/boolean children', () => {
const v = h('div', null, null, undefined, true, false, 'x');
assert(v.ch.length === 1 && v.ch[0].text === 'x');
});
// === html() ===
test('html() parses static element', () => {
const nodes = html`<div>hello</div>`;
assert(nodes[0].tag === 'div' && nodes[0].ch[0].text === 'hello');
});
test('html() interpolates text into element children', () => {
const name = 'World';
const nodes = html`<div>Hello ${name}</div>`;
assert(nodes[0].tag === 'div');
// Should have: text "Hello ", then text "World"
assert(nodes[0].ch[0].text && nodes[0].ch[0].text === 'Hello ');
assert(nodes[0].ch[1].tag === '#text' && nodes[0].ch[1].text === 'World');
});
test('html() interpolates class attribute value', () => {
const cls = 'active';
const nodes = html`<div class="${cls}">x</div>`;
assert(nodes[0].tag === 'div' && nodes[0].props.class === 'active');
});
test('html() interpolates on:click attribute value', () => {
const handler = function click() {};
const nodes = html`<button on:click="${handler}">Go</button>`;
assert(nodes[0].tag === 'button' && typeof nodes[0].props['on:click'] === 'function');
});
test('html() handles multiple interpolations', () => {
const a = 'first', b = 'second';
const nodes = html`<div><span>${a}</span> <span>${b}</span></div>`;
assert(nodes[0].tag === 'div');
assert(nodes[0].ch[0].tag === 'span' && nodes[0].ch[0].ch[0].text === 'first');
});
test('html() interpolates VNode into element children', () => {
const nodes = html`<ul>${html`<li>item</li>`[0]}</ul>`;
assert(nodes[0].tag === 'ul' && nodes[0].ch[0].tag === 'li');
});
// === Router ===
test('Router initializes with current hash', () => {
globalThis.location = { hash: '' };
globalThis.window = { addEventListener: () => {} };
const router = Router({ '/home': () => {} });
assert(router.state.path === '/');
});
// === Link ===
test('Link creates anchor with hash href', () => {
const link = Link({ path: '/dashboard' });
assert(link.tag === 'a' && link.props.href === '#/dashboard');
});
// === DOM functions (basic, no actual DOM) ===
test('createDom produces document.createElement call', () => {
const v = h('div', { class: 'foo' }, 'hi', h('span', null, 'nested'));
// Can't test actual DOM without jsdom, but we can verify the VNode structure
assert(v.tag === 'div' && v.ch.length === 2);
});
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);