mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-11 22:54:44 +00:00
8b924cd217
## Summary Encodes view + filter state in the URL hash so deep links restore the exact page state (issue #749). ## Changes New shared helper `public/url-state.js` exposing `URLState`: - `parseSort('col:asc')` → `{column, direction}` (defaults to `desc`) - `serializeSort('col', 'desc')` → `'col'` (omits default direction) - `parseHash('#/nodes/abc?tab=x')` → `{route: 'nodes/abc', params: {tab:'x'}}` - `buildHash(route, params)` and `updateHashParams(updates, currentHash)` for round-tripping while preserving subpaths. Wired into: - **packets.js** — sort column/direction now in `#/packets?sort=col[:asc]`, restored on init (overrides localStorage). Subpath `#/packets/<hash>` preserved. - **nodes.js** — sort encoded as `#/nodes?sort=col[:asc]`, restored on init. Subpath `#/nodes/<pubkey>` preserved. - **analytics.js** — both selected tab (`tab=topology`) AND time-window picker value (`window=7d`) now round-trip via URL. Subview keys used by rf-health (`range/observer/from/to`) cleared when switching tabs to keep URLs clean. Existing deep links (`#/nodes/<pubkey>`, `#/packets/<hash>`, `?filter=…`, `?node=…`, `?observer=…`, `?channel=…`, `?timeWindow=…`, `?region=…`) all keep working — additive change only. ## Tests TDD red→green: - Red: `5e1482e` (stub throws "not implemented"; 18/18 tests fail on assertions) - Green: `512940e` (helper implemented; 18/18 pass) Wired `test-url-state.js` into `test-all.sh`. Fixes #749 --------- Co-authored-by: clawbot <clawbot@users.noreply.github.com>
104 lines
4.3 KiB
JavaScript
104 lines
4.3 KiB
JavaScript
/* Unit tests for URL state helpers (issue #749) */
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const URLState = require('./public/url-state.js');
|
|
|
|
let passed = 0, failed = 0;
|
|
function test(name, fn) {
|
|
try { fn(); passed++; console.log(' ✅ ' + name); }
|
|
catch (e) { failed++; console.log(' ❌ ' + name + ': ' + e.message); }
|
|
}
|
|
|
|
console.log('── URL State Helpers ──');
|
|
|
|
// ------- parseSort -------
|
|
test('parseSort: column only defaults to desc', function () {
|
|
assert.deepStrictEqual(URLState.parseSort('time'), { column: 'time', direction: 'desc' });
|
|
});
|
|
test('parseSort: column:asc', function () {
|
|
assert.deepStrictEqual(URLState.parseSort('lastSeen:asc'), { column: 'lastSeen', direction: 'asc' });
|
|
});
|
|
test('parseSort: column:desc', function () {
|
|
assert.deepStrictEqual(URLState.parseSort('time:desc'), { column: 'time', direction: 'desc' });
|
|
});
|
|
test('parseSort: invalid direction → desc', function () {
|
|
assert.deepStrictEqual(URLState.parseSort('time:weird'), { column: 'time', direction: 'desc' });
|
|
});
|
|
test('parseSort: empty/null → null', function () {
|
|
assert.strictEqual(URLState.parseSort(''), null);
|
|
assert.strictEqual(URLState.parseSort(null), null);
|
|
assert.strictEqual(URLState.parseSort(undefined), null);
|
|
});
|
|
|
|
// ------- serializeSort -------
|
|
test('serializeSort: desc default omitted', function () {
|
|
assert.strictEqual(URLState.serializeSort('time', 'desc'), 'time');
|
|
});
|
|
test('serializeSort: asc included', function () {
|
|
assert.strictEqual(URLState.serializeSort('lastSeen', 'asc'), 'lastSeen:asc');
|
|
});
|
|
test('serializeSort: empty column → empty string', function () {
|
|
assert.strictEqual(URLState.serializeSort('', 'desc'), '');
|
|
assert.strictEqual(URLState.serializeSort(null, 'asc'), '');
|
|
});
|
|
|
|
// ------- parseHash -------
|
|
test('parseHash: bare route', function () {
|
|
assert.deepStrictEqual(URLState.parseHash('#/packets'), { route: 'packets', params: {} });
|
|
});
|
|
test('parseHash: route with params', function () {
|
|
var r = URLState.parseHash('#/packets?filter=type%3D%3DADVERT&sort=time');
|
|
assert.strictEqual(r.route, 'packets');
|
|
assert.strictEqual(r.params.filter, 'type==ADVERT');
|
|
assert.strictEqual(r.params.sort, 'time');
|
|
});
|
|
test('parseHash: route with subpath kept (existing deep links)', function () {
|
|
var r = URLState.parseHash('#/nodes/abc123def?tab=repeaters');
|
|
assert.strictEqual(r.route, 'nodes/abc123def');
|
|
assert.strictEqual(r.params.tab, 'repeaters');
|
|
});
|
|
test('parseHash: empty hash', function () {
|
|
assert.deepStrictEqual(URLState.parseHash(''), { route: '', params: {} });
|
|
assert.deepStrictEqual(URLState.parseHash('#/'), { route: '', params: {} });
|
|
});
|
|
|
|
// ------- buildHash -------
|
|
test('buildHash: bare route', function () {
|
|
assert.strictEqual(URLState.buildHash('packets', {}), '#/packets');
|
|
});
|
|
test('buildHash: with params, omits empty values', function () {
|
|
var h = URLState.buildHash('packets', { filter: 'type==ADVERT', sort: '', empty: null, blank: undefined });
|
|
assert.strictEqual(h, '#/packets?filter=type%3D%3DADVERT');
|
|
});
|
|
test('buildHash: encodes special chars', function () {
|
|
var h = URLState.buildHash('analytics', { tab: 'topology', window: '7d' });
|
|
// Order is preserved in object iteration
|
|
assert.ok(h === '#/analytics?tab=topology&window=7d' || h === '#/analytics?window=7d&tab=topology');
|
|
});
|
|
test('buildHash: leading "#/" is OK on route, normalized', function () {
|
|
assert.strictEqual(URLState.buildHash('#/packets', { sort: 'time' }), '#/packets?sort=time');
|
|
});
|
|
|
|
// ------- updateHashParams -------
|
|
test('updateHashParams: round-trip preserves route subpath', function () {
|
|
// Simulate location.hash environment
|
|
var fakeLocation = { hash: '#/nodes/abcdef?tab=repeaters' };
|
|
var newHash = URLState.updateHashParams({ sort: 'lastSeen:asc' }, fakeLocation.hash);
|
|
// Must keep the nodes/abcdef subpath
|
|
var r = URLState.parseHash(newHash);
|
|
assert.strictEqual(r.route, 'nodes/abcdef');
|
|
assert.strictEqual(r.params.tab, 'repeaters');
|
|
assert.strictEqual(r.params.sort, 'lastSeen:asc');
|
|
});
|
|
test('updateHashParams: setting empty/null removes key', function () {
|
|
var newHash = URLState.updateHashParams({ tab: '' }, '#/nodes?tab=repeaters&search=foo');
|
|
var r = URLState.parseHash(newHash);
|
|
assert.strictEqual(r.params.tab, undefined);
|
|
assert.strictEqual(r.params.search, 'foo');
|
|
});
|
|
|
|
console.log('');
|
|
console.log(passed + ' passed, ' + failed + ' failed');
|
|
process.exit(failed > 0 ? 1 : 0);
|