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`
hello
`; assert(nodes[0].tag === 'div' && nodes[0].ch[0].text === 'hello'); }); test('html() interpolates text into element children', () => { const name = 'World'; const nodes = html`
Hello ${name}
`; 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`
x
`; assert(nodes[0].tag === 'div' && nodes[0].props.class === 'active'); }); test('html() interpolates on:click attribute value', () => { const handler = function click() {}; const nodes = html``; 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`
${a} ${b}
`; 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``; 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);