Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tests/end-to-end/fixtures/chart/hidden-legend.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'bar' AS type, TRUE AS stacked, FALSE AS show_legend;
WITH points(series, x, y) AS (VALUES ('CPU', 'Mon', 10), ('CPU', 'Tue', 20), ('GPU', 'Mon', 5), ('GPU', 'Tue', 15)) SELECT * FROM points;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'bar' AS type, TRUE AS horizontal, 100 AS ymax;
SELECT 90 AS yline, 'full' AS label;
SELECT 'db-1' AS xline, 'watched' AS label;
WITH points(x, y) AS (VALUES ('backup-1', 41), ('db-1', 88)) SELECT * FROM points;
2 changes: 1 addition & 1 deletion tests/end-to-end/fixtures/chart/stacked-time-series.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'area' AS type, TRUE AS stacked, TRUE AS time, 4 AS marker;
WITH points(series, x, y) AS (VALUES ('CPU', '2024-01-01T00:00:00Z', 10), ('CPU', '2024-01-01T00:01:00Z', 20), ('CPU', '2024-01-01T00:02:00Z', 30), ('CPU', '2024-01-01T00:03:00Z', 40), ('GPU', '2024-01-01T00:01:00Z', 50), ('GPU', '2024-01-01T00:02:00Z', 50), ('GPU', '2024-01-01T00:03:00Z', 50)) SELECT * FROM points;
WITH points(series, x, y) AS (VALUES ('CPU', '2024-01-01T00:00:00Z', 10), ('CPU', '2024-01-01T00:01:00Z', 20), ('CPU', '2024-01-01T00:02:00Z', 30), ('CPU', '2024-01-01T00:03:00Z', 40), ('CPU', '2024-01-01T00:04:00Z', 30), ('GPU', '2024-01-01T00:01:00Z', 50), ('GPU', '2024-01-01T00:02:00Z', 50), ('GPU', '2024-01-01T00:03:00Z', 50)) SELECT * FROM points;
57 changes: 55 additions & 2 deletions tests/end-to-end/fixtures/chart/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ test("gives a stacked series a zero at every x it did not measure", async ({
["2024-01-01T00:01:00.000Z", 50],
["2024-01-01T00:02:00.000Z", 50],
["2024-01-01T00:03:00.000Z", 50],
["2024-01-01T00:04:00.000Z", 0],
]);
});

