mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 20:19:39 +00:00
* wasm first steps * parsing start * parsing continued * progress * progress * progress * progress * progress * progress * progress * progress * fixes * reorganize * change type * fix * progress * refactoring * progress * progress * progress * keep style info * progress * progress * remove (copy) in name * progress * merge methods into 1 * splice * progress * progress * progress * cleaning * progress * progress * progress * visibility * progress * progress * progress * progress * progress * fixes * progress * fix markers * fix * progress * progress * progress * progress * progress * progress * small fix * use engine info * fix popups * small fix * progress * progress * progress * clean tool * handle self closing elements * reduce data exchanges across boundary * copy on wasm side to avoid any data corruption * speed fix * routing * refactoring * reverse * reverse * crop * split * merge * progress * time tool * fix bounds for waypoints * reduce * fix reactivity * handle corrupt time data * export * progress * remove gpx js package * remove todo * update readme * cleaning * progress * fix link fields * remove unused fields * refactoring and tests * import and export of routes, ref #192 * per-chunk categories storage * cross-tab sync * outdated state * refactoring * reorder methods * refactoring * fix * improve flow * renaming * progress * progress * fix * progress * per file history * keep original flag * use history variant * fix * progress * file explorer * progress * fix * sticky folders * improved sticky folders * fix sortable * fix sortable * fix * fix tab size * no file delete on desktop * fix routing on other file * fix sorting * remember language * open links in browser * commit missing changes * avoid selecting on resize * clarify filename * explorer actions * fix folder styling * drop anywhere in folder * cleaning * fixes * remove focus-ring * full screen * small fix * consistent highlight color * improved preview * performance improvements * sparser data transfers * sparser data transfers * show popup once data is loaded * fix routing control size * fix * sparse desktop build * remove custom ign styles * use new endpoint for openhikingmap * strip native menu * reorder context menu items * route full support * route - track conversion * create route file action * route export, filter out anchors at high zoom level * clarify name handling * move hide & center to view * routes continued * update docs * fix power format, closes #378 * lint fixes * fix display of additional data with gaps, closes #361 * fix linting issues * fix import modules * fix multidrag * change base to resolve * fix type errors * fix layer typings * fix last type error * each keys * improve typings * deploy to cloudflare --------- Co-authored-by: vcoppe <vcoppe@hexaly.com>
222 lines
8.3 KiB
TypeScript
222 lines
8.3 KiB
TypeScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import type { FileStructure } from 'gpx-wasm';
|
|
import {
|
|
isHidden,
|
|
isSegmentHidden,
|
|
isWaypointHidden,
|
|
parseVisibility,
|
|
setHidden,
|
|
stringifyVisibility,
|
|
waypointsKey,
|
|
type Visibility,
|
|
type VisibilityState,
|
|
} from '$lib/engine/file-visibility';
|
|
|
|
const segment = (id: string) => ({ id, rev: `rev-${id}`, length: 2 });
|
|
|
|
// F
|
|
// ├── waypoints: W1, W2
|
|
// ├── T1: S1, S2
|
|
// └── T2: S3
|
|
const structure: FileStructure = {
|
|
id: 'F',
|
|
name: 'file',
|
|
waypointsRev: 'wpt-rev',
|
|
waypoints: [{ id: 'W1' }, { id: 'W2' }],
|
|
routes: [],
|
|
tracks: [
|
|
{ id: 'T1', segments: [segment('S1'), segment('S2')] },
|
|
{ id: 'T2', segments: [segment('S3')] },
|
|
],
|
|
};
|
|
const WPTS = waypointsKey('F');
|
|
const elements = ['F', WPTS, 'W1', 'W2', 'T1', 'S1', 'S2', 'T2', 'S3'];
|
|
|
|
/** Applies the actions one after the other, starting with everything visible. */
|
|
function apply(...actions: [ids: string[], hidden: boolean][]): VisibilityState {
|
|
let visibility: Visibility = new Map();
|
|
for (const [ids, hidden] of actions) {
|
|
visibility = setHidden(structure, visibility, ids, hidden);
|
|
}
|
|
return { structure, visibility };
|
|
}
|
|
|
|
function hiddenElements(state: VisibilityState): string[] {
|
|
return elements.filter((id) => isHidden(state, id));
|
|
}
|
|
|
|
describe('file visibility', () => {
|
|
it('shows everything by default', () => {
|
|
assert.deepEqual(hiddenElements(apply()), []);
|
|
});
|
|
|
|
it('hides an element and what is below it', () => {
|
|
assert.deepEqual(hiddenElements(apply([['T1'], true])), ['T1', 'S1', 'S2']);
|
|
assert.deepEqual(hiddenElements(apply([['S1'], true])), ['S1']);
|
|
assert.deepEqual(hiddenElements(apply([[WPTS], true])), [WPTS, 'W1', 'W2']);
|
|
assert.deepEqual(hiddenElements(apply([['W2'], true])), ['W2']);
|
|
});
|
|
|
|
it('hides a whole file', () => {
|
|
assert.deepEqual(hiddenElements(apply([['F'], true])), elements);
|
|
});
|
|
|
|
it('shows again what was hidden', () => {
|
|
assert.deepEqual(hiddenElements(apply([['F'], true], [['F'], false])), []);
|
|
assert.deepEqual(hiddenElements(apply([['S1'], true], [['S1'], false])), []);
|
|
});
|
|
|
|
it('keeps the other segments hidden when one is shown below a hidden file', () => {
|
|
const state = apply([['F'], true], [['S1'], false]);
|
|
assert.deepEqual(hiddenElements(state), [WPTS, 'W1', 'W2', 'S2', 'T2', 'S3']);
|
|
// the file and the track of the visible segment are not hidden, they have visible content
|
|
assert.equal(isHidden(state, 'F'), false);
|
|
assert.equal(isHidden(state, 'T1'), false);
|
|
assert.equal(isHidden(state, 'S1'), false);
|
|
});
|
|
|
|
it('hides the file again once what was shown is hidden', () => {
|
|
const state = apply([['F'], true], [['S1'], false], [['S1'], true]);
|
|
assert.deepEqual(hiddenElements(state), elements);
|
|
});
|
|
|
|
it('resets what was shown below when hiding a parent', () => {
|
|
const state = apply([['F'], true], [['S1'], false], [['F'], true]);
|
|
assert.deepEqual(hiddenElements(state), elements);
|
|
assert.equal(state.visibility.has('S1'), false);
|
|
});
|
|
|
|
it('resets what was hidden below when showing a parent', () => {
|
|
const state = apply([['S1', 'S3', 'W1'], true], [['F'], false]);
|
|
assert.deepEqual(hiddenElements(state), []);
|
|
assert.equal(state.visibility.has('S1'), false);
|
|
});
|
|
|
|
it('shows a track hidden below a hidden file, with all its segments', () => {
|
|
const state = apply([['F'], true], [['T1'], false]);
|
|
assert.deepEqual(hiddenElements(state), [WPTS, 'W1', 'W2', 'T2', 'S3']);
|
|
});
|
|
|
|
it('shows a waypoint below a hidden file', () => {
|
|
const state = apply([['F'], true], [['W1'], false]);
|
|
assert.deepEqual(hiddenElements(state), ['W2', 'T1', 'S1', 'S2', 'T2', 'S3']);
|
|
assert.equal(isHidden(state, WPTS), false);
|
|
assert.equal(isHidden(state, 'F'), false);
|
|
});
|
|
|
|
it('hides a segment shown inside a hidden track of a visible file', () => {
|
|
const state = apply([['T1'], true], [['S2'], false]);
|
|
assert.deepEqual(hiddenElements(state), ['S1']);
|
|
});
|
|
|
|
it('does not touch the other branches of the tree', () => {
|
|
const state = apply([['T1'], true], [['T2'], true], [['T1'], false]);
|
|
assert.deepEqual(hiddenElements(state), ['T2', 'S3']);
|
|
});
|
|
|
|
it('has leaf helpers agreeing with isHidden', () => {
|
|
const states = [
|
|
apply(),
|
|
apply([['F'], true]),
|
|
apply([['F'], true], [['S1'], false], [['W2'], false]),
|
|
apply([['T1'], true], [['S2'], false]),
|
|
apply([[WPTS], true], [['W1'], false]),
|
|
];
|
|
for (const state of states) {
|
|
for (const track of structure.tracks) {
|
|
for (const { id } of track.segments) {
|
|
assert.equal(isSegmentHidden(state, track.id, id), isHidden(state, id));
|
|
}
|
|
}
|
|
for (const { id } of structure.waypoints) {
|
|
assert.equal(isWaypointHidden(state, id), isHidden(state, id));
|
|
}
|
|
}
|
|
});
|
|
|
|
it('hides a route, which is a leaf like a waypoint', () => {
|
|
const withRoute: FileStructure = {
|
|
...structure,
|
|
routes: [{ id: 'R', segment: segment('RS') }],
|
|
};
|
|
const state = (...actions: [ids: string[], hidden: boolean][]): VisibilityState => {
|
|
let visibility: Visibility = new Map();
|
|
for (const [ids, hidden] of actions) {
|
|
visibility = setHidden(withRoute, visibility, ids, hidden);
|
|
}
|
|
return { structure: withRoute, visibility };
|
|
};
|
|
assert.equal(isHidden(state([['R'], true]), 'R'), true);
|
|
assert.equal(isHidden(state([['R'], true]), 'F'), false);
|
|
assert.equal(isSegmentHidden(state([['R'], true]), 'R', 'RS'), true);
|
|
// with its file, and shown on its own below it
|
|
assert.equal(isHidden(state([['F'], true]), 'R'), true);
|
|
const shown = state([['F'], true], [['R'], false]);
|
|
assert.equal(isHidden(shown, 'R'), false);
|
|
assert.equal(isHidden(shown, 'F'), false);
|
|
assert.equal(isHidden(shown, 'T1'), true);
|
|
});
|
|
|
|
it('does not consider unknown ids hidden', () => {
|
|
assert.equal(isHidden(apply([['F'], true]), 'unknown'), false);
|
|
});
|
|
|
|
it('does not mutate the previous visibility', () => {
|
|
const before: Visibility = new Map([['S1', false]]);
|
|
setHidden(structure, before, ['F'], true);
|
|
assert.deepEqual([...before], [['S1', false]]);
|
|
});
|
|
});
|
|
|
|
describe('kept visibility', () => {
|
|
const visibility = (entries: [string, boolean][]): Visibility => new Map(entries);
|
|
|
|
it('comes back as it was kept', () => {
|
|
const files = new Map([
|
|
[
|
|
'F',
|
|
visibility([
|
|
['F', false],
|
|
['S1', true],
|
|
[WPTS, false],
|
|
]),
|
|
],
|
|
['G', visibility([['G', false]])],
|
|
]);
|
|
const json = stringifyVisibility(files, () => true);
|
|
assert.deepEqual(parseVisibility(json), files);
|
|
});
|
|
|
|
it('leaves out the files that are gone and the ones with nothing set', () => {
|
|
const files = new Map([
|
|
['F', visibility([['F', false]])],
|
|
['G', visibility([['G', false]])],
|
|
['H', visibility([])],
|
|
]);
|
|
const json = stringifyVisibility(files, (fileId) => fileId !== 'G');
|
|
assert.deepEqual([...parseVisibility(json).keys()], ['F']);
|
|
});
|
|
|
|
it('is nothing when there is nothing to keep', () => {
|
|
assert.equal(
|
|
stringifyVisibility(new Map(), () => true),
|
|
undefined
|
|
);
|
|
assert.equal(
|
|
stringifyVisibility(new Map([['F', visibility([['F', false]])]]), () => false),
|
|
undefined
|
|
);
|
|
assert.equal(parseVisibility(undefined).size, 0);
|
|
});
|
|
|
|
it('ignores what is not visibility', () => {
|
|
assert.equal(parseVisibility('not json').size, 0);
|
|
assert.equal(parseVisibility('3').size, 0);
|
|
assert.equal(parseVisibility('null').size, 0);
|
|
const parsed = parseVisibility('{"F":{"F":false,"S1":"no"},"G":3,"H":{}}');
|
|
assert.deepEqual([...parsed.keys()], ['F']);
|
|
assert.deepEqual([...parsed.get('F')!], [['F', false]]);
|
|
});
|
|
});
|