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);
});
});