Files
MrAlders0nanddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dff9729583 v1.2.0 (#19)
* stats: node-types donut, multi-IATA filtering, preset split

 - node-types: new /stats/node-types endpoint + useNodeTypes; donutOption
   replaces the "Coming soon" card with a live donut
 - stats endpoints take iatas[] instead of a single iata, so multi-IATA
   regions filter instead of falling back to all (drops useStatsIata)
 - radio presets keep the node/observer split via stacked presetBarsOption
 - drop client-side telemetry ms-normalization (backend now emits epoch ms
   on both paths); charts pick delta-vs-raw counters off the response interval
 - MeshTab: range-driven charts lead, all-time charts follow; KPI window
   from overview.windowHours instead of a hardcoded 24h
 - ci: docker-publish builds a :dev image on dev-branch pushes

* feat: traces vs pings
feat: SNR for trace path in list
feat: known niehgbour count

* fix: github workflow, post images publically

* build(deps): bump undici from 7.27.2 to 7.28.0 (#9)

Bumps [undici](https://github.com/nodejs/undici) from 7.27.2 to 7.28.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.27.2...v7.28.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 7.28.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: discover renderers, neighbor map lines, live packet flow

- Discover packets decoded: DISCOVER_REQ/DISCOVER_RESP render real fields
  (type-filter roles, tag, node-to-node request SNR) instead of a raw dump.
- Neighbor lines on the map: On/Selected/Off control draws links between nodes
  and their known neighbors.
- Live packet flow: a play button animates packets hop-to-hop between repeaters
  in real time (off by default).

* map: live mode dims nodes, flashes each packet's route

* map: shoot a comet along each packet's route in live mode

* map: rework live mode — packet dot, dashed trail, per-hop flash

* map: fade live-mode clusters more so dense ones stay see-through

* map: persist clustering + node-type, default neighbor lines to selected

* map: fade live-mode node markers more so overlaps stay see-through

* map: fade the live-mode node flash in sync with its packet trail

* nodes: let the mobile detail panel minimize without deselecting

* map: dim other nodes to spotlight a selected node and its neighbours

* map: colour a selected node's neighbour lines by obs count and freshness

* Add a neighbour graph under the renamed Analytics tab

* Focus the neighbour graph into an ego view on click

* Wrap the analytics sub-header so the range stays on-screen on mobile

* Use a dropdown for the analytics sections on mobile

* Gate the neighbour graph to a region and size labels by busyness

* Add a name search that spotlights matches and dims the rest of the mesh

* Add a copy button beside node and observer public keys

* Add a copy link to node and observer detail panels

Deep-linked ?node/?observer selections now survive load: the region
reset watches the raw selection instead of the async-resolved regionKey,
so slug expansion no longer wipes a restored panel.

* Add .env options for hiding tabs, MeshMapper themes, app name and a GitHub link

* Make VITE_ENABLED_THEMES an exclusive theme allowlist

* Add a .env option to skip the load splash

* Keep the packets list static while scrolled down instead of jumping on new packets

* Add Map deep-links for camera and settings with a copy-link button

* Filter packet history on the server when a single type, route or scope is selected

* Show loading feedback while packet history loads

* build(deps): bump echarts from 5.5.1 to 6.1.0 (#14)

* Fix: github workflow + stats tab + add traces vs pings (#10)

* stats: node-types donut, multi-IATA filtering, preset split

 - node-types: new /stats/node-types endpoint + useNodeTypes; donutOption
   replaces the "Coming soon" card with a live donut
 - stats endpoints take iatas[] instead of a single iata, so multi-IATA
   regions filter instead of falling back to all (drops useStatsIata)
 - radio presets keep the node/observer split via stacked presetBarsOption
 - drop client-side telemetry ms-normalization (backend now emits epoch ms
   on both paths); charts pick delta-vs-raw counters off the response interval
 - MeshTab: range-driven charts lead, all-time charts follow; KPI window
   from overview.windowHours instead of a hardcoded 24h
 - ci: docker-publish builds a :dev image on dev-branch pushes

* feat: traces vs pings
feat: SNR for trace path in list
feat: known niehgbour count

* fix: github workflow, post images publically

* build(deps): bump echarts from 5.5.1 to 6.1.0

Bumps [echarts](https://github.com/apache/echarts) from 5.5.1 to 6.1.0.
- [Release notes](https://github.com/apache/echarts/releases)
- [Commits](https://github.com/apache/echarts/compare/5.5.1...6.1.0)

---
updated-dependencies:
- dependency-name: echarts
  dependency-version: 6.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com>
Co-authored-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump version to 1.2.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 23:18:10 -04:00

76 lines
2.8 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { TABS, parseEnvList, filterEnabledTabs, parseEnvBool, selectableThemes } from "../../src/lib/constants";
describe("parseEnvList", () => {
it("returns [] for undefined or empty", () => {
expect(parseEnvList(undefined)).toEqual([]);
expect(parseEnvList("")).toEqual([]);
});
it("splits, trims, and drops empty entries", () => {
expect(parseEnvList("Map,Nodes")).toEqual(["Map", "Nodes"]);
expect(parseEnvList(" Map , Nodes ,, ")).toEqual(["Map", "Nodes"]);
});
});
describe("filterEnabledTabs", () => {
it("returns all tabs (order preserved) when nothing is disabled", () => {
expect(filterEnabledTabs(TABS, undefined)).toEqual([...TABS]);
expect(filterEnabledTabs(TABS, "")).toEqual([...TABS]);
});
it("excludes named tabs case-insensitively", () => {
expect(filterEnabledTabs(TABS, "map,nodes")).toEqual([
"Packets", "Channels", "Observers", "Routes", "Traces", "Analytics",
]);
expect(filterEnabledTabs(TABS, "MAP , NODES")).toEqual([
"Packets", "Channels", "Observers", "Routes", "Traces", "Analytics",
]);
});
it("ignores unknown tab names", () => {
expect(filterEnabledTabs(TABS, "bogus")).toEqual([...TABS]);
});
it("can disable every tab", () => {
expect(filterEnabledTabs(TABS, TABS.join(","))).toEqual([]);
});
});
describe("parseEnvBool", () => {
it("is true for common truthy strings (case-insensitive)", () => {
for (const v of ["1", "true", "TRUE", "Yes", "on", " on "]) expect(parseEnvBool(v)).toBe(true);
});
it("is false for unset, empty, sentinel, or other values", () => {
for (const v of [undefined, "", "0", "false", "no", "__VITE_SKIP_SPLASH__"]) expect(parseEnvBool(v)).toBe(false);
});
});
describe("selectableThemes", () => {
const THEMES = [
{ id: "neutral-blue", name: "Neutral Blue" },
{ id: "teal", name: "Teal" },
{ id: "meshmapper_dark", name: "MeshMapper Dark", hidden: true },
{ id: "meshmapper_light", name: "MeshMapper Light", hidden: true },
];
it("shows all non-hidden themes when no allowlist is set", () => {
expect(selectableThemes(THEMES, new Set()).map((t) => t.id)).toEqual(["neutral-blue", "teal"]);
});
it("shows ONLY the allowlisted themes when set (exclusive), including hidden ones", () => {
const ids = selectableThemes(THEMES, new Set(["meshmapper_dark", "meshmapper_light"])).map((t) => t.id);
expect(ids).toEqual(["meshmapper_dark", "meshmapper_light"]);
});
it("allowlist excludes non-listed defaults", () => {
expect(selectableThemes(THEMES, new Set(["teal"])).map((t) => t.id)).toEqual(["teal"]);
});
it("matches allowlist ids case-insensitively", () => {
const themes = [{ id: "MeshMapper_Dark", hidden: true }];
expect(selectableThemes(themes, new Set(["meshmapper_dark"])).map((t) => t.id)).toEqual(["MeshMapper_Dark"]);
});
});