← Files DataARCHIVED FILE
tests/data-app-ranked-list-browser.smoke.mjs
20.7 KB · Sep 30, 2026 · 23:19 UTC
import assert from "node:assert/strict";
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { chromium } from "playwright-core";
import { installDashboardBrowserMocks, resolveChromiumExecutable, runDataAppFixtureBuild } from "./browser-helpers.mjs";
const pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const template = join(pluginRoot, "templates/data-app/base");
const project = mkdtempSync(join(tmpdir(), "data-ranked-list-smoke-"));
cpSync(template, project, {
recursive: true,
filter: (path) => !path.includes("/node_modules") && !path.includes("/dist"),
});
writeFileSync(join(project, "src/content/dashboard/DashboardContent.jsx"), `
import React, { useState } from "react";
import { ChartRenderer, DataComponent, EvidenceChart, useDataApp } from "../../data-app-public.jsx";
function Comparison({ id, title, rows, spec, tall = false, visibleSeries }) {
const { chartOverrides, chartProps } = useDataApp();
const chart = { ...spec, ...(chartOverrides[id] ?? {}) };
const interactions = chartProps(id);
return <DataComponent id={id} title={title} queryId="feature_movement" kind="chart" chart={chart}
displayRows={rows} sourceRows={rows} className="dashboard-panel ranking-smoke-card">
<ChartRenderer spec={chart} rows={rows} height={tall ? 410 : 220} {...interactions}
visibleSeries={visibleSeries ?? interactions.visibleSeries} />
</DataComponent>;
}
function DynamicReviewedPie({ rows }) {
const [expanded, setExpanded] = useState(false);
return <section>
<button type="button" onClick={() => setExpanded((current) => !current)}>
{expanded ? "Reduce reviewed plan categories" : "Expand reviewed plan categories"}
</button>
<Comparison id="dynamic-reviewed-pie" title="Dynamic reviewed plans" rows={expanded ? rows : rows.slice(0, 7)}
spec={{ type: "pie", x: "plan", y: "users", groupOther: true }} />
</section>;
}
export function DashboardContent() {
const { reviewedRows } = useDataApp();
const summary = reviewedRows("usage_summary").at(-1);
const rows = reviewedRows("feature_movement", ["feature"])
.filter((row) => row.week === summary?.week)
.map((row, index) => ({ ...row, targetUsers: row.activeUsers + 50, change: index ? index * 10 : -15 }));
const ranking = { x: "feature", y: "activeUsers" };
const plans = rows.map((row) => ({ plan: row.feature, users: row.activeUsers }));
const products = Array.from({ length: 20 }, (_, index) => ({
product: "Long reviewed product category " + (index + 1), users: 100 - index,
}));
const codes = Array.from({ length: 20 }, (_, index) => ({ code: String.fromCharCode(65 + index), count: index + 1 }));
const reviewedPlans = ["Plus", "Pro", "Team", "Free", "Business", "Enterprise", "Education", "Education Pro"]
.map((plan, index) => ({ plan, users: (8 - index) * 10 }));
return <article className="page ranking-smoke-page">
<section className="ranking-smoke-pair">
<Comparison id="adaptive-ranking" title="Adaptive leaderboard" rows={rows}
spec={{ ...ranking, type: "rankedList" }} />
<Comparison id="tall-neighbor" title="Taller comparison" rows={rows} tall
spec={{ ...ranking, type: "bar" }} />
</section>
<section className="ranking-smoke-grid">
<Comparison id="baseline-ranking" title="Baseline leaderboard" rows={rows}
spec={{ ...ranking, type: "rankedList", colors: {
Projects: "var(--chart-2)", Canvas: "var(--chart-3)",
} }} />
<Comparison id="legacy-leaderboard" title="Legacy leaderboard" rows={rows}
spec={{ ...ranking, type: "leaderboard" }} />
<Comparison id="horizontal-comparison" title="Horizontal comparison" rows={rows}
spec={{ ...ranking, type: "horizontalBar" }} />
<Comparison id="signed-comparison" title="Signed comparison" rows={rows}
spec={{ ...ranking, type: "horizontalBar", y: "change" }} />
<Comparison id="multiple-measures" title="Multiple measures" rows={rows}
spec={{ ...ranking, type: "horizontalBar", fields: ["activeUsers", "targetUsers"] }} />
<Comparison id="explicit-bars" title="Explicit bar chart" rows={rows}
spec={{ ...ranking, type: "horizontalBar" }} />
<Comparison id="axis-bars" title="Axis comparison" rows={rows}
spec={{ ...ranking, type: "horizontalBar", xLabel: "Active accounts" }} />
<Comparison id="unsafe-product-pie" title="Potentially overlapping product audiences" rows={products}
spec={{ type: "pie", x: "product", y: "users" }} />
<Comparison id="reviewed-plan-pie" title="Reviewed exclusive account plans" rows={plans}
spec={{ type: "pie", x: "plan", y: "users", groupOther: true }} />
<Comparison id="filtered-reviewed-plan-pie" title="Filtered reviewed account plans" rows={reviewedPlans}
visibleSeries={reviewedPlans.filter((row) => row.plan !== "Education").map((row) => row.plan)}
spec={{ type: "pie", x: "plan", y: "users", groupOther: true, showValues: true }} />
<Comparison id="short-category-bars" title="Every reviewed category code" rows={codes}
spec={{ type: "bar", x: "code", y: "count" }} />
<Comparison id="spelled-horizontal-bars" title="Channel comparison" rows={[
{ channel: "Customer Story", count: 270 }, { channel: "Paid Search", count: 300 },
{ channel: "Paid Social", count: 180 }, { channel: "Partner Event", count: 360 },
]} spec={{ type: "horizontal-bar", x: "channel", y: "count" }} />
<EvidenceChart id="signed-rate-comparison" queryId="feature_movement" title="Weekly growth comparison"
variant="card" rows={[
{ feature: "A", previousGrowthRate: .174, currentGrowthRate: -.024 },
{ feature: "B", previousGrowthRate: .04, currentGrowthRate: .11 },
]} spec={{ type: "horizontalBar", x: "feature", y: "currentGrowthRate",
fields: ["previousGrowthRate", "currentGrowthRate"], showLegend: false }} height={220}>
<div data-detail-reading>Supporting reading</div>
</EvidenceChart>
<DynamicReviewedPie rows={plans} />
</section>
</article>;
}
`);
writeFileSync(join(project, "src/content/dashboard/dashboard.css"), `
.ranking-smoke-page { display: grid; gap: 20px; padding: 24px; }
.ranking-smoke-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.ranking-smoke-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: start; }
.ranking-smoke-card { min-width: 0; padding: 20px; border: 1px solid var(--border); border-radius: var(--card-radius); }
.ranking-smoke-grid > .ranking-smoke-card { align-self: start; }
@media (max-width: 600px) { .ranking-smoke-pair, .ranking-smoke-grid { grid-template-columns: 1fr; } }
`);
let browser;
try {
const build = runDataAppFixtureBuild(project, { pluginRoot });
assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);
browser = await chromium.launch({ executablePath: resolveChromiumExecutable(), headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } });
const failures = [];
page.on("pageerror", (error) => failures.push(error.message));
await installDashboardBrowserMocks(page);
await page.goto(pathToFileURL(join(project, "dist/index.html")).href, { waitUntil: "load" });
for (const viewport of [{ width: 1440, height: 1100 }, { width: 640, height: 1000 }]) {
await page.setViewportSize(viewport);
const channels = page.locator('[data-component-id="spelled-horizontal-bars"]');
await channels.locator('.recharts-bar-rectangle').first().waitFor();
assert.equal(await channels.locator('.recharts-yAxis-tick-labels text').count(), 4,
"The horizontal spelling retains all four channel labels, including at narrow widths");
const growth = page.locator('[data-component-id="signed-rate-comparison"]');
await growth.locator('.recharts-bar-rectangle').first().waitFor();
assert.equal(await growth.locator('.recharts-xAxis').count(), 1, "Compared growth rates share a single numeric axis");
const ticks = await growth.locator('.recharts-xAxis-tick-labels text').allTextContents();
assert.ok(ticks.length > 1 && ticks.every(tick => tick.includes('%')), `Negative growth keeps percentage ticks: ${JSON.stringify(ticks)}`);
const gap = await growth.evaluate(element => element.querySelector('[data-detail-reading]').getBoundingClientRect().top
- element.querySelector('.chart-layout').getBoundingClientRect().bottom);
assert.ok(gap >= 15, "Supporting readings have breathing room below the plot and its axis labels");
const bars = await growth.locator('.recharts-bar-rectangle').evaluateAll(elements => elements.map(element => {
const rect = element.getBoundingClientRect(); return { left: rect.left, right: rect.right, width: rect.width };
}));
assert.equal(bars.length, 4);
assert.ok(Math.abs(bars[0].left - bars[2].right) < 2, "Positive and negative comparison bars meet the same zero");
assert.ok(Math.abs(bars[0].width / bars[2].width - .174 / .024) < .1,
"Bar lengths encode the compared rates on the same scale");
}
await page.setViewportSize({ width: 1440, height: 1100 });
const baseline = page.locator('[data-component-id="baseline-ranking"]');
await baseline.getByRole("button", { name: "Baseline leaderboard actions" }).click();
const baselineMenu = page.getByRole("menu", { name: "Baseline leaderboard actions" });
assert.equal(
await baselineMenu.getByRole("menuitem", { name: "Download PNG" }).count(),
0,
"Chart components without a renderable image should not offer a PNG download",
);
assert.equal(
await baselineMenu.getByRole("menuitem", { name: "Copy as image" }).count(),
0,
"Chart components without a renderable image should not offer image copying",
);
assert.equal(await baselineMenu.getByRole("menuitem", { name: "Export chart" }).count(), 0,
"HTML-only charts should not open the SVG/canvas export dialog");
await page.keyboard.press("Escape");
const baselineRows = baseline.locator(".chart-ranked-list-row");
assert.equal(await baselineRows.count(), 5, "Standalone leaderboards should default to five reviewed rows");
const categoryFills = await baseline.locator(".chart-ranked-list-fill").evaluateAll((fills) =>
fills.slice(0, 2).map((fill) => getComputedStyle(fill).backgroundColor));
assert.notEqual(categoryFills[0], categoryFills[1],
"Explicit reviewed category colors should produce matching distinct leaderboard tints");
const adaptiveFills = page.locator('[data-component-id="adaptive-ranking"] .chart-ranked-list-fill');
const defaultColors = await adaptiveFills.evaluateAll((fills) =>
fills.slice(0, 5).map((fill) => getComputedStyle(fill).backgroundColor));
assert.equal(new Set(defaultColors).size, 1,
"Leaderboards should use one restrained theme-aware fill unless category colors are explicitly authored");
const themedFill = adaptiveFills.first();
const originalFill = await themedFill.evaluate((fill) => getComputedStyle(fill).backgroundColor);
assert.equal(await themedFill.evaluate((fill) => fill.style.getPropertyValue("--ranked-list-fill")), "",
"Default leaderboard fills should inherit their theme token without inventing per-category colors");
const paletteToken = "chart-1";
await page.evaluate((token) => {
const root = document.documentElement;
root.dataset.previousChartColor = root.style.getPropertyValue(`--${token}`);
root.style.setProperty(`--${token}`, "rgb(255, 35, 140)");
}, paletteToken);
assert.notEqual(await themedFill.evaluate((fill) => getComputedStyle(fill).backgroundColor), originalFill,
"Leaderboard fills should respond immediately when the active dashboard theme changes");
await page.evaluate((token) => {
const root = document.documentElement;
if (root.dataset.previousChartColor) root.style.setProperty(`--${token}`, root.dataset.previousChartColor);
else root.style.removeProperty(`--${token}`);
delete root.dataset.previousChartColor;
}, paletteToken);
await baseline.getByRole("button", { name: "Show 7 more" }).click();
assert.equal(await baselineRows.count(), 12, "Disclosure should reveal every additional reviewed category");
await baseline.getByRole("button", { name: "Show fewer" }).click();
assert.equal(await baselineRows.count(), 5, "Collapsing a standalone leaderboard should restore five rows");
const adaptive = page.locator('[data-component-id="adaptive-ranking"]');
const adaptiveRows = adaptive.locator(".chart-ranked-list-row");
await page.waitForFunction(() =>
document.querySelectorAll('[data-component-id="adaptive-ranking"] .chart-ranked-list-row').length > 5);
const adaptiveCount = await adaptiveRows.count();
assert.ok(adaptiveCount > 5 && adaptiveCount <= 12,
`A taller adjacent chart should expose additional reviewed rows, received ${adaptiveCount}`);
const bounds = await adaptive.evaluate((card) => ({
card: card.getBoundingClientRect().bottom,
content: (card.querySelector(".chart-ranked-list-toggle") ?? card.querySelector(".chart-ranked-list"))
.getBoundingClientRect().bottom,
}));
assert.ok(bounds.content <= bounds.card + 1,
`Adaptive rows and disclosure must remain inside their panel: ${JSON.stringify(bounds)}`);
if (adaptiveCount < 12) {
assert.equal(await adaptive.getByRole("button", { name: `Show ${12 - adaptiveCount} more` }).count(), 1,
"Additional categories beyond the available card height must remain expandable");
}
await page.setViewportSize({ width: 540, height: 1100 });
await page.waitForFunction(() =>
document.querySelectorAll('[data-component-id="adaptive-ranking"] .chart-ranked-list-row').length === 5);
await page.setViewportSize({ width: 1440, height: 1100 });
await page.waitForFunction(() =>
document.querySelectorAll('[data-component-id="adaptive-ranking"] .chart-ranked-list-row').length > 5);
for (const id of ["legacy-leaderboard"]) {
const chart = page.locator(`[data-component-id="${id}"]`);
assert.equal(await chart.locator(".chart-ranked-list-row").count(), 5,
`${id} should resolve safe simple rankings to the compact leaderboard`);
}
for (const id of ["horizontal-comparison", "signed-comparison", "multiple-measures", "explicit-bars", "axis-bars"]) {
const chart = page.locator(`[data-component-id="${id}"]`);
assert.equal(await chart.locator(".chart-ranked-list").count(), 0,
`${id} should preserve comparisons that genuinely need a bar chart`);
assert.ok(await chart.locator(".recharts-surface").count(), `${id} should render a real bar-chart surface`);
}
const overlappingPie = page.locator('[data-component-id="unsafe-product-pie"]');
assert.equal(await overlappingPie.locator(".chart-legend-button").count(), 20,
"Unverified product audiences must remain separate rather than silently becoming Other");
const overflowingLegend = overlappingPie.locator(".chart-legend");
const expandLegend = overlappingPie.getByRole("button", { name: "Show all 20 categories" });
await expandLegend.waitFor({ state: "visible" });
await overflowingLegend.evaluate((legend) => { legend.scrollTop = legend.scrollHeight; });
assert.equal(await expandLegend.isVisible(), true,
"Scrolling to the end of a collapsed legend must not remove its expansion control");
const reviewedPie = page.locator('[data-component-id="reviewed-plan-pie"]');
assert.equal(await reviewedPie.locator(".chart-legend-button").count(), 7,
"Explicitly reviewed mutually exclusive plans should retain six leaders plus Other");
await reviewedPie.getByRole("button", { name: "Toggle Other" }).waitFor();
const filteredPie = page.locator('[data-component-id="filtered-reviewed-plan-pie"]');
const reviewedSliceValues = await filteredPie.locator(".recharts-label-list text")
.evaluateAll((labels) => labels.map((label) => label.textContent.trim()));
assert.equal(reviewedSliceValues.at(-1), "10",
"The visible Other slice must exclude the hidden Education category instead of displaying its unfiltered total");
const categoryBars = page.locator('[data-component-id="short-category-bars"]');
assert.equal(await categoryBars.locator('[data-axis-layout]').count(), 20,
"Every reviewed short categorical bar label must remain visible even when densely spaced");
const dynamicPie = page.locator('[data-component-id="dynamic-reviewed-pie"]');
const originalPlanToggle = dynamicPie.getByRole("button", { name: /^Toggle /u }).first();
await originalPlanToggle.click();
await page.getByRole("button", { name: "Expand reviewed plan categories" }).click();
const groupedOther = dynamicPie.getByRole("button", { name: "Toggle Other" });
assert.equal(await groupedOther.getAttribute("aria-pressed"), "true",
"A newly grouped Other slice should inherit visibility from its reviewed constituent categories");
await groupedOther.click();
await page.getByRole("button", { name: "Reduce reviewed plan categories" }).click();
assert.equal(await dynamicPie.getByRole("button", { name: /^Toggle /u }).nth(6).getAttribute("aria-pressed"), "false",
"Hiding Other must preserve the hidden state of its source categories after grouping disappears");
await page.getByRole("button", { name: "Expand reviewed plan categories" }).click();
assert.equal(await groupedOther.getAttribute("aria-pressed"), "false",
"Previously hidden source categories must remain hidden when Other becomes available again");
await groupedOther.click();
await page.getByRole("button", { name: "Reduce reviewed plan categories" }).click();
assert.equal(await dynamicPie.getByRole("button", { name: /^Toggle /u }).nth(6).getAttribute("aria-pressed"), "true",
"Showing Other must restore its source categories when the chart becomes ungrouped");
await page.getByRole("button", { name: "Expand reviewed plan categories" }).click();
await groupedOther.press("Shift+Enter");
await page.getByRole("button", { name: "Reduce reviewed plan categories" }).click();
const isolatedPlans = await dynamicPie.getByRole("button", { name: /^Toggle /u })
.evaluateAll((buttons) => buttons.map((button) => button.getAttribute("aria-pressed")));
assert.deepEqual(isolatedPlans, ["false", "false", "false", "false", "false", "false", "true"],
"Isolating Other must persist the identities of its underlying reviewed categories");
const horizontal = page.locator('[data-component-id="horizontal-comparison"]');
await horizontal.getByRole("button", { name: "Horizontal comparison actions" }).click();
await page.getByRole("menuitem", { name: "Edit chart" }).click();
const editor = page.getByRole("dialog", { name: "Horizontal comparison" });
const chartType = editor.getByRole("button", { name: "Chart type", exact: true });
assert.equal((await chartType.innerText()).trim(), "Horizontal bar",
"The chart editor should preserve the explicitly authored chart type");
await editor.getByRole("button", { name: "Sort order" }).click();
await page.getByRole("menuitemradio", { name: "Ascending", exact: true }).click();
await editor.getByRole("button", { name: "Save", exact: true }).click();
await editor.waitFor({ state: "hidden" });
const storedRanking = await page.evaluate(() => Object.values(localStorage)
.map((value) => JSON.parse(value)).map((record) => record.presentation?.chartOverrides?.["horizontal-comparison"])
.find(Boolean));
assert.equal(storedRanking?.type, "horizontalBar",
"Unrelated chart edits must preserve the authored horizontal-bar type");
await horizontal.getByRole("button", { name: "Horizontal comparison actions" }).click();
await page.getByRole("menuitem", { name: "Edit chart" }).click();
await editor.waitFor();
await chartType.click();
assert.equal(await page.getByRole("menuitemradio", { name: "Leaderboard", exact: true }).count(), 1,
"The chart editor should present one canonical leaderboard choice");
assert.equal(await page.getByRole("menuitemradio", { name: "Ranked list", exact: true }).count(), 0,
"The chart editor should not expose a second competing leaderboard label");
await page.getByRole("menuitemradio", { name: "Horizontal bar", exact: true }).click();
await editor.getByRole("button", { name: "Save", exact: true }).click();
await editor.waitFor({ state: "hidden" });
assert.equal(await horizontal.locator(".chart-ranked-list").count(), 0,
"An explicitly selected horizontal bar should opt out of automatic ranking conversion");
assert.ok(await horizontal.locator(".recharts-surface").count(),
"The explicit horizontal-bar editor choice should render as a chart");
assert.deepEqual(failures, [], "Leaderboard rendering should not emit browser runtime errors");
process.stdout.write(`Ranked-list browser smoke passed: five baseline rows, ${adaptiveCount} adaptive rows.\n`);
} finally {
await browser?.close();
rmSync(project, { recursive: true, force: true });
}
SHA-256: e7b946b0330ce6fcec57bce37b97685c82f2f92033d460c2f39c70a23aed8375