Files
manage/frontend/src/lib/__tests__/metricFormat.test.ts
T
Developer b7019b33ac feat(widgets): scale chart axes/tooltips with unit + scale options
Consistent graph scaling across every line-chart widget. A new shared
frontend/src/lib/metricFormat.ts picks a decimal prefix (kB/MB/GB, kbps/Mbps,
Gbps, …) from the series magnitude and formats values; LineSeriesChart accepts
unit + scale and formats both the Y-axis ticks and the tooltip with the SAME
prefix (one consistent unit per axis). MetricChartWidget (Prometheus) and
QbittorrentSpeedWidget pass the widget config through; qBit speed defaults to
bytes/sec → MB/s.

WidgetConfigDialog now renders `enum` schema fields as a <Select> dropdown, so
the backend's unit/scale Literal enums become consistent pickers in every graph
widget's config (and any future enum option).

Decimal (x1000) prefixes by default (matches Mbps/MB/s/Grafana).

Tests: 13 new metricFormat tests (auto/fixed scaling, percent, seconds,
nulls, trailing-zero trimming). 179/179 frontend tests pass; tsc + ESLint clean.
2026-07-12 11:46:07 +00:00

82 lines
2.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
metricScaleInfo,
formatScaled,
formatMetricValue,
} from "../metricFormat";
describe("metricScaleInfo", () => {
it("auto-scales bytes/sec by magnitude (MB/s)", () => {
const info = metricScaleInfo(1_500_000, "bytes_per_sec", "auto"); // 1.5 MB/s
expect(info.suffix).toBe("MB/s");
expect(info.divisor).toBe(1_000_000);
});
it("auto-scales bytes/sec to kB/s for smaller magnitudes", () => {
const info = metricScaleInfo(2_500, "bytes_per_sec", "auto");
expect(info.suffix).toBe("kB/s");
expect(info.divisor).toBe(1_000);
});
it("auto-scales bits/sec to Mbps", () => {
const info = metricScaleInfo(5_000_000, "bits_per_sec", "auto");
expect(info.suffix).toBe("Mbps");
});
it("forces a fixed prefix when scale is set", () => {
const info = metricScaleInfo(2_000_000_000, "bytes", "m"); // force MB even though it'd auto-pick GB
expect(info.suffix).toBe("MB");
expect(info.divisor).toBe(1_000_000);
});
it("clamps beyond the largest prefix", () => {
const info = metricScaleInfo(1e30, "bytes", "auto");
expect(info.suffix).toBe("TB");
});
it("raw unit never scales (divisor 1, no suffix)", () => {
const info = metricScaleInfo(9999, "none", "auto");
expect(info.suffix).toBe("");
expect(info.divisor).toBe(1);
});
});
describe("formatScaled", () => {
it("formats bytes/sec with the chosen scale", () => {
const info = metricScaleInfo(1_500_000, "bytes_per_sec", "auto");
expect(formatScaled(1_500_000, info, "bytes_per_sec")).toBe("1.5 MB/s");
});
it("appends % for percent unit regardless of scale", () => {
expect(
formatScaled(42, metricScaleInfo(42, "percent", "auto"), "percent"),
).toBe("42%");
});
it("humanizes seconds", () => {
expect(
formatScaled(90, metricScaleInfo(90, "seconds", "auto"), "seconds"),
).toBe("1.5 min");
});
it("returns em dash for null/NaN", () => {
const info = metricScaleInfo(1, "none", "auto");
expect(formatScaled(null, info, "none")).toBe("—");
expect(formatScaled(NaN, info, "none")).toBe("—");
});
});
describe("formatMetricValue (one-shot)", () => {
it("auto-scales a scalar value", () => {
expect(formatMetricValue(12_500_000, "bytes_per_sec")).toBe("12.5 MB/s");
});
it("formats bits/sec as Gbps for large values", () => {
expect(formatMetricValue(1_000_000_000, "bits_per_sec")).toBe("1 Gbps");
});
it("raw unit keeps the number as-is", () => {
expect(formatMetricValue(1500, "none")).toBe("1500");
});
});