← Files DataARCHIVED FILE
templates/data-app/base/tests/experimental-bar-chart.test.mjs
30.5 KB · Oct 3, 2026 · 06:03 UTC
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { before, after, test } from "node:test";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { createServer } from "vite";
import { barChartSpec, barPresentations, barPresentationIssue, barValue, segmentCalloutsFit, rangePosition } from "../src/charting/bar-family.js";
import { chartDataShape, projectChartSpec } from "../src/charting/chart-data-shape.js";
import { semanticColorResolver } from "../src/charting/chart-theme.js";
let server, Chart, BarFamily, Explorer, availableTypes, BarTooltip, ChartTooltip, ProgressTooltip, Library, Shell, labSnapshot, State, iconNames, DataAppContext;
const originalWindow = globalThis.window, originalLocation = globalThis.location;
before(async () => {
globalThis.location = new URL("../examples/component-lab/index.html", import.meta.url);
globalThis.window = { location: globalThis.location };
server = await createServer({ root: fileURLToPath(new URL("../", import.meta.url)), configFile: false,
appType: "custom", logLevel: "silent", server: { middlewareMode: true } });
Chart = (await server.ssrLoadModule("/src/charting/ChartRenderer.jsx")).ChartRenderer;
BarFamily = (await server.ssrLoadModule("/src/charting/BarFamilyRenderer.jsx")).BarFamilyRenderer;
Explorer = (await server.ssrLoadModule("/src/components/ChartExplorer.jsx")).ChartExplorer;
availableTypes = (await server.ssrLoadModule("/src/components/ChartExplorer.jsx")).availableChartTypes;
BarTooltip = (await server.ssrLoadModule("/src/charting/BarFamilyRenderer.jsx")).BarTooltip;
ChartTooltip = (await server.ssrLoadModule("/src/charting/ChartTooltip.jsx")).ChartTooltip;
ProgressTooltip = (await server.ssrLoadModule("/src/charting/BarFamilyRenderer.jsx")).ProgressTooltip;
Library = await server.ssrLoadModule("/examples/component-lab/Library.jsx");
State = await server.ssrLoadModule("/src/charting/ChartState.jsx");
iconNames = (await server.ssrLoadModule("/src/components/Icon.jsx")).dashboardIconNames;
Shell = (await server.ssrLoadModule("/src/DataAppShell.jsx")).DataAppShell;
DataAppContext = (await server.ssrLoadModule("/src/DataAppContext.jsx")).DataAppContext;
labSnapshot = (await server.ssrLoadModule("/examples/component-lab/data.js")).snapshot;
});
after(async () => { await server?.close(); globalThis.window = originalWindow; globalThis.location = originalLocation; });
const render = (rows, recipe) => renderToStaticMarkup(React.createElement(Chart, { rows, spec: barChartSpec(recipe) }));
test("authored content receives an explicit inactive export signal without changing chart state", () => {
let context;
function Content() {
context = React.useContext(DataAppContext);
return React.createElement("output", null, String(context.chartExportActive));
}
const html = renderToStaticMarkup(React.createElement(Shell, { snapshot: labSnapshot, hosted: true }, React.createElement(Content)));
assert.equal(context.chartExportActive, false);
assert.match(html, /<output>false<\/output>/u);
assert.equal(typeof context.chartProps, "function");
assert.equal(typeof context.componentActions.additionalActions, "function");
assert.deepEqual(context.chartProps("export-contract").visibleSeries, context.chartStates["export-contract"]?.visibleSeries);
assert.deepEqual(context.chartProps("export-contract").zoomRange, context.chartStates["export-contract"]?.zoomRange);
});
test("native wide time series render every configured field beyond the argument-count limit", () => {
// A reviewed dashboard has 615 dates × 992 model series. Keep this shape;
// lowering source rows or dropping fields would conceal the regression.
const fields = Array.from({ length: 992 }, (_, index) => `model-${index}`);
const rows = Array.from({ length: 615 }, (_, index) => ({
day: index,
...Object.fromEntries(fields.map(field => [field, 1])),
}));
rows.at(-1)[fields.at(-1)] = 19;
const spec = { type: "stackedArea", x: "day", y: fields[0], fields,
rightAxisFields: [], groupOther: false, showLegend: false };
const shape = chartDataShape(spec, rows);
assert.deepEqual(shape.fields, fields);
assert.equal(rows.at(-1)[fields.at(-1)], 19);
assert.doesNotThrow(() => renderToStaticMarkup(React.createElement(Chart, { rows, spec })));
});
test("canonical bar-family plots infer orientation from type and preserve explicit overrides", () => {
for (const [type, orientation, expected] of [["horizontalBar",undefined,"vertical"],
["bar",undefined,"horizontal"],["horizontalBar","vertical","horizontal"],["bar","horizontal","vertical"]]) {
let tree;
function Probe() {
tree = BarFamily({rows:[{category:"A",value:3}],spec:{type,x:"category",y:"value",presentation:"plot",barOptions:{orientation}}});
return tree;
}
renderToStaticMarkup(React.createElement(Probe));
const layouts = [];
function visit(element) {
if (!React.isValidElement(element)) return;
if (element.props.layout) layouts.push(element.props.layout);
React.Children.forEach(element.props.children,visit);
}
visit(tree);
assert.deepEqual(layouts,[expected],`${type}, orientation=${orientation}`);
}
});
test("ranked-list selections request detail actions for the clicked reviewed row", () => {
const rows = [{ channel: "Search", value: 3 }, { channel: "Partner", value: 9 }];
const requested = [];
let tree;
function Probe() {
const renderer = Chart({ rows, spec: { type: "rankedList", x: "channel", y: "value" },
getMarkActions: mark => { requested.push(mark); return [{ label: "View records", onSelect() {} }]; } });
// Capture the rendered ranking's callbacks without a browser event harness.
tree = renderer.type(renderer.props);
return tree;
}
renderToStaticMarkup(React.createElement(Probe));
const buttons = [];
function visit(element) {
if (!React.isValidElement(element)) return;
if (element.props.className === "chart-ranked-list-row") buttons.push(element);
React.Children.forEach(element.props.children, visit);
}
visit(tree);
assert.equal(buttons.length, 2);
buttons[0].props.onClick({ nativeEvent: {} });
buttons[1].props.onClick({ nativeEvent: {} });
assert.deepEqual(requested, [{ row: rows[1], field: "value" }, { row: rows[0], field: "value" }]);
});
test("bar recipes round-trip only supported options and preserve every source dependency", () => {
const spec = barChartSpec({ presentation: "bullet", category: "team", value: "actual", target: "goal",
projection: "forecast", range: ["low", "high"], rangeLabelField: "rangeLabel", style: { colorField: "color", sql: "private" },
format: { style: "currency", currency: "USD", maximumFractionDigits: 0 },
markers: [{ value: 50, label: "Target", sql: "private" }], sql: "private", formatValue: () => "unsafe" });
assert.deepEqual(JSON.parse(JSON.stringify(projectChartSpec(spec))), spec);
assert.ok(!JSON.stringify(spec).includes("private") && !JSON.stringify(spec).includes("unsafe"));
const shape = chartDataShape(spec, [{ team: "A", actual: 10, goal: 20, forecast: 15, low: 5, high: 12, rangeLabel: "5–12", color: "red" }]);
for (const field of ["team", "actual", "goal", "forecast", "low", "high"]) assert.ok(shape.requiredRowFields.includes(field));
for (const field of ["color", "rangeLabel"]) assert.ok(shape.optionalRowFields.includes(field));
});
test("compact bar presentations render through Chart with shared mark interactions", () => {
const rows = [{ category: "Core", value: 25, goal: 100, previous: 20, other: 75 }];
for (const [presentation, options, className] of [
["bullet", { target: "goal" }, "bar-family-bullets"],
["segmented", { series: ["value", "other"] }, "bar-family-segmented"],
["groupedList", { series: [{ key: "value" }, { key: "previous" }] }, "bar-family-grouped-list"],
["rankedList", {}, "bar-family-list--rankedList"],
["progress", { track: { max: "goal" } }, "bar-family-list--progress"],
["comparison", {}, "bar-family-list--comparison"],
["rangePosition", { range: ["previous", "goal"] }, "bar-family-range"],
]) {
const html = render(rows, { category: "category", value: "value", presentation, ...options });
assert.ok(html.includes(className), presentation);
assert.match(html, /data-chart-interaction-root/u);
assert.match(html, /data-chart-mark/u);
assert.doesNotMatch(html, /NaN|Infinity|Unsupported/u);
}
});
test("segmented progress preserves attainment and validates segment counts", () => {
const recipe = { presentation: "progress", category: "label", value: "actual", track: {max:"goal"}, style:{segments:10}, detailField:"detail" };
for (const [actual, expected] of [[0,0],[44,4],[46,5],[125,10],[null,0]]) {
const html = render([{label:"Capacity",actual,goal:100,detail:"Reviewed goal"}],recipe);
assert.equal((html.match(/<span data-active="true"/g) ?? []).length, expected);
assert.match(html, actual === null ? /Capacity: —/u : new RegExp(`Capacity: ${actual}%`));
assert.doesNotMatch(html, /NaN|Infinity/u);
}
const spec = barChartSpec(recipe);
assert.match(render([{label:"Capacity",actual:.746,goal:1}],{...recipe,labels:{value:"formatted",position:"summary"},format:{style:"percent",maximumFractionDigits:1}}), /74.6%/u);
assert.deepEqual(projectChartSpec(JSON.parse(JSON.stringify(spec))), spec);
assert.ok(chartDataShape(spec,[{label:"Capacity",actual:44,goal:100,detail:"Reviewed goal"}]).optionalRowFields.includes("detail"));
for (const segments of [0,1,2.5,41]) assert.match(barPresentationIssue([],barChartSpec({...recipe,style:{segments}})),/Segment count/u);
});
test("range positions handle signed, collapsed, unknown and out-of-range evidence without changing values", () => {
for (const [low,high,value,expected] of [[-10,10,0,50],[80,120,104,60],[80,120,135,100],[80,120,60,0],[5,5,5,50],[5,5,6,100],[5,5,4,0],[null,10,5,null],[0,10,null,null],[10,0,5,null]])
assert.equal(rangePosition(low,high,value),expected);
const spec = barChartSpec({presentation:"rangePosition",category:"label",value:"current",range:["low","high"]});
const rows = [{label:"Observed range",low:-10,high:10,current:15}];
assert.equal(barPresentationIssue(rows,spec),null);
assert.match(render(rows,{...spec}), /Observed range: 15; -10 to 10/u);
assert.deepEqual(chartDataShape(spec,rows).requiredRowFields,["label","current","low","high"]);
assert.deepEqual(projectChartSpec(JSON.parse(JSON.stringify(spec))),spec);
assert.match(barPresentationIssue([],barChartSpec({presentation:"rangePosition"})), /low and high/u);
assert.match(barPresentationIssue([{low:10,high:5}],spec),/ordered/u);
const unknown = render([{label:"Unknown",low:0,high:10,current:null}],spec);
assert.doesNotMatch(unknown, /<i style=/u);
});
test("range tooltip and editor retain current value separately from both endpoints", () => {
const html = renderToStaticMarkup(React.createElement(BarTooltip, {row:{label:"Price",current:135,low:80,high:120},spec:{
presentation:"rangePosition",category:"label",value:"current",range:["low","high"],tooltipFields:[],formatCategory:String,formatValue:value=>`$${value}`,
}}));
assert.match(html, /\$135/u);
assert.match(html, /Low<b>\$80/u);
assert.match(html, /High<b>\$120/u);
assert.doesNotMatch(html, /<i |data-comparison/u);
const spec = barChartSpec({presentation:"rangePosition",category:"label",value:"current",range:["low","high"]});
const editor = renderToStaticMarkup(React.createElement(Explorer,{component:{id:"range",chart:spec},rows:[{label:"Price",current:135,low:80,high:120}],onChange:()=>{}}));
for (const field of ["Range start","Range end","Current value"]) assert.ok(editor.includes(field));
});
test("missing values and absent bullet targets are not presented as zero", () => {
assert.equal(barValue({ value: 0 }, "value"), 0);
for (const value of [null, undefined, "", NaN]) assert.equal(barValue({ value }, "value"), null);
const html = render([{ category: "Unknown", value: null }], { category: "category", value: "value", presentation: "bullet" });
assert.match(html, /Unknown: —/u);
assert.doesNotMatch(html, /target 0|<i style=|bullet-projection/u);
});
test("signed data, incomplete compositions, invalid goals and reversed ranges fail honestly", () => {
const recipe = { category: "category", value: "value", presentation: "progress", track: { max: "goal" } };
assert.match(barPresentationIssue([{ value: -1, goal: 5 }], barChartSpec(recipe)), /signed/u);
for (const goal of [0, null, undefined, -1]) assert.match(barPresentationIssue([{ value: 5, goal }], barChartSpec(recipe)), /positive goal/u);
assert.match(barPresentationIssue([{ value: 5, other: null }], barChartSpec({ ...recipe, presentation: "segmented", series: ["value", "other"] })), /complete/u);
assert.match(barPresentationIssue([{ low: 10, high: 5 }], barChartSpec({ category: "category", value: "value", range: ["low", "high"] })), /ordered/u);
assert.equal(barPresentationIssue([{ value: -5 }], barChartSpec({ ...recipe, presentation: "plot" })), null);
});
test("zero-total compositions do not invent percentage shares", () => {
const html = render([{ category: "No activity", a: 0, b: 0 }], { presentation: "segmented", category: "category",
series: ["a", "b"], annotations: true, labels: { primary: "share", position: "below" } });
assert.match(html, /—/u);
assert.doesNotMatch(html, /0% of total|NaN|Infinity/u);
});
test("source projection retains all grouped measures and presentation metadata", () => {
const spec = barChartSpec({ category: "category", presentation: "groupedList",
series: [{ key: "actual", label: "Current", color: "green" }, { key: "previous", label: "Previous", color: "gray" }] });
const projected = projectChartSpec(spec);
assert.deepEqual(projected.barOptions.series, spec.barOptions.series);
assert.deepEqual(chartDataShape(projected, [{ category: "A", actual: 2, previous: 1 }]).requiredRowFields,
["category", "actual", "previous"]);
});
test("specialized chart edits expose supported mappings only and respect read-only viewers", () => {
const chart = barChartSpec({ presentation: "bullet", category: "team", value: "actual", target: "goal" });
const props = { component: { id: "example", chart }, rows: [{ team: "A", actual: 5, goal: 10 }] };
const readOnly = renderToStaticMarkup(React.createElement(Explorer, { ...props, canEdit: false }));
assert.doesNotMatch(readOnly, /explorer-controls/u);
const editable = renderToStaticMarkup(React.createElement(Explorer, { ...props, canEdit: true }));
assert.match(editable, /Reviewed data settings/u);
assert.match(editable, />Target</u);
assert.doesNotMatch(editable, /Chart type|Split series by|Show legend/u);
});
test("annotated chart editors render the shared visibility switch in either state", () => {
for (const type of ["line", "bar"]) for (const showAnnotations of [true, false]) {
const chart = { type, x: "date", y: "value", showAnnotations,
annotations: [{ id: "release", kind: "event", at: "2026-07-20", label: "Release" }] };
const html = renderToStaticMarkup(React.createElement(Explorer, {
component: { id: "annotated", chart }, rows: [{ date: "2026-07-20", value: 10 }],
}));
assert.match(html, new RegExp(`role="switch" aria-label="Show annotations" aria-checked="${showAnnotations}"`));
}
});
test("goal and projection tooltips distinguish reference roles from observed values", () => {
const html = renderToStaticMarkup(React.createElement(BarTooltip, {
row: { name: "Steps", actual: 8000, goal: 10000, projected: 9500 },
spec: { category: "name", value: "actual", target: "goal", projection: "projected",
series: [], tooltipFields: [], style: { color: "green" }, formatCategory: String, formatValue: String },
}));
assert.match(html, /data-comparison="current"[^>]*background:green/u);
assert.match(html, /data-comparison="target"[^>]*background:var\(--secondary\)/u);
assert.match(html, /data-comparison="projection"[^>]*background:var\(--secondary\)/u);
assert.match(html, /Actual/u);
assert.match(html, /Target/u);
assert.match(html, /Projected/u);
});
test("public-component inventory exposes controls and unique chart identities", () => {
const inventory = renderToStaticMarkup(React.createElement(Shell, { snapshot: labSnapshot, hosted: true }, React.createElement(Library.LibraryInventory)));
for (const name of ["Chart", "DataTable", "DateRangePicker", "RangeSlider", "ExecutiveSummary", "SourceInspector"])
assert.ok(inventory.includes(`>${name}</button>`), name);
assert.match(inventory, /Find a component/u);
assert.match(inventory, /Minimum card width/u);
assert.match(inventory, /Aliases/u);
const ids = [...inventory.matchAll(/data-component-id="(library-chart-[^"]+)"/gu)].map(match => match[1]);
assert.equal(new Set(ids).size, ids.length, "simultaneous variants retain separate source/edit identities");
for (const presentation of barPresentations) assert.ok(ids.includes(`library-chart-bar-${presentation}`), presentation);
for (const type of Library.libraryChartTypes) assert.ok(ids.includes(`library-chart-${type}-standard`), type);
});
test("non-additive series cannot acquire stacked transformations when editing", () => {
const chart = projectChartSpec({ type: "bar", x: "month", y: "users", series: "platform", stackable: false });
const choices = availableTypes(chart);
assert.ok(choices.includes("bar") && choices.includes("line"));
assert.ok(!choices.some(type => /stacked/iu.test(type)));
assert.ok(availableTypes({ ...chart, stackable: true }).some(type => /stacked/iu.test(type)));
});
test("review category colors remain distinct and stable when categories are filtered", () => {
const resolve = semanticColorResolver(labSnapshot.queries);
const rows = labSnapshot.queries.category_performance.rows;
const colors = rows.map(row => resolve({ dimension: "segment", value: row.segment }));
assert.equal(new Set(colors).size, rows.length);
for (const row of rows.slice(2)) assert.equal(resolve({ dimension: "segment", value: row.segment }), colors[rows.indexOf(row)]);
});
test("skeletons preserve category counts and heatmap dimensions without copying values", () => {
const rows = [{day:"Mon",period:"AM",value:9182},{day:"Tue",period:"AM",value:9182},{day:"Mon",period:"PM",value:9182}];
const renderSkeleton = chart => renderToStaticMarkup(React.createElement(State.ComponentSkeleton, { chart, rows }));
assert.equal((renderSkeleton({type:"bar"}).match(/<i /g) ?? []).length, 3);
const heatmap = renderSkeleton({type:"heatmap",x:"day",series:"period"});
assert.match(heatmap, /--skeleton-columns:2;--skeleton-rows:2/u);
assert.equal((heatmap.match(/<i /g) ?? []).length, 4);
assert.doesNotMatch(heatmap, /9182/u);
});
test("date picker is the real bounded control with disabled and empty states", async () => {
const {DateRangePicker} = await server.ssrLoadModule("/src/data-app-public.jsx");
const renderPicker = props => renderToStaticMarkup(React.createElement(DateRangePicker, props));
const loaded = renderPicker({choices:["2026-08-28","invalid","2026-02-30","2026-08-01"]});
assert.match(loaded, /Latest 28 days|Last 28 days/u);
assert.doesNotMatch(loaded, /invalid|Feb/u);
assert.match(renderPicker({choices:[]}), /disabled=""[^>]*aria-label="Date range"/u);
assert.match(renderPicker({choices:["2026-08-01"],disabled:true}), /disabled=""/u);
});
test("empty, loading and error chart states are distinct and cover every chart family", () => {
const empty = renderToStaticMarkup(React.createElement(Chart, { rows: [], spec: {type:"line",x:"date",y:"value"}, height:280 }));
assert.match(empty, /No data to display/u);
assert.match(empty, /min-height:280px/u);
assert.doesNotMatch(empty, /recharts-cartesian|data-skeleton-family/u);
for (const [type, family] of [["line","trend"],["pie","pie"],["heatmap","heatmap"],["scatter","scatter"],["sankey","sankey"],["funnel","funnel"],["boxPlot","boxPlot"],["horizontalBar","list"],["bar","bar"]]) {
const html = renderToStaticMarkup(React.createElement(State.ComponentSkeleton, { chart:{type} }));
assert.ok(html.includes(`data-skeleton-family="${family}"`), type);
assert.doesNotMatch(html, /Loading|Updating/u);
}
for (const [presentation, family] of [["segmented","composition"],["bullet","list"],["progress","list"]])
assert.equal(State.chartSkeletonFamily({type:"bar",presentation}), family);
const unavailable = renderToStaticMarkup(React.createElement(State.ComponentState, {error:true,height:280}));
assert.match(unavailable, /Couldn’t load this chart/u);
assert.doesNotMatch(unavailable, /<button/u);
const retryable = renderToStaticMarkup(React.createElement(State.ComponentState, {error:true,onRetry:()=>{}}));
assert.match(retryable, /Try again<\/button>/u);
});
test("malformed chart bindings show a scoped diagnostic instead of blank marks or a report crash", () => {
const spec = { type: "line", x: "week", y: ["siteA", "siteB"] };
const rows = [{ week: "2026-08-03", siteA: 18, siteB: 12 }];
for (const currentRows of [rows, []]) {
const html = renderToStaticMarkup(React.createElement(Chart, {
chartId: "throughput", spec, rows: currentRows, height: 280,
}));
assert.match(html, /role="alert"[^>]*data-chart-config-error="true"[^>]*data-chart-id="throughput"/u);
assert.match(html, /chart\.y must be a single field name, not an array/u);
assert.match(html, /y: "siteA", fields: \["siteA","siteB"\]/u);
assert.doesNotMatch(html, /No data to display|recharts-cartesian/u);
}
for (const currentRows of [[], rows, [{ week: "2026-08-03", siteA: 0, siteB: null }]]) {
const html = renderToStaticMarkup(React.createElement(Chart, {
spec: { ...spec, y: "siteA", fields: ["siteA", "siteB"] }, rows: currentRows,
}));
assert.doesNotMatch(html, /data-chart-config-error/u);
}
});
test("control stories are isolated and the icon story discovers the bundled assets", () => {
for (const name of ["Dropdown","Filters","InlineFilters","SegmentedControl"]) {
const html = renderToStaticMarkup(React.createElement(Shell, {snapshot:labSnapshot,hosted:true}, React.createElement(Library.LibraryStory, {name})));
assert.doesNotMatch(html, /<table|Search data|Weekly:|role="switch"/u, name);
}
const icons = renderToStaticMarkup(React.createElement(Shell, {snapshot:labSnapshot,hosted:true}, React.createElement(Library.LibraryStory, {name:"Icon"})));
for (const name of iconNames) assert.ok(icons.includes(`data-dashboard-icon="${name}"`),name);
assert.match(icons, /Find an icon/u);
});
test("progress tooltips preserve units, missing values and uncapped attainment without a swatch grid", () => {
for (const [actual, goal, expected] of [[7.7, 8, "96%"], [0, 8, "0%"], [10, 8, "125%"], [null, 8, "—"], [7.7, 0, "—"]]) {
const html = renderToStaticMarkup(React.createElement(ProgressTooltip, { label: "Sleep", actual, goal, unit: "hours" }));
assert.ok(html.includes(expected));
assert.match(html, /chart-tooltip--plain/u);
assert.match(html, /chart-tooltip-summary/u);
assert.match(html, /hours/u);
assert.doesNotMatch(html, /<i |data-comparison=/u);
}
const spec = barChartSpec({ presentation: "progress", category: "name", value: "actual", track: { max: "goal" }, unitField: "unit" });
assert.equal(projectChartSpec(spec).barOptions.unitField, "unit");
assert.ok(chartDataShape(spec, [{ name: "Sleep", actual: 7.7, goal: 8, unit: "hours" }]).optionalRowFields.includes("unit"));
});
test("adaptive pipeline annotations preserve both values and full stage names", () => {
for (const annotations of ["auto", "aligned"]) {
const html = render([{ period: "Current", discovery: 8, closed: 2 }, { period: "Prior", discovery: 6, closed: 4 }], {
presentation: "segmented", category: "period", comparison: true, annotations,
series: [{ key: "discovery", label: "Discovery" }, { key: "closed", label: "Closed won" }],
});
// Before measurement, use the readable table rather than flash overlapping callouts.
assert.match(html, /data-callouts-fit="false"/u);
assert.match(html, /bar-family-segment-callouts/u);
assert.match(html, /bar-family-comparison-table/u);
assert.match(html, /<th scope="row">Closed won<\/th><td>2<\/td><td>4<\/td>/u);
}
});
test("aligned annotations require every current and comparison column to fit", () => {
const roomy = { width: 160, labelWidth: 90, valueWidth: 120 };
assert.equal(segmentCalloutsFit([roomy, roomy]), true);
assert.equal(segmentCalloutsFit([roomy, { ...roomy, width: 72 }]), false);
assert.equal(segmentCalloutsFit([{ ...roomy, labelWidth: 190 }, roomy]), false);
assert.equal(segmentCalloutsFit([{ ...roomy, valueWidth: 190 }, roomy]), false);
assert.equal(segmentCalloutsFit([{ width: 0, labelWidth: 0, valueWidth: 0 }]), false);
assert.equal(segmentCalloutsFit([]), false);
});
test("every public library component mounts in each declared state using the real shell", () => {
for (const name of Library.componentNames) for (const state of Library.libraryStates(name)) {
const html = renderToStaticMarkup(React.createElement(Shell, { snapshot: labSnapshot, hosted: true },
React.createElement(Library.LibraryStory, { name, state })));
assert.doesNotMatch(html, /No interactive case is registered/u, `${name}: ${state}`);
if (state === "Loading") assert.match(html, /aria-busy="true"/u, name);
// Closed portaled menu contents are not emitted by server rendering; browser coverage remains required there.
if (state === "Disabled" && !["Menu", "MenuItem"].includes(name)) assert.match(html, /disabled|data-disabled/u, name);
}
});
test("library chart recipes cover the actual editor types and all promoted bar presentations", () => {
for (const type of Library.libraryChartTypes) {
const example = Library.libraryChartCase(type);
assert.ok(labSnapshot.queries[example.queryId].rows.some(row => example.spec.y in row), type);
const html = renderToStaticMarkup(React.createElement(Shell, { snapshot: labSnapshot, hosted: true },
React.createElement(Library.LibraryStory, { name: "Chart", chartType: type })));
assert.doesNotMatch(html, /Unsupported chart|NaN|Infinity/u, type);
}
for (const presentation of barPresentations) {
assert.ok(Library.libraryChartChoices.includes(`bar:${presentation}`), `${presentation} is directly discoverable`);
const html = renderToStaticMarkup(React.createElement(Shell, { snapshot: labSnapshot, hosted: true },
React.createElement(Library.LibraryStory, { name: "Chart", presentation })));
assert.doesNotMatch(html, /Unsupported|requires|NaN|Infinity/u, presentation);
}
});
test("retired adapters are absent and all three funnel specimens remain in the shared library", () => {
for (const name of ["ExperimentalBarChart","Toggle"]) assert.ok(!Library.componentNames.includes(name));
for (const id of ["sales","market","activation"]) {
const rows = labSnapshot.queries[`funnel_${id}`].rows;
for (let index=0;index<rows.length;index++) {
assert.ok(rows[index].count >= 0);
if (index) assert.ok(rows[index].count <= rows[index-1].count);
}
const html = renderToStaticMarkup(React.createElement(Shell, {snapshot:labSnapshot,hosted:true},
React.createElement(Library.LibraryStory,{name:"Chart",chartType:"funnel",options:{funnelExample:id}})));
assert.doesNotMatch(html, /Unsupported|NaN|Infinity/u);
}
});
test("cohort-age headings and gradient-series swatches retain readable meaning", () => {
const html = renderToStaticMarkup(React.createElement(ChartTooltip, {
active:true, label:4, xLabel:"Weeks since activation", xField:"week",
payload:[{dataKey:"revenue",name:"Revenue",value:258441,color:"url(#gradient)"}],
resolveColor:()=>"#0057ff",
}));
assert.match(html, /<strong>Week 4<\/strong>/u);
assert.match(html, /background:#0057ff/u);
assert.doesNotMatch(html, /url\(#gradient\)/u);
});
test("pie hover retains category identity and explicit monetary units without an axis label", () => {
const html = renderToStaticMarkup(React.createElement(ChartTooltip, {
active:true, mode:"pie", xField:"category", payload:[{dataKey:"amount",name:"Amount",value:45320,payload:{category:"Stocks",amount:45320}}],
formatValue: value => new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(value),
}));
assert.match(html, /<strong>Stocks<\/strong>/u);
assert.match(html, /\$45,320/u);
});
test("range indicators retain style choices without fabricating unavailable positions", () => {
const html = render([{label:"Current",low:0,high:100,current:40}], {
presentation:"rangePosition",category:"label",value:"current",range:["low","high"],style:{color:"var(--chart-2)",fontSize:12,gap:8},
});
assert.match(html, /--bar-family-range-color:var\(--chart-2\)/u);
assert.match(html, /--bar-family-font-size:12px/u);
assert.match(html, /40%/u);
});
test("thin grouped bars put exact values outside their clipped tracks", () => {
const html = render([{ team: "Core", actual: 84, target: 76 }, { team: "Other", actual: 0, target: 2 }], {
presentation: "groupedList", category: "team", value: "actual", style: { thickness: 10 },
series: [{ key: "actual", label: "Actual" }, { key: "target", label: "Target" }],
});
assert.equal((html.match(/class="bar-family-grouped-value"/g) ?? []).length, 4);
assert.match(html, /<\/span><strong class="bar-family-grouped-value">84/u);
assert.match(html, /aria-label="Chart legend"/u);
});
test("inventory comparison does not claim an undrawn target", () => {
const html = renderToStaticMarkup(React.createElement(Shell, { snapshot: labSnapshot, hosted: true }, React.createElement(Library.LibraryStory, { name: "Chart", state: "Default", chartType: "bar", presentation: "comparison", options: {}, onEvent: () => {} })));
assert.match(html, /bar-family-list--comparison/u);
assert.doesNotMatch(html, /data-track="true"/u);
assert.equal((html.match(/class="bar-family-list-row"/g) ?? []).length, 2);
});
test("heatmap tooltip uses authored dimension labels once without losing values or maturity", () => {
const render = unknown => renderToStaticMarkup(React.createElement(ChartTooltip, {
active: true, mode: "heatmap", xField: "weekLabel", yField: "retention", groupField: "cohortShortLabel",
detailFields: [{ field: "cohortShortLabel", label: "Cohort" }],
payload: [{ payload: { weekLabel: "Week 2", cohortShortLabel: "Jun 15", retention: .287, __unknown: unknown } }],
formatValue: value => `${(value * 100).toFixed(1)}%`,
}));
const html = render(false);
assert.match(html, /Cohort<b>Jun 15<\/b>/u);
assert.equal((html.match(/Jun 15/g) ?? []).length, 1);
assert.doesNotMatch(html, /Short Label/u);
assert.match(html, /28.7%/u);
assert.match(render(true), /Not yet observed/u);
});
SHA-256: f1000e5ab8c89d1b5010c467430664638763fb9a80f2961957f984b7cf857c3e