94 lines
3.8 KiB
TypeScript
94 lines
3.8 KiB
TypeScript
import type { Bucket } from "@/lib/types";
|
|
import { MARGIN, isEmptyTimeseries, layoutTimeseries, niceTicks } from "./chartLayout";
|
|
|
|
function bucket(ts: number, queries: number, blocked = 0, cached = 0): Bucket {
|
|
return { ts, queries, blocked, cached };
|
|
}
|
|
|
|
describe("niceTicks", () => {
|
|
test("zero max yields a single zero tick", () => {
|
|
expect(niceTicks(0)).toEqual([0]);
|
|
});
|
|
|
|
test("picks a 1/2/5 step and extends past max", () => {
|
|
expect(niceTicks(7)).toEqual([0, 2, 4, 6, 8]);
|
|
expect(niceTicks(100)).toEqual([0, 50, 100]);
|
|
expect(niceTicks(1234)).toEqual([0, 500, 1000, 1500]);
|
|
});
|
|
});
|
|
|
|
describe("isEmptyTimeseries", () => {
|
|
test("true for no buckets and for all-zero buckets", () => {
|
|
expect(isEmptyTimeseries([])).toBe(true);
|
|
expect(isEmptyTimeseries([bucket(0, 0), bucket(60, 0)])).toBe(true);
|
|
});
|
|
|
|
test("false when any bucket has queries", () => {
|
|
expect(isEmptyTimeseries([bucket(0, 0), bucket(60, 3)])).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("layoutTimeseries", () => {
|
|
test("segment heights are proportional and stack to the queries total", () => {
|
|
const layout = layoutTimeseries([bucket(0, 100, 40, 10), bucket(60, 50, 0, 0)], 480, 240);
|
|
const plotHeight = 240 - MARGIN.top - MARGIN.bottom;
|
|
const baseline = MARGIN.top + plotHeight;
|
|
const [first, second] = layout.bars;
|
|
|
|
expect(layout.scaleMax).toBe(100);
|
|
expect(first.other).toBe(50);
|
|
expect(first.segments.blocked.height).toBeCloseTo(plotHeight * 0.4);
|
|
expect(first.segments.cached.height).toBeCloseTo(plotHeight * 0.1);
|
|
expect(first.segments.other.height).toBeCloseTo(plotHeight * 0.5);
|
|
expect(first.segments.blocked.y + first.segments.blocked.height).toBeCloseTo(baseline);
|
|
expect(first.segments.cached.y + first.segments.cached.height).toBeCloseTo(first.segments.blocked.y);
|
|
expect(first.segments.other.y + first.segments.other.height).toBeCloseTo(first.segments.cached.y);
|
|
expect(first.segments.other.y).toBeCloseTo(MARGIN.top);
|
|
expect(second.segments.other.height).toBeCloseTo(plotHeight * 0.5);
|
|
});
|
|
|
|
test("clamps other at zero when blocked + cached exceed queries", () => {
|
|
const layout = layoutTimeseries([bucket(0, 10, 8, 5)], 480, 240);
|
|
expect(layout.bars[0].other).toBe(0);
|
|
expect(layout.bars[0].segments.other.height).toBe(0);
|
|
});
|
|
|
|
test("zero data still lays out zero-height bars on a unit scale", () => {
|
|
const layout = layoutTimeseries([bucket(0, 0), bucket(60, 0)], 480, 240);
|
|
expect(layout.scaleMax).toBe(1);
|
|
expect(layout.bars).toHaveLength(2);
|
|
for (const bar of layout.bars) {
|
|
expect(bar.segments.blocked.height).toBe(0);
|
|
expect(bar.segments.cached.height).toBe(0);
|
|
expect(bar.segments.other.height).toBe(0);
|
|
}
|
|
expect(layout.yTicks).toEqual([{ value: 0, y: MARGIN.top + (240 - MARGIN.top - MARGIN.bottom) }]);
|
|
});
|
|
|
|
test("single bucket fills the plot width minus the gap", () => {
|
|
const layout = layoutTimeseries([bucket(0, 5, 1, 1)], 480, 240);
|
|
const plotWidth = 480 - MARGIN.left - MARGIN.right;
|
|
const bar = layout.bars[0];
|
|
expect(bar.slot.width).toBeCloseTo(plotWidth);
|
|
expect(bar.segments.blocked.width).toBeCloseTo(plotWidth - 2);
|
|
expect(bar.segments.blocked.x).toBeCloseTo(MARGIN.left + 1);
|
|
expect(layout.xTicks).toEqual([{ ts: 0, x: MARGIN.left + plotWidth / 2 }]);
|
|
});
|
|
|
|
test("x ticks thin out when buckets outnumber the label budget", () => {
|
|
const buckets = Array.from({ length: 168 }, (_, i) => bucket(i * 3600, i));
|
|
const layout = layoutTimeseries(buckets, 800, 240);
|
|
expect(layout.xTicks.length).toBeLessThan(buckets.length / 10);
|
|
expect(layout.xTicks[0].ts).toBe(0);
|
|
const xs = layout.xTicks.map((tick) => tick.x);
|
|
expect([...xs].sort((a, b) => a - b)).toEqual(xs);
|
|
});
|
|
|
|
test("empty bucket list yields no bars and no x ticks", () => {
|
|
const layout = layoutTimeseries([], 480, 240);
|
|
expect(layout.bars).toEqual([]);
|
|
expect(layout.xTicks).toEqual([]);
|
|
expect(layout.scaleMax).toBe(1);
|
|
});
|
|
});
|