174 lines
7.7 KiB
JavaScript
174 lines
7.7 KiB
JavaScript
// 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 & 2" title=""q" 's'">x < 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 & 2" title=""q" \'s\'">x < 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> & 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);
|
|
});
|
|
});
|