import { render } from "@testing-library/react"; import { CHART_HEIGHT, ChartFrame, bandPaddingInner, bandScale, labelTickValues, plotArea, useMeasuredWidth, valueScale, valueTicks, } from "./chartKit"; describe("useMeasuredWidth", () => { /** * jsdom lays nothing out, so `clientWidth` is 0 there and a chart scaled to it * would draw a zero-width plot. Every chart test depends on this fallback. */ test("an unmeasurable container falls back to 640", () => { function Probe() { const [ref, width] = useMeasuredWidth(); return
; } const { getByTestId } = render(); expect(getByTestId("probe").getAttribute("data-width")).toBe("640"); }); }); describe("valueScale", () => { test("the data maximum is nicened outward and the ticks land on round numbers", () => { const scale = valueScale(1780, [240, 0]); expect(scale.domain()).toEqual([0, 1800]); expect(scale.ticks(5)).toEqual([0, 500, 1000, 1500]); expect(valueTicks(scale)).toEqual([0, 500, 1000, 1500]); }); }); describe("bandPaddingInner", () => { /** * The gap is a constant number of pixels, so the fraction it takes of one slot * has to fall as the slots get wider. 24 hourly columns in a 1388px plot is * the 24h window on a full-width panel. */ test("the fraction is two pixels per column of the plot's width", () => { expect(bandPaddingInner(24, 1388)).toBeCloseTo((2 * 24) / 1388, 12); expect(bandPaddingInner(24, 1388)).toBeCloseTo(0.034582, 6); }); /** * Past the cap the gap would be most of the slot and the columns would vanish * into slivers, so it stops at half the slot and the bars stay visible. */ test("the gap never takes more than half a slot", () => { expect(bandPaddingInner(1000, 100)).toBe(0.5); }); test("no columns and no width mean no gap to compute", () => { expect(bandPaddingInner(0, 640)).toBe(0); expect(bandPaddingInner(24, 0)).toBe(0); }); }); describe("labelTickValues", () => { /** A week of hourly buckets in a panel the width of a laptop window. */ test("labels thin out to one every 21 buckets at 748px of plot", () => { const timestamps = Array.from({ length: 168 }, (_, i) => i * 3600); expect(labelTickValues(timestamps, 748)).toEqual([0, 75600, 151200, 226800, 302400, 378000, 453600, 529200]); }); test("every bucket is labelled when they all fit", () => { expect(labelTickValues([0, 3600, 7200], 748)).toEqual([0, 3600, 7200]); }); }); /** The frame on its own, at the width the charts draw at in jsdom. */ function renderFrame() { const plot = plotArea(640); const timestamps = [0, 3600, 7200]; const yScale = valueScale(100, [plot.bottom, plot.y]); const yTicks = valueTicks(yScale); const { container } = render( , ); return { container, plot, yTicks }; } describe("ChartFrame", () => { test("the value axis is bare: no axis line, no tick marks, labels 6px left of the plot", () => { const { container, plot, yTicks } = renderFrame(); const axis = container.querySelector(".visx-axis-left") as SVGGElement; expect(axis.getAttribute("transform")).toBe(`translate(${plot.x}, 0)`); expect(axis.querySelector("line")).toBeNull(); const labels = Array.from(axis.querySelectorAll("text")); expect(labels.map((label) => label.textContent)).toEqual(yTicks.map(String)); for (const label of labels) { expect(label.getAttribute("x")).toBe("0"); expect(label.getAttribute("dx")).toBe("-6px"); expect(label.getAttribute("text-anchor")).toBe("end"); // The label sits on the tick, centred: visx's own 0.25em nudge is // cancelled so it does not double up with the middle baseline. expect(label.getAttribute("dy")).toBe("0"); expect(label.getAttribute("dominant-baseline")).toBe("middle"); } }); /** * The time axis keeps its baseline and drops its tick marks, and the labels * are placed by the group transform plus `dy` rather than by the font-size * guess visx would otherwise make. */ test("the time axis keeps only its baseline, and its labels sit 16px below it", () => { const { container, plot } = renderFrame(); const axis = container.querySelector(".visx-axis-bottom") as SVGGElement; expect(axis.getAttribute("transform")).toBe(`translate(0, ${plot.bottom})`); const lines = Array.from(axis.querySelectorAll("line")); expect(lines).toHaveLength(1); expect(lines[0].getAttribute("class")).toContain("visx-axis-line"); for (const label of Array.from(axis.querySelectorAll("text"))) { expect(label.getAttribute("y")).toBe("0"); expect(label.getAttribute("dy")).toBe("16px"); expect(label.getAttribute("text-anchor")).toBe("middle"); } }); test("every grid line has a labelled tick on it and no tick floats without a line", () => { const { container } = renderFrame(); const gridY = Array.from(container.querySelectorAll(".visx-rows line")).map((line) => line.getAttribute("y1")); const labelY = Array.from(container.querySelectorAll(".visx-axis-left text")).map((label) => label.getAttribute("y"), ); expect(gridY.length).toBeGreaterThan(0); expect(gridY).toEqual(labelY); }); });