Files
MeshChatX/tests/frontend/MapInternalHelpers.test.js

216 lines
9.1 KiB
JavaScript

import { describe, it, expect } from "vitest";
import { extentDiagonal, buildClusterItems, getFeatureCoord } from "@/components/map/internal/clusterUtils.js";
import { getDiscoveredIconName } from "@/components/map/internal/discoveredIcons.js";
import { dedupeDiscoveredMapNodes, dedupeTelemetryMarkersForMap } from "@/components/map/internal/mapDedupe.js";
function feature(props, coord) {
const geom = coord ? { getCoordinates: () => coord } : null;
return {
get: (key) => props[key],
set: (key, value) => {
props[key] = value;
},
getGeometry: () => geom,
};
}
describe("clusterUtils.extentDiagonal", () => {
it("returns 0 for invalid extents", () => {
expect(extentDiagonal(null)).toBe(0);
expect(extentDiagonal(undefined)).toBe(0);
expect(extentDiagonal([])).toBe(0);
expect(extentDiagonal([1, 2, 3])).toBe(0);
expect(extentDiagonal([Infinity, Infinity, -Infinity, -Infinity])).toBe(0);
});
it("returns the diagonal length for valid extents", () => {
expect(extentDiagonal([0, 0, 3, 4])).toBeCloseTo(5);
expect(extentDiagonal([10, 10, 10, 10])).toBe(0);
expect(extentDiagonal([-2, -2, 2, 2])).toBeCloseTo(Math.sqrt(32));
});
});
describe("clusterUtils.getFeatureCoord", () => {
it("returns the originalCoord when set", () => {
const f = feature({ originalCoord: [11, 22] }, [99, 99]);
expect(getFeatureCoord(f)).toEqual([11, 22]);
});
it("falls back to the geometry's coordinates", () => {
const f = feature({}, [33, 44]);
expect(getFeatureCoord(f)).toEqual([33, 44]);
});
it("returns null for missing feature or geometry", () => {
expect(getFeatureCoord(null)).toBeNull();
expect(getFeatureCoord(feature({}, null))).toBeNull();
});
});
describe("clusterUtils.buildClusterItems", () => {
it("returns an empty array for missing features or empty clusters", () => {
expect(buildClusterItems(null)).toEqual([]);
expect(buildClusterItems(feature({ clusterItems: [] }, [0, 0]))).toEqual([]);
});
it("normalises telemetry, discovered, and unknown items", () => {
const items = [
feature(
{
telemetry: { destination_hash: "deadbeefcafe1234" },
peer: { display_name: "Alice" },
originalCoord: [10, 20],
},
[10, 20]
),
feature(
{
discovered: { name: "RNode", interface: "AutoIf", type: "AutoInterface" },
originalCoord: [11, 21],
},
[11, 21]
),
feature({ originalCoord: [12, 22] }, [12, 22]),
];
const cluster = feature({ clusterItems: items }, [11, 21]);
const out = buildClusterItems(cluster);
expect(out).toHaveLength(3);
expect(out[0]).toMatchObject({ kind: "telemetry", label: "Alice", identifier: "deadbeefcafe1234" });
expect(out[1]).toMatchObject({ kind: "discovered", label: "RNode", identifier: "AutoIf" });
expect(out[2]).toMatchObject({ kind: "unknown", label: "Unknown" });
});
it("falls back to a truncated destination hash when no peer name is available", () => {
const items = [feature({ telemetry: { destination_hash: "abcdef0123456789" } }, [0, 0])];
const cluster = feature({ clusterItems: items }, [0, 0]);
expect(buildClusterItems(cluster)[0].label).toBe("abcdef01");
});
it("ignores nullish entries inside the cluster", () => {
const cluster = feature(
{ clusterItems: [null, feature({ telemetry: { destination_hash: "x" } }, [0, 0])] },
[0, 0]
);
expect(buildClusterItems(cluster)).toHaveLength(1);
});
});
describe("discoveredIcons.getDiscoveredIconName", () => {
it("falls back to a generic icon for null/empty input", () => {
expect(getDiscoveredIconName(null)).toBe("map-marker-radius");
expect(getDiscoveredIconName(undefined)).toBe("map-marker-radius");
expect(getDiscoveredIconName({})).toBe("server-network");
});
it("maps known interface types to their MDI names", () => {
expect(getDiscoveredIconName({ type: "AutoInterface" })).toBe("home-automation");
expect(getDiscoveredIconName({ type: "RNodeMultiInterface" })).toBe("access-point-network");
expect(getDiscoveredIconName({ type: "TCPClientInterface" })).toBe("lan-connect");
expect(getDiscoveredIconName({ type: "BackboneInterface" })).toBe("lan-connect");
expect(getDiscoveredIconName({ type: "TCPServerInterface" })).toBe("lan");
expect(getDiscoveredIconName({ type: "UDPInterface" })).toBe("wan");
expect(getDiscoveredIconName({ type: "SerialInterface" })).toBe("usb-port");
expect(getDiscoveredIconName({ type: "KISSInterface" })).toBe("antenna");
expect(getDiscoveredIconName({ type: "AX25KISSInterface" })).toBe("antenna");
expect(getDiscoveredIconName({ type: "I2PInterface" })).toBe("eye");
expect(getDiscoveredIconName({ type: "PipeInterface" })).toBe("pipe");
});
it("differentiates serial vs TCP for RNodeInterface based on port", () => {
expect(getDiscoveredIconName({ type: "RNodeInterface" })).toBe("radio-tower");
expect(getDiscoveredIconName({ type: "RNodeInterface", port: "/dev/ttyUSB0" })).toBe("radio-tower");
expect(getDiscoveredIconName({ type: "RNodeInterface", port: "tcp://10.0.0.1:4242" })).toBe("lan-connect");
});
it("uses interface_type as a fallback when type is missing", () => {
expect(getDiscoveredIconName({ interface_type: "AutoInterface" })).toBe("home-automation");
});
});
describe("mapDedupe.dedupeDiscoveredMapNodes", () => {
it("returns an empty array for invalid input", () => {
expect(dedupeDiscoveredMapNodes(null)).toEqual([]);
expect(dedupeDiscoveredMapNodes(undefined)).toEqual([]);
expect(dedupeDiscoveredMapNodes([])).toEqual([]);
});
it("preserves nameless entries even at the same location", () => {
const nodes = [
{ name: "", latitude: 1, longitude: 1, last_heard: 100 },
{ name: "", latitude: 1, longitude: 1, last_heard: 200 },
];
expect(dedupeDiscoveredMapNodes(nodes)).toHaveLength(2);
});
it("collapses same-name nearby duplicates and keeps the freshest", () => {
const nodes = [
{ name: "Alpha", latitude: 1, longitude: 1, last_heard: 100 },
{ name: "alpha", latitude: 1.001, longitude: 1.001, last_heard: 500 },
{ name: "Beta", latitude: 5, longitude: 5, last_heard: 300 },
];
const out = dedupeDiscoveredMapNodes(nodes);
expect(out).toHaveLength(2);
const alpha = out.find((n) => n.name.toLowerCase() === "alpha");
expect(alpha.last_heard).toBe(500);
});
it("keeps same-name nodes that are far apart", () => {
const nodes = [
{ name: "Hub", latitude: 0, longitude: 0, last_heard: 100 },
{ name: "Hub", latitude: 10, longitude: 10, last_heard: 200 },
];
expect(dedupeDiscoveredMapNodes(nodes)).toHaveLength(2);
});
});
describe("mapDedupe.dedupeTelemetryMarkersForMap", () => {
const peers = {
AAA: { display_name: "Alice" },
BBB: { display_name: "Alice" },
CCC: { display_name: "Bob" },
};
function entry(hash, lat, lon, ts) {
return {
destination_hash: hash,
updated_at: new Date(ts * 1000).toISOString(),
telemetry: { location: { latitude: lat, longitude: lon } },
};
}
it("returns an empty array for invalid input", () => {
expect(dedupeTelemetryMarkersForMap(null)).toEqual([]);
expect(dedupeTelemetryMarkersForMap(undefined)).toEqual([]);
expect(dedupeTelemetryMarkersForMap([])).toEqual([]);
});
it("collapses peers with the same display name in the same place", () => {
const list = [entry("AAA", 1, 1, 100), entry("BBB", 1.001, 1.001, 200), entry("CCC", 1, 1, 50)];
const out = dedupeTelemetryMarkersForMap(list, peers);
expect(out).toHaveLength(2);
expect(out[0].destination_hash).toBe("BBB");
});
it("keeps same-name peers that are far apart", () => {
const list = [entry("AAA", 0, 0, 100), entry("BBB", 10, 10, 200)];
expect(dedupeTelemetryMarkersForMap(list, peers)).toHaveLength(2);
});
it("falls back to a truncated hash when no peer name is registered", () => {
const list = [entry("ffffff0011", 0, 0, 100), entry("ffffff0011", 0.001, 0.001, 200)];
const out = dedupeTelemetryMarkersForMap(list, {});
expect(out).toHaveLength(1);
expect(out[0].updated_at).toBe(list[1].updated_at);
});
it("preserves entries whose location is missing from comparison", () => {
const list = [entry("AAA", 0, 0, 100), { destination_hash: "AAA", updated_at: list1Time(200) }];
const out = dedupeTelemetryMarkersForMap(list, peers);
expect(out).toHaveLength(2);
});
});
function list1Time(ts) {
return new Date(ts * 1000).toISOString();
}