Refactor of tree stuff

This commit is contained in:
2026-07-11 20:30:14 -05:00
parent 4d44660285
commit c32a984c8e
8 changed files with 566 additions and 375 deletions
+100
View File
@@ -0,0 +1,100 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { JSDOM } from 'jsdom';
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'https://example.test/' });
globalThis.window = dom.window;
globalThis.document = dom.window.document;
globalThis.Element = dom.window.Element;
globalThis.localStorage = dom.window.localStorage;
const { CheckboxTree } = await import('../web/lib/checkbox-tree/checkbox-tree.js');
function createTree(options = {}) {
const container = document.createElement('div');
document.body.replaceChildren(container);
const tree = new CheckboxTree(container, options);
tree.setData([
{
id: 'parent',
label: 'Parent',
metadata: { kind: 'parent' },
children: [
{ id: 'first', label: 'First', metadata: { kind: 'leaf' } },
{ id: 'second', label: 'Second', metadata: { kind: 'leaf' } }
]
}
]);
return { container, tree };
}
function checkbox(container, id) {
return container.querySelector(`.checkbox-tree__checkbox[data-node-id="${id}"]`);
}
test('selecting a parent selects all descendants and emits selected nodes', () => {
let change;
const { container, tree } = createTree({ onSelectionChange: event => { change = event; } });
const parent = checkbox(container, 'parent');
parent.checked = true;
parent.dispatchEvent(new window.Event('change', { bubbles: true }));
assert.deepEqual(tree.getSelectedIds(), ['parent', 'first', 'second']);
assert.deepEqual(change.selectedIds, ['parent', 'first', 'second']);
assert.equal(change.selectedNodes[1].metadata.kind, 'leaf');
});
test('partial child selection makes the parent indeterminate', () => {
const { container, tree } = createTree();
const first = checkbox(container, 'first');
first.checked = true;
first.dispatchEvent(new window.Event('change', { bubbles: true }));
const parent = checkbox(container, 'parent');
assert.equal(parent.checked, false);
assert.equal(parent.indeterminate, true);
assert.deepEqual(tree.getSelectedIds(), ['first']);
});
test('persists selection and collapsed branches per configured key', () => {
localStorage.clear();
const first = createTree({ storageKey: 'example-tree' });
checkbox(first.container, 'first').click();
const toggle = first.container.querySelector('.checkbox-tree__toggle');
toggle.click();
assert.equal(toggle.getAttribute('aria-expanded'), 'true');
const second = createTree({ storageKey: 'example-tree' });
assert.deepEqual(second.tree.getSelectedIds(), ['first']);
assert.equal(second.container.querySelector('.checkbox-tree__toggle').getAttribute('aria-expanded'), 'true');
});
test('supports independent instances and removes listeners on destroy', () => {
const firstContainer = document.createElement('div');
const secondContainer = document.createElement('div');
document.body.replaceChildren(firstContainer, secondContainer);
const first = new CheckboxTree(firstContainer);
const second = new CheckboxTree(secondContainer);
const data = [{ id: 'node', label: 'Node' }];
first.setData(data);
second.setData(data);
checkbox(firstContainer, 'node').click();
assert.deepEqual(first.getSelectedIds(), ['node']);
assert.deepEqual(second.getSelectedIds(), []);
first.destroy();
assert.equal(firstContainer.childElementCount, 0);
assert.equal(firstContainer.classList.contains('checkbox-tree'), false);
});
test('rejects duplicate node ids', () => {
const container = document.createElement('div');
const tree = new CheckboxTree(container);
assert.throws(
() => tree.setData([{ id: 'same', label: 'One' }, { id: 'same', label: 'Two' }]),
/Duplicate CheckboxTree node id/
);
});