Files
diagrams-drawio/scripts/src/services/transforms/heatmap.test.ts
T

75 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import type { DiagramIRV2 } from "../../model/diagram-ir.js";
import { applyMetricsHeatmap } from "./heatmap.js";
import { contrastRatio } from "../themes/theme-service.js";
const IR: DiagramIRV2 = {
version: 2,
pages: [{ id: "p", title: "Page", nodes: [
{ id: "low", label: "Low", style: "shape=hexagon;strokeColor=#000000;", geometry: { x: 0, y: 0, width: 100, height: 60 } },
{ id: "high", label: "High", style: "shape=ellipse;", geometry: { x: 200, y: 0, width: 100, height: 60 } },
], edges: [{ id: "flow", source: "low", target: "high", waypoints: [{ x: 150, y: 30 }] }] }],
};
test("metrics heatmap is deterministic with legend metadata and preserves topology", () => {
const request = { metric: "latency_ms", min: 0, max: 100, values: { "p/low": 0, "p/high": 100 } };
const snapshot = structuredClone(IR);
const first = applyMetricsHeatmap(IR, request);
assert.deepEqual(first, applyMetricsHeatmap(IR, request));
assert.deepEqual(IR, snapshot);
assert.deepEqual(first.legend, {
metric: "latency_ms", min: 0, max: 100,
stops: [
{ value: 0, color: "#440154", fontColor: "#FFFFFF" },
{ value: 25, color: "#3B528B", fontColor: "#FFFFFF" },
{ value: 50, color: "#21918C", fontColor: "#1A1A1A" },
{ value: 75, color: "#5EC962", fontColor: "#1A1A1A" },
{ value: 100, color: "#FDE725", fontColor: "#1A1A1A" },
],
});
assert.match(first.diagram.pages[0].nodes[0].style!, /shape=hexagon/);
assert.match(first.diagram.pages[0].nodes[0].style!, /fillColor=#440154/);
assert.match(first.diagram.pages[0].nodes[1].style!, /fillColor=#FDE725/);
assert.deepEqual(first.diagram.pages[0].edges, IR.pages[0].edges);
assert.deepEqual(first.diagram.pages[0].nodes.map(({ id, geometry }) => ({ id, geometry })), IR.pages[0].nodes.map(({ id, geometry }) => ({ id, geometry })));
});
test("metrics heatmap sets readable text for every palette stop", () => {
const result = applyMetricsHeatmap(IR, {
metric: "latency_ms", min: 0, max: 100,
values: { "p/low": 0, "p/high": 100 },
});
for (const stop of result.legend.stops) {
assert.ok(contrastRatio(stop.fontColor, stop.color) >= 4.5, `${stop.fontColor} on ${stop.color}`);
}
assert.match(result.diagram.pages[0].nodes[0].style!, /fontColor=#FFFFFF/);
assert.match(result.diagram.pages[0].nodes[1].style!, /fontColor=#1A1A1A/);
});
test("metrics heatmap switches colors at the legend stop boundaries", () => {
const colorsAt = (low: number, high: number): string[] => applyMetricsHeatmap(IR, {
metric: "latency_ms", min: 0, max: 100, values: { "p/low": low, "p/high": high },
}).diagram.pages[0].nodes.map((node) => /fillColor=(#[0-9A-F]{6})/i.exec(node.style!)?.[1] ?? "");
assert.deepEqual(colorsAt(24.999, 25), ["#440154", "#3B528B"]);
assert.deepEqual(colorsAt(49.999, 50), ["#3B528B", "#21918C"]);
assert.deepEqual(colorsAt(74.999, 75), ["#21918C", "#5EC962"]);
assert.deepEqual(colorsAt(99.999, 100), ["#5EC962", "#FDE725"]);
});
test("metrics heatmap rejects palettes without a readable text color", () => {
assert.throws(() => applyMetricsHeatmap(IR, {
metric: "x", min: 0, max: 1, values: {}, colors: ["#777777", "#777777"],
}), /no readable font color/i);
});
test("metrics heatmap rejects invalid bounds, colors, keys, and out-of-range values", () => {
assert.throws(() => applyMetricsHeatmap(IR, { metric: "x", min: 1, max: 1, values: {} }), /min.*max/i);
assert.throws(() => applyMetricsHeatmap(IR, { metric: "x", min: 0, max: 1, values: { "p/low": 2 } }), /bounded/i);
assert.throws(() => applyMetricsHeatmap(IR, { metric: "x", min: 0, max: 1, values: { missing: 0 } }), /unknown/i);
assert.throws(() => applyMetricsHeatmap(IR, { metric: "x", min: 0, max: 1, values: {}, colors: ["red"] }), /color/i);
});