mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
The matrix mounted every badge's tooltip up front, hidden by CSS, and re-rendered every row on each keystroke. Tooltips now mount while their badge is hovered or has keyboard focus, are named by aria-describedby and close on Escape. Badges and rows are memoized, each row's search text is built once, the filter makes one pass for its rows and its count, and the table follows deferred search and status values, with expand all and collapse all as transitions, so input stays responsive. The tree and filter passes move to src/lib/compatibility-tree.ts, with unit tests that also render the badges on the server (pnpm test:unit, part of pnpm check). Closes #601 Co-authored-by: jonbaldie <8376953+jonbaldie@users.noreply.github.com>
116 lines
4.9 KiB
JavaScript
116 lines
4.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { test } from "node:test";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import ts from "typescript";
|
|
import {
|
|
buildTree,
|
|
expandableIds,
|
|
filterTree,
|
|
flattenTree,
|
|
nodeCount,
|
|
rowMatcher,
|
|
searchTexts,
|
|
statusCounts,
|
|
} from "../src/lib/compatibility-tree.ts";
|
|
|
|
const row = (id, depth, label, status, detail = "") => ({
|
|
id,
|
|
chapter: "zlib",
|
|
kind: depth === 0 ? "module" : "method",
|
|
name: label,
|
|
signature: label,
|
|
label,
|
|
apiSymbol: label,
|
|
depth,
|
|
scope: "api",
|
|
anchor: "",
|
|
nodeStability: null,
|
|
static: { status, detail, verification: "test-backed" },
|
|
});
|
|
const rows = [
|
|
row("root", 0, "zlib", "partial"),
|
|
row("a", 1, "zlib.gzipSync()", "supported", "Lowered with Node's options"),
|
|
row("b", 1, "zlib.Gzip", "partial"),
|
|
row("b1", 2, "gzip.flush()", "supported"),
|
|
row("c", 1, "zlib.zstdCompress()", "not-implemented"),
|
|
];
|
|
const titles = new Map([["zlib", "Zlib"]]);
|
|
|
|
test("the tree follows the rows' heading depths", () => {
|
|
const tree = buildTree(rows);
|
|
assert.equal(tree.length, 1);
|
|
assert.deepEqual(tree[0].children.map((node) => node.row.id), ["a", "b", "c"]);
|
|
assert.deepEqual(tree[0].children[1].children.map((node) => node.row.id), ["b1"]);
|
|
assert.equal(nodeCount(tree), 5);
|
|
assert.deepEqual([...expandableIds(tree)].sort(), ["b", "root"]);
|
|
});
|
|
|
|
test("filtering keeps matching rows with their ancestors and counts the matches once", () => {
|
|
const tree = buildTree(rows);
|
|
const texts = searchTexts(rows, titles);
|
|
const byText = filterTree(tree, rowMatcher(texts, "flush", "all"));
|
|
assert.equal(byText.matched, 1);
|
|
assert.deepEqual(flattenTree(byText.nodes, new Set(), true).map(({ node, level }) => `${node.row.id}@${level}`), ["root@1", "b@2", "b1@3"]);
|
|
const byDetail = filterTree(tree, rowMatcher(texts, "node's options", "all"));
|
|
assert.deepEqual(flattenTree(byDetail.nodes, new Set(), true).map(({ node }) => node.row.id), ["root", "a"]);
|
|
const byStatus = filterTree(tree, rowMatcher(texts, "", "supported"));
|
|
assert.equal(byStatus.matched, 2);
|
|
const both = filterTree(tree, rowMatcher(texts, "gzip", "partial"));
|
|
assert.equal(both.matched, 1);
|
|
assert.equal(filterTree(tree, rowMatcher(texts, "brotli", "all")).nodes.length, 0);
|
|
});
|
|
|
|
test("collapsed rows hide their children until expanded", () => {
|
|
const tree = buildTree(rows);
|
|
assert.deepEqual(flattenTree(tree, new Set(), false).map(({ node }) => node.row.id), ["root"]);
|
|
assert.deepEqual(flattenTree(tree, new Set(["root"]), false).map(({ node }) => node.row.id), ["root", "a", "b", "c"]);
|
|
assert.deepEqual(flattenTree(tree, new Set(["root", "b"]), false).map(({ node }) => node.row.id), ["root", "a", "b", "b1", "c"]);
|
|
});
|
|
|
|
test("status counts cover API rows below the chapters", () => {
|
|
const counts = statusCounts(rows);
|
|
assert.equal(counts.supported, 2);
|
|
assert.equal(counts.partial, 1);
|
|
assert.equal(counts["not-implemented"], 1);
|
|
});
|
|
|
|
/** The matrix component, compiled for Node from its TSX source. */
|
|
async function loadMatrix() {
|
|
const docs = new URL("../", import.meta.url);
|
|
const source = await readFile(new URL("src/components/node-compatibility-matrix.tsx", docs), "utf8");
|
|
const { outputText } = ts.transpileModule(source, {
|
|
compilerOptions: { jsx: ts.JsxEmit.ReactJSX, module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
|
|
});
|
|
const lib = pathToFileURL(fileURLToPath(new URL("src/lib/compatibility-tree.ts", docs))).href;
|
|
const dir = new URL("node_modules/.cache/compatibility-matrix-test/", docs);
|
|
await mkdir(dir, { recursive: true });
|
|
const file = new URL("node-compatibility-matrix.mjs", dir);
|
|
await writeFile(file, outputText.replace('"@/lib/compatibility-tree"', JSON.stringify(lib)));
|
|
return import(file.href);
|
|
}
|
|
|
|
test("badges render without their tooltips until hovered or focused", async () => {
|
|
const { StatusBadge, StabilityBadge } = await loadMatrix();
|
|
const status = renderToStaticMarkup(
|
|
createElement(StatusBadge, {
|
|
status: "partial",
|
|
detail: "Lowered with Node's options",
|
|
verification: { label: "Test-backed", detail: "Corpus programs cover it." },
|
|
}),
|
|
);
|
|
assert.ok(status.includes('tabindex="0"'), status);
|
|
assert.ok(status.includes('title="Lowered with Node's options'), status);
|
|
assert.ok(!status.includes('role="tooltip"'), status);
|
|
assert.ok(!status.includes("aria-describedby"), status);
|
|
const stability = renderToStaticMarkup(
|
|
createElement(StabilityBadge, { stability: { index: "2", level: 2, text: "Stable", inherited: false } }),
|
|
);
|
|
assert.ok(stability.includes('tabindex="0"'), stability);
|
|
assert.ok(!stability.includes('role="tooltip"'), stability);
|
|
const legend = renderToStaticMarkup(createElement(StatusBadge, { status: "supported" }));
|
|
assert.ok(!legend.includes("tabindex"), legend);
|
|
});
|