New techniques for storing state in querystring
This commit is contained in:
@@ -203,6 +203,132 @@ test('serializes and restores checked and expanded state through a fragment', ()
|
||||
assert.equal(second.container.querySelector('.checkbox-tree__toggle').getAttribute('aria-expanded'), 'true');
|
||||
});
|
||||
|
||||
test('tiered encoding stores only child differences and restores nested state', () => {
|
||||
const manifest = loadManifest({
|
||||
schema: 1,
|
||||
nodes: ['parent', 'parent>first', 'parent>second']
|
||||
});
|
||||
const first = createTree({ stateEncoding: 'tiered' });
|
||||
checkbox(first.container, 'first').click();
|
||||
first.container.querySelector('.checkbox-tree__toggle').click();
|
||||
const fragment = first.tree.serializeStateToFragment(manifest);
|
||||
const params = new URLSearchParams(fragment.slice(1));
|
||||
assert.equal(params.has('c1'), false);
|
||||
assert.equal(params.has('c2'), true);
|
||||
assert.equal(params.has('x1'), true);
|
||||
assert.equal(params.has('x2'), false);
|
||||
assert.equal(params.has('c'), false);
|
||||
|
||||
const second = createTree({ manifest, stateEncoding: 'tiered' });
|
||||
const result = second.tree.restoreStateFromLocation(manifest, { hash: fragment });
|
||||
assert.equal(result.applied, true);
|
||||
assert.deepEqual(second.tree.getSelectedIds(), ['first']);
|
||||
assert.equal(second.container.querySelector('.checkbox-tree__toggle').getAttribute('aria-expanded'), 'true');
|
||||
});
|
||||
|
||||
test('tiered expansion treats collapsed as the baseline at every depth', () => {
|
||||
const data = [{
|
||||
id: 'root',
|
||||
label: 'Root',
|
||||
children: [{
|
||||
id: 'branch',
|
||||
label: 'Branch',
|
||||
children: [{ id: 'leaf', label: 'Leaf' }]
|
||||
}]
|
||||
}];
|
||||
const manifest = loadManifest({
|
||||
schema: 1,
|
||||
nodes: ['root', 'root>branch', 'root>branch>leaf']
|
||||
});
|
||||
const container = document.createElement('div');
|
||||
document.body.replaceChildren(container);
|
||||
const first = new CheckboxTree(container, { stateEncoding: 'tiered' });
|
||||
first.setData(data);
|
||||
first.itemForNode('root').querySelector('.checkbox-tree__toggle').click();
|
||||
const fragment = first.serializeStateToFragment(manifest);
|
||||
const params = new URLSearchParams(fragment.slice(1));
|
||||
assert.equal(params.has('x1'), true);
|
||||
assert.equal(params.has('x2'), false);
|
||||
|
||||
const restoredContainer = document.createElement('div');
|
||||
document.body.replaceChildren(restoredContainer);
|
||||
const restored = new CheckboxTree(restoredContainer, { manifest, stateEncoding: 'tiered' });
|
||||
restored.setData(data);
|
||||
restored.restoreStateFromLocation(manifest, { hash: fragment });
|
||||
assert.equal(restored.itemForNode('root').querySelector('.checkbox-tree__toggle').getAttribute('aria-expanded'), 'true');
|
||||
assert.equal(restored.itemForNode('branch').querySelector('.checkbox-tree__toggle').getAttribute('aria-expanded'), 'false');
|
||||
});
|
||||
|
||||
test('tiered encoding uses false beneath a non-selectable parent', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.replaceChildren(container);
|
||||
const manifest = loadManifest({
|
||||
schema: 1,
|
||||
nodes: ['root', 'root>group', 'root>group>leaf']
|
||||
});
|
||||
const data = [{
|
||||
id: 'root',
|
||||
label: 'Root',
|
||||
children: [{
|
||||
id: 'group',
|
||||
label: 'Group',
|
||||
selectable: false,
|
||||
children: [{ id: 'leaf', label: 'Leaf' }]
|
||||
}]
|
||||
}];
|
||||
const first = new CheckboxTree(container, { stateEncoding: 'tiered' });
|
||||
first.setData(data);
|
||||
checkbox(container, 'leaf').click();
|
||||
const fragment = first.serializeStateToFragment(manifest);
|
||||
assert.equal(new URLSearchParams(fragment.slice(1)).has('c3'), true);
|
||||
|
||||
const restoredContainer = document.createElement('div');
|
||||
document.body.replaceChildren(restoredContainer);
|
||||
const restored = new CheckboxTree(restoredContainer, { manifest, stateEncoding: 'tiered' });
|
||||
restored.setData(data);
|
||||
restored.restoreStateFromLocation(manifest, { hash: fragment });
|
||||
assert.deepEqual(restored.getSelectedIds(), ['leaf']);
|
||||
});
|
||||
|
||||
test('tiered URLs default nodes appended beyond their coverage to false', () => {
|
||||
const originalManifest = loadManifest({ schema: 1, nodes: ['parent', 'parent>first', 'parent>second'] });
|
||||
const first = createTree({ initiallySelected: true, stateEncoding: 'tiered' });
|
||||
const fragment = first.tree.serializeStateToFragment(originalManifest);
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.body.replaceChildren(container);
|
||||
const evolvedManifest = loadManifest({
|
||||
schema: 1,
|
||||
nodes: ['parent', 'parent>first', 'parent>second', 'parent>new']
|
||||
});
|
||||
const restored = new CheckboxTree(container, { manifest: evolvedManifest, stateEncoding: 'tiered' });
|
||||
restored.setData([{
|
||||
id: 'parent',
|
||||
label: 'Parent',
|
||||
children: [
|
||||
{ id: 'first', label: 'First' },
|
||||
{ id: 'second', label: 'Second' },
|
||||
{ id: 'new', label: 'New' }
|
||||
]
|
||||
}]);
|
||||
restored.restoreStateFromLocation(evolvedManifest, { hash: fragment });
|
||||
assert.deepEqual(restored.getSelectedIds(), ['first', 'second']);
|
||||
assert.equal(checkbox(container, 'parent').indeterminate, true);
|
||||
});
|
||||
|
||||
test('dense encoding can be selected independently of the URL', () => {
|
||||
const manifest = loadManifest({ schema: 1, nodes: ['parent', 'parent>first', 'parent>second'] });
|
||||
const first = createTree({ stateEncoding: 'dense' });
|
||||
checkbox(first.container, 'first').click();
|
||||
const fragment = first.tree.serializeStateToFragment(manifest);
|
||||
assert.equal(fragment, '#v=1&c=Ag');
|
||||
|
||||
const second = createTree({ manifest, stateEncoding: 'dense' });
|
||||
second.tree.restoreStateFromLocation(manifest, { hash: fragment });
|
||||
assert.deepEqual(second.tree.getSelectedIds(), ['first']);
|
||||
assert.throws(() => createTree({ stateEncoding: 'unknown' }), /Unsupported CheckboxTree state encoding/);
|
||||
});
|
||||
|
||||
test('compatible manifest changes preserve slots and default appended nodes', () => {
|
||||
const oldManifest = loadManifest({ schema: 1, nodes: ['parent', 'parent>first', 'parent>second'] });
|
||||
const first = createTree();
|
||||
@@ -226,6 +352,8 @@ test('malformed, oversized, and unsupported URL state fails safely', () => {
|
||||
assert.equal(decodeTreeState('#v=2&c=AQ', manifest).applied, false);
|
||||
assert.equal(decodeTreeState('#v=1&c=!', manifest).applied, false);
|
||||
assert.equal(decodeTreeState('#v=1&c=Aw', manifest).applied, false);
|
||||
assert.equal(decodeTreeState('#v=1&n=3&c1=AQA', manifest).applied, false);
|
||||
assert.equal(decodeTreeState('#v=1&c=AQA', manifest, 'tiered').applied, false);
|
||||
|
||||
const { tree } = createTree({ initiallySelected: true });
|
||||
const result = tree.restoreStateFromLocation(manifest, { hash: '#v=1&c=!' });
|
||||
|
||||
Reference in New Issue
Block a user