Expand All @@ -316,8 +317,13 @@ test("stacks a series above the one it shares an x with", async ({ page }) => {
const [cpu, gpu] = chart.drawnPerSeries;
assert.ok(cpu && gpu, "Both series were drawn");

expect(gpu.heights).toHaveLength(4);
expect(gpu.heights[0]).toBe(cpu.heights[0]);
expect(cpu.lefts).toHaveLength(5);
expect(gpu.lefts).toEqual(cpu.lefts);
expect(gpu.heights).toHaveLength(5);
expect([gpu.heights[0], gpu.heights[4]]).toEqual([
cpu.heights[0],
cpu.heights[4],
]);
const cpuHeight = cpu.heights[1];
assert.ok(cpuHeight !== undefined, "CPU has a second point");
expect(gpu.heights[1]).toBeLessThan(cpuHeight);
Expand Down Expand Up @@ -507,6 +513,53 @@ test("gives every slice of a pie chart its label and its share", async ({
expect(chart.dataLabels).toEqual(["Yes: 65%", "No: 35%"]);
});

test("shows series legends by default and hides them when requested", async ({
page,
}) => {
await renderChart(page, "stacked-time-series");
await expect(page.locator("#test-chart .apexcharts-legend-text")).toHaveText([
"CPU",
"GPU",
]);

await renderChart(page, "hidden-legend");
await expect(page.locator("#test-chart .apexcharts-legend")).toBeHidden();
});

for (const { axis, fixture, labels } of [
{ axis: "y", fixture: "y-reference-band", labels: ["target", "throttling"] },
{ axis: "x", fixture: "x-reference-band", labels: ["deploy", "incident"] },
]) {
test(`draws a ${axis} reference line and band with their labels`, async ({
page,
}) => {
await renderChart(page, fixture);
const annotations = page.locator(
`#test-chart .apexcharts-${axis}axis-annotations`,
);
await expect(annotations.locator("line")).toHaveCount(1);
await expect(
annotations.locator(".apexcharts-annotation-rect"),
).toHaveCount(1);
for (const label of labels) {
await expect(annotations.getByText(label, { exact: true })).toBeVisible();
}
});
}

test("horizontal bars keep reference lines on their data axes", async ({
page,
}) => {
await renderChart(page, "horizontal-reference-lines");
const down = page.locator("#test-chart .apexcharts-xaxis-annotations");
const across = page.locator("#test-chart .apexcharts-yaxis-annotations");

await expect(down.locator("line")).toHaveCount(1);
await expect(down.getByText("full", { exact: true })).toBeVisible();
await expect(across.locator("line")).toHaveCount(1);
await expect(across.getByText("watched", { exact: true })).toBeVisible();
});

test("draws a reference line that carries no label", async ({ page }) => {
const chart = await renderChart(page, "unlabeled-reference-lines");

Expand Down
4 changes: 4 additions & 0 deletions tests/end-to-end/fixtures/chart/x-reference-band.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'area' AS type, TRUE AS time;
SELECT '2024-05-01T10:00:00Z' AS xline, 'deploy' AS label;
SELECT '2024-05-01T11:30:00Z' AS xline, '2024-05-01T13:00:00Z' AS xline_end, 'incident' AS label;
WITH points(x, y) AS (VALUES ('2024-05-01T08:00:00Z', 120), ('2024-05-01T12:00:00Z', 512), ('2024-05-01T15:00:00Z', 133)) SELECT * FROM points;
4 changes: 4 additions & 0 deletions tests/end-to-end/fixtures/chart/y-reference-band.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'line' AS type, TRUE AS time, 100 AS ymax;
SELECT 70 AS yline, 'target' AS label;
SELECT 90 AS yline, 100 AS yline_end, 'throttling' AS label;
WITH points(x, y) AS (VALUES ('2024-05-01T08:00:00Z', 52), ('2024-05-01T11:00:00Z', 83), ('2024-05-01T14:00:00Z', 63)) SELECT * FROM points;
149 changes: 26 additions & 123 deletions tests/end-to-end/official-site.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, type Locator, type Page, test } from "@playwright/test";
import { expect, type Page, test } from "@playwright/test";
import {
checkModal,
checkTableFiltering,
Expand All @@ -20,131 +20,38 @@ test("Open documentation", async ({ page }) => {
}
});

async function openChartDocumentation(page: Page) {
await page.goto("/documentation.sql?component=chart#component");
// This page initializes every documented chart, which can take several seconds.
await expect(page.locator('[data-pre-init="chart"]')).toHaveCount(0, {
timeout: 15_000,
});
}

test("chart", async ({ page }) => {
await openChartDocumentation(page);
await expect(page.getByText("Loading...")).toHaveCount(0);
await expect(page.locator(".apexcharts-canvas").first()).toBeVisible();
});

test("chart supports hiding legend", async ({ page }) => {
await openChartDocumentation(page);

const expensesChart = page.locator(".card", {
has: page.getByRole("heading", { name: "Expenses" }),
});

await expect(expensesChart.locator(".apexcharts-canvas")).toBeVisible();
await expect(expensesChart.locator(".apexcharts-legend")).toBeHidden();
});

const DATA_POINT_MARKERS = ".apexcharts-series-markers > .apexcharts-marker";

const chartCard = (page: Page, title: string) =>
page.locator(".card", { has: page.getByRole("heading", { name: title }) });

const drawnPoints = (card: Locator, series: string) =>
card
.locator(`.apexcharts-series[seriesName='${series}'] ${DATA_POINT_MARKERS}`)
.evaluateAll((markers) =>
markers.map((m) => ({
x: m.getAttribute("cx"),
y: m.getAttribute("cy"),
})),
);

test("stacked chart draws every series at every x of the chart", async ({
page,
}) => {
await openChartDocumentation(page);
const powerChart = chartCard(page, "Power draw");
await expect(powerChart.locator(".apexcharts-canvas")).toBeVisible();

const cpu = await drawnPoints(powerChart, "CPU");
const gpu = await drawnPoints(powerChart, "GPU");

expect(cpu).toHaveLength(4);
expect(gpu.map((p) => p.x)).toEqual(cpu.map((p) => p.x));
});

test("stacked chart raises a series only where it has a value", async ({
page,
}) => {
await openChartDocumentation(page);
const powerChart = chartCard(page, "Power draw");
await expect(powerChart.locator(".apexcharts-canvas")).toBeVisible();

const cpu = await drawnPoints(powerChart, "CPU");
const gpu = await drawnPoints(powerChart, "GPU");

expect([gpu[0], gpu[3]]).toEqual([cpu[0], cpu[3]]);
expect(Number(gpu[1]?.y)).toBeLessThan(Number(cpu[1]?.y));
});

test("chart draws a yline as a line and a yline_end as a band", async ({
test("chart documentation renders its examples without browser errors", async ({
page,
}) => {
await openChartDocumentation(page);

const temperature = page.locator(".card", {
has: page.getByRole("heading", { name: "CPU temperature" }),
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
await expect(temperature.locator(".apexcharts-canvas")).toBeVisible();

const annotations = temperature.locator(".apexcharts-yaxis-annotations");
const lines = annotations.locator("line");
const bands = annotations.locator(".apexcharts-annotation-rect");

await expect(lines).toHaveCount(1);
await expect(bands).toHaveCount(1);
await expect(annotations.getByText("target")).toBeVisible();
await expect(annotations.getByText("throttling")).toBeVisible();
});

test("chart draws an xline as a line and an xline_end as a band", async ({
page,
}) => {
await openChartDocumentation(page);

const latency = page.locator(".card", {
has: page.getByRole("heading", { name: "Request latency" }),
await test.step("Open the chart reference", async () => {
await page.goto("/documentation.sql?component=chart#component");
await expect(
page.getByRole("heading", { name: "The chart component", exact: true }),
).toBeVisible();
});
await expect(latency.locator(".apexcharts-canvas")).toBeVisible();

const annotations = latency.locator(".apexcharts-xaxis-annotations");
const lines = annotations.locator("line");
const bands = annotations.locator(".apexcharts-annotation-rect");

await expect(lines).toHaveCount(1);
await expect(bands).toHaveCount(1);
await expect(annotations.getByText("deploy")).toBeVisible();
await expect(annotations.getByText("incident")).toBeVisible();
});

test("horizontal chart draws a yline down it and an xline across it", async ({
page,
}) => {
await openChartDocumentation(page);

const disks = page.locator(".card", {
has: page.getByRole("heading", { name: "Disk usage" }),
await test.step("Render every inline and embedded example", async () => {
// Work around Chromium's slow :has() invalidation on this many-chart page.
// https://issues.chromium.org/issues/576187899
await expect(page.getByText("Loading...")).toHaveCount(0, {
timeout: 15_000,
});
await expect(page.locator('[data-pre-init="chart"]')).toHaveCount(0);
const examples = page.locator(
'[data-sqlpage-js*="apexcharts."], [data-embed]',
);
await expect(examples.first()).toBeVisible();
for (const example of await examples.all()) {
await expect(example.locator(".apexcharts-canvas")).toBeVisible();
}
expect(errors).toEqual([]);
});
await expect(disks.locator(".apexcharts-canvas")).toBeVisible();

const down = disks.locator(".apexcharts-xaxis-annotations");
const across = disks.locator(".apexcharts-yaxis-annotations");

await expect(down.locator("line")).toHaveCount(1);
await expect(down.getByText("full")).toBeVisible();
await expect(across.locator("line")).toHaveCount(1);
await expect(across.getByText("watched")).toBeVisible();
});

test("map", async ({ page }) => {
Expand Down Expand Up @@ -254,10 +161,6 @@ test("no console errors on table page", async ({ page }) => {
await checkNoConsoleErrors(page, "table");
});

test("no console errors on chart page", async ({ page }) => {
await checkNoConsoleErrors(page, "chart");
});

test("no console errors on map page", async ({ page }) => {
await checkNoConsoleErrors(page, "map");
});
Expand Down
Loading