308 lines
7.9 KiB
TypeScript
308 lines
7.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { createRequire } from "node:module";
|
|
import test from "node:test";
|
|
|
|
const browser_globals_apexcharts_reads_when_it_loads = {
|
|
document: { body: null },
|
|
add_init_fn: () => {},
|
|
};
|
|
Object.assign(globalThis, browser_globals_apexcharts_reads_when_it_loads);
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const {
|
|
align_series,
|
|
align_series_for,
|
|
merged_x_values,
|
|
} = require("../../sqlpage/apexcharts.js");
|
|
|
|
const ADDS_NOTHING_TO_THE_STACK = 0;
|
|
const LEAVES_A_GAP = null;
|
|
const STACKED = true;
|
|
const UNSTACKED = false;
|
|
|
|
type XValue = number | string | Date;
|
|
type Point = { x: XValue; y: number | string | null | number[]; z?: number };
|
|
type Series = { name: string; data: Point[] };
|
|
|
|
const series = (name: string, ...data: Point[]): Series => ({ name, data });
|
|
const xs = (s: Series) => s.data.map((p) => p.x);
|
|
|
|
test("merged_x_values keeps the order the series agree on", () => {
|
|
const merged = merged_x_values([
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }, { x: "Q3", y: 3 }),
|
|
series("b", { x: "Q1", y: 4 }, { x: "Q2", y: 5 }, { x: "Q3", y: 6 }),
|
|
]);
|
|
|
|
assert.deepEqual(merged, ["Q1", "Q2", "Q3"]);
|
|
});
|
|
|
|
test("merged_x_values orders by name the x values the series do not share (#951)", () => {
|
|
const merged = merged_x_values([
|
|
series("a", { x: "X2", y: 10 }, { x: "X3", y: 30 }),
|
|
series("b", { x: "X1", y: 25 }, { x: "X2", y: 20 }),
|
|
]);
|
|
|
|
assert.deepEqual(merged, ["X1", "X2", "X3"]);
|
|
});
|
|
|
|
test("merged_x_values compares numbers as numbers, not as text", () => {
|
|
const merged = merged_x_values([
|
|
series("a", { x: 2, y: 1 }, { x: 10, y: 1 }),
|
|
series("b", { x: 9, y: 1 }),
|
|
]);
|
|
|
|
assert.deepEqual(merged, [2, 9, 10]);
|
|
});
|
|
|
|
test("merged_x_values matches equal dates written as different objects", () => {
|
|
const merged = merged_x_values([
|
|
series("a", { x: new Date("2024-03-01"), y: 1 }),
|
|
series("b", { x: new Date("2024-03-01"), y: 2 }),
|
|
]);
|
|
|
|
assert.equal(merged.length, 1);
|
|
});
|
|
|
|
test("merged_x_values ignores series that hold no points", () => {
|
|
const merged = merged_x_values([
|
|
series("empty"),
|
|
series("a", { x: 1, y: 1 }),
|
|
]);
|
|
|
|
assert.deepEqual(merged, [1]);
|
|
});
|
|
|
|
test("align_series gives every series a point at every x (#727)", () => {
|
|
const [a, b] = align_series(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }),
|
|
series("b", { x: "Q2", y: 3 }),
|
|
],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.deepEqual(xs(a), ["Q1", "Q2"]);
|
|
assert.deepEqual(xs(b), ["Q1", "Q2"]);
|
|
});
|
|
|
|
test("align_series counts an x a stacked series skipped as zero (#727)", () => {
|
|
const [, b] = align_series(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }),
|
|
series("b", { x: "Q2", y: 3 }),
|
|
],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [
|
|
{ x: "Q1", y: 0 },
|
|
{ x: "Q2", y: 3 },
|
|
]);
|
|
});
|
|
|
|
test("align_series leaves a gap where an unstacked series has no value", () => {
|
|
const [, b] = align_series(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }),
|
|
series("b", { x: "Q2", y: 3 }),
|
|
],
|
|
LEAVES_A_GAP,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [
|
|
{ x: "Q1", y: null },
|
|
{ x: "Q2", y: 3 },
|
|
]);
|
|
});
|
|
|
|
test("align_series keeps a measured zero apart from a missing value", () => {
|
|
const [, b] = align_series(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }),
|
|
series("b", { x: "Q2", y: 0 }),
|
|
],
|
|
LEAVES_A_GAP,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [
|
|
{ x: "Q1", y: null },
|
|
{ x: "Q2", y: 0 },
|
|
]);
|
|
});
|
|
|
|
test("align_series counts a null value as missing", () => {
|
|
const [, b] = align_series(
|
|
[series("a", { x: "Q1", y: 1 }), series("b", { x: "Q1", y: null })],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [{ x: "Q1", y: 0 }]);
|
|
});
|
|
|
|
test("align_series keeps a blank value the series wrote", () => {
|
|
const [, b] = align_series(
|
|
[series("a", { x: "Q1", y: 1 }), series("b", { x: "Q1", y: "" })],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [{ x: "Q1", y: "" }]);
|
|
});
|
|
|
|
test("align_series keeps a value the series wrote as text", () => {
|
|
const [, b] = align_series(
|
|
[series("a", { x: "Q1", y: 1 }), series("b", { x: "Q1", y: "7" })],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [{ x: "Q1", y: "7" }]);
|
|
});
|
|
|
|
test("align_series keeps the third dimension of points it did not fill in", () => {
|
|
const [a] = align_series(
|
|
[series("a", { x: "Q1", y: 1, z: 42 }), series("b", { x: "Q2", y: 2 })],
|
|
LEAVES_A_GAP,
|
|
);
|
|
|
|
assert.equal(a.data[0].z, 42);
|
|
});
|
|
|
|
test("align_series matches dates by value rather than by identity", () => {
|
|
const [a, b] = align_series(
|
|
[
|
|
series("a", { x: new Date("2024-03-01"), y: 1 }),
|
|
series("b", { x: new Date("2024-03-01"), y: 2 }),
|
|
],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.equal(a.data.length, 1);
|
|
assert.equal(b.data.length, 1);
|
|
assert.equal(b.data[0].y, 2);
|
|
});
|
|
|
|
test("align_series leaves a lone series in the order it arrived (#930)", () => {
|
|
const [only] = align_series(
|
|
[series("a", { x: "Q2", y: 1 }, { x: "Q1", y: 2 })],
|
|
LEAVES_A_GAP,
|
|
);
|
|
|
|
assert.deepEqual(only.data, [
|
|
{ x: "Q2", y: 1 },
|
|
{ x: "Q1", y: 2 },
|
|
]);
|
|
});
|
|
|
|
test("align_series returns series that already share every x unchanged", () => {
|
|
const given = [
|
|
series("a", { x: "Q3", y: 1 }, { x: "Q1", y: 2 }, { x: "Q2", y: 3 }),
|
|
series("b", { x: "Q3", y: 4 }, { x: "Q1", y: 5 }, { x: "Q2", y: 6 }),
|
|
];
|
|
|
|
assert.deepEqual(align_series(given, ADDS_NOTHING_TO_THE_STACK), given);
|
|
});
|
|
|
|
test("align_series does not mutate the series it is given", () => {
|
|
const given = [
|
|
series("a", { x: "Q1", y: 1 }),
|
|
series("b", { x: "Q2", y: 2 }),
|
|
];
|
|
const before = JSON.stringify(given);
|
|
|
|
align_series(given, LEAVES_A_GAP);
|
|
|
|
assert.equal(JSON.stringify(given), before);
|
|
});
|
|
|
|
test("align_series keeps the last of duplicated x values", () => {
|
|
const [a] = align_series(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q1", y: 9 }),
|
|
series("b", { x: "Q2", y: 2 }),
|
|
],
|
|
ADDS_NOTHING_TO_THE_STACK,
|
|
);
|
|
|
|
assert.deepEqual(a.data, [
|
|
{ x: "Q1", y: 9 },
|
|
{ x: "Q2", y: 0 },
|
|
]);
|
|
});
|
|
|
|
test("align_series_for gives a stacked series a zero at every x it skipped", () => {
|
|
const [, b] = align_series_for(
|
|
[series("a", { x: 1, y: 1 }, { x: 2, y: 2 }), series("b", { x: 2, y: 3 })],
|
|
"area",
|
|
STACKED,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [
|
|
{ x: 1, y: 0 },
|
|
{ x: 2, y: 3 },
|
|
]);
|
|
});
|
|
|
|
for (const type of ["line", "area", "scatter", "bubble", "heatmap"]) {
|
|
test(`align_series_for leaves a gap where a ${type} series skips a label`, () => {
|
|
const [, b] = align_series_for(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }),
|
|
series("b", { x: "Q2", y: 3 }),
|
|
],
|
|
type,
|
|
UNSTACKED,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [
|
|
{ x: "Q1", y: null },
|
|
{ x: "Q2", y: 3 },
|
|
]);
|
|
});
|
|
}
|
|
|
|
test("align_series_for counts a label a bar series skipped as zero", () => {
|
|
const [, b] = align_series_for(
|
|
[
|
|
series("a", { x: "Q1", y: 1 }, { x: "Q2", y: 2 }),
|
|
series("b", { x: "Q2", y: 3 }),
|
|
],
|
|
"bar",
|
|
UNSTACKED,
|
|
);
|
|
|
|
assert.deepEqual(b.data, [
|
|
{ x: "Q1", y: 0 },
|
|
{ x: "Q2", y: 3 },
|
|
]);
|
|
});
|
|
|
|
test("align_series_for leaves a treemap's regions their own labels", () => {
|
|
const regions = [
|
|
series(
|
|
"North America",
|
|
{ x: "United States", y: 35 },
|
|
{ x: "Canada", y: 15 },
|
|
),
|
|
series("Europe", { x: "France", y: 30 }, { x: "Germany", y: 55 }),
|
|
];
|
|
|
|
assert.equal(align_series_for(regions, "treemap", UNSTACKED), regions);
|
|
});
|
|
|
|
test("align_series_for leaves a rangeBar timeline alone", () => {
|
|
const tasks = [
|
|
series("Design", { x: "Alice", y: [1, 5] }),
|
|
series("Build", { x: "Bob", y: [4, 9] }),
|
|
];
|
|
|
|
assert.equal(align_series_for(tasks, "rangeBar", UNSTACKED), tasks);
|
|
});
|
|
|
|
test("align_series_for leaves unstacked series without labels alone", () => {
|
|
const lines = [
|
|
series("a", { x: 1, y: 1 }, { x: 2, y: 2 }),
|
|
series("b", { x: 2, y: 3 }),
|
|
];
|
|
|
|
assert.equal(align_series_for(lines, "line", UNSTACKED), lines);
|
|
});
|