Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+173
View File
@@ -0,0 +1,173 @@
// Generated, do not edit. sha256:23dd50a7ad5448e3230fa33ba9e025d2fdfec896be66303dade6c4b550502fa8
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { installDom, parseSelector } from '../helpers/mini-dom.mjs';
const dom = installDom();
const html = s => { const el = dom.host(); el.innerHTML = s; return el; };
describe('mini-dom: parsing and serialising', () => {
it('builds elements, text and attributes, and decodes what esc() writes', () => {
const el = html('<div class="a b" data-x="1 &amp; 2" title="&quot;q&quot; &#39;s&#39;">x &lt; y</div>');
const d = el.querySelector('div');
assert.equal(d.getAttribute('data-x'), '1 & 2');
assert.equal(d.title, `"q" 's'`);
assert.equal(d.textContent, 'x < y');
assert.equal(el.innerHTML, '<div class="a b" data-x="1 &amp; 2" title="&quot;q&quot; \'s\'">x &lt; y</div>');
});
it('knows void elements, self-closing svg and a raw textarea', () => {
const el = html('<input data-field="a" value="v"><svg><line x1="1"/></svg><textarea data-field="t">a <b> &amp; c</textarea><span>na</span>');
assert.equal(el.children.length, 4);
assert.equal(el.querySelector('svg').children[0].localName, 'line');
assert.equal(el.querySelector('textarea').value, 'a <b> & c');
assert.equal(el.querySelector('span').textContent, 'na');
});
it('a > inside a quoted attribute does not end the tag', () => {
const el = html('<button title="a > b" data-k="1">x</button>');
assert.equal(el.querySelector('button').title, 'a > b');
assert.equal(el.querySelector('button').textContent, 'x');
});
it('a stray closing tag is ignored, and replacing innerHTML detaches the old nodes', () => {
const el = html('<p>a</span>b</p>');
assert.equal(el.querySelector('p').textContent, 'ab');
const p = el.querySelector('p');
el.innerHTML = '<i>new</i>';
assert.equal(p.isConnected, false);
assert.equal(el.querySelector('i').isConnected, true);
});
});
describe('mini-dom: selectors', () => {
const el = html(`<div class="table-wrap has-exp"><table><tbody>
<tr data-row-id="1" class="row-alt"><td class="num">1</td></tr>
<tr class="row-expansion" data-exp-id="1"><td>drawer</td></tr>
<tr data-row-id="2"><td><input data-field="a" disabled><input data-field="b" type="hidden"><input data-field="c" type="date"></td></tr>
</tbody></table></div>`);
it('tag, id, class, attribute with and without value, and :not', () => {
assert.equal(el.querySelectorAll('tr').length, 3);
assert.equal(el.querySelectorAll('tr[data-row-id]').length, 2);
assert.equal(el.querySelector('tr[data-row-id="2"]').dataset.rowId, '2');
assert.equal(el.querySelector('tr.row-expansion[data-exp-id="1"] td').textContent, 'drawer');
assert.equal(el.querySelectorAll('.table-wrap.has-exp').length, 1);
const free = el.querySelectorAll('input:not([disabled]):not([type="hidden"]), select:not([disabled])');
assert.deepEqual(free.map(i => i.dataset.field), ['c']);
});
it('descendant and child combinators are not the same', () => {
assert.equal(el.querySelectorAll('tbody td').length, 3);
assert.equal(el.querySelectorAll('table > td').length, 0);
assert.equal(el.querySelectorAll('tr > td').length, 3);
});
it('closest walks up and includes the element itself; matches tests one element', () => {
const td = el.querySelector('tr[data-row-id="1"] td');
assert.equal(td.closest('tr[data-row-id]').dataset.rowId, '1');
assert.equal(td.closest('td'), td);
assert.equal(td.closest('[data-save]'), null);
assert.equal(td.matches('.num'), true);
});
it('a selector it does not understand throws instead of matching nothing', () => {
assert.throws(() => parseSelector('tr:first-child'), /unsupported/);
assert.throws(() => parseSelector('a ~ b'), /unsupported/);
assert.throws(() => parseSelector('[x'), /unclosed/);
});
});
describe('mini-dom: form state', () => {
it('a select reads the selected option, then what was set', () => {
const el = html('<select><option value="a">A</option><option value="b" selected>B</option></select><select><option value="x">X</option></select>');
const [s1, s2] = el.querySelectorAll('select');
assert.equal(s1.value, 'b');
assert.equal(s2.value, 'x');
s1.value = 'a';
assert.equal(s1.value, 'a');
});
it('an input reads its attribute until a value is set; a checkbox its checked', () => {
const el = html('<input value="1"><input type="checkbox" checked><input type="checkbox">');
const [t, c1, c2] = el.querySelectorAll('input');
assert.equal(t.value, '1');
t.value = '2';
assert.equal(t.value, '2');
assert.equal(t.getAttribute('value'), '1');
assert.equal(c1.checked, true);
assert.equal(c2.checked, false);
assert.equal(c1.type, 'checkbox');
assert.equal(t.type, 'text');
});
it('there is a head for scripts, and getComputedStyle reads the inline style', () => {
assert.equal(dom.document.head.parentNode, dom.document.documentElement);
dom.document.documentElement.style.setProperty('--accent', '#ff6b00');
assert.equal(getComputedStyle(dom.document.documentElement).getPropertyValue('--accent'), '#ff6b00');
});
it('focus moves activeElement, and contains sees it', () => {
const el = html('<input data-field="a">');
el.querySelector('input').focus();
assert.equal(dom.document.activeElement, el.querySelector('input'));
assert.equal(el.contains(dom.document.activeElement), true);
});
});
describe('mini-dom: events', () => {
it('bubbles through on-properties and listeners, and stopPropagation stops it', () => {
const el = html('<div id="outer"><div id="inner"><button>b</button></div></div>');
const log = [];
el.querySelector('#outer').onclick = () => log.push('outer');
el.querySelector('#inner').addEventListener('click', e => { log.push('inner'); e.stopPropagation(); });
dom.click(el.querySelector('button'));
assert.deepEqual(log, ['inner']);
});
it('a capture listener on the document runs first, and target is the clicked element', () => {
const el = html('<span>x</span>');
const log = [];
const cap = e => log.push(['doc', e.target.localName]);
dom.document.addEventListener('click', cap, true);
el.onclick = () => log.push(['host']);
dom.click(el.querySelector('span'));
dom.document.removeEventListener('click', cap, true);
assert.deepEqual(log, [['doc', 'span'], ['host']]);
});
it('dispatchEvent with a platform Event works, and keydown carries key and modifiers', () => {
const el = html('<input>');
let got = null;
el.oninput = e => { got = e.type; };
el.querySelector('input').dispatchEvent(new Event('input', { bubbles: true }));
assert.equal(got, 'input');
let k = null;
el.onkeydown = e => { k = [e.key, e.ctrlKey]; e.preventDefault(); };
assert.equal(dom.key(el.querySelector('input'), 'Enter', { ctrlKey: true }), false);
assert.deepEqual(k, ['Enter', true]);
});
it('the document dispatches too, and a CustomEvent keeps its detail', () => {
let got = null;
const fn = e => { got = e.detail; };
dom.document.addEventListener('blur-changed', fn);
dom.document.dispatchEvent(new CustomEvent('blur-changed', { detail: { on: true } }));
dom.document.removeEventListener('blur-changed', fn);
assert.deepEqual(got, { on: true });
});
it('append adds several children in order', () => {
const el = html('');
el.append(dom.document.createElement('b'), dom.document.createElement('i'));
assert.deepEqual(el.children.map(c => c.localName), ['b', 'i']);
});
it('animation frames wait until they are flushed', () => {
let ran = false;
requestAnimationFrame(() => { ran = true; });
assert.equal(ran, false);
dom.flushFrames();
assert.equal(ran, true);
});
});