feat(ui): stabilize laboratory viewer primitives
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { Icon } from "../packages/ui-react/dist/index.js";
|
||||
|
||||
test("only registered playback glyphs are filled", async () => {
|
||||
const registry = JSON.parse(await readFile(
|
||||
new URL("../registry/icons.json", import.meta.url),
|
||||
"utf8",
|
||||
));
|
||||
const names = registry.groups.flatMap((group) => group.names);
|
||||
const filledNames = new Set(registry.filledNames);
|
||||
|
||||
assert.deepEqual([...filledNames], ["play", "stop"]);
|
||||
assert.equal(new Set(names).size, names.length);
|
||||
|
||||
for (const name of names) {
|
||||
const markup = renderToStaticMarkup(createElement(Icon, { name }));
|
||||
const rootTag = markup.slice(0, markup.indexOf(">") + 1);
|
||||
if (filledNames.has(name)) {
|
||||
assert.match(rootTag, /fill="currentColor"/, `${name} must be filled`);
|
||||
assert.match(rootTag, /stroke-width="0"/, `${name} must not retain an outline`);
|
||||
} else {
|
||||
assert.match(rootTag, /fill="none"/, `${name} must remain outline-only`);
|
||||
assert.doesNotMatch(rootTag, /fill="currentColor"/, `${name} must not be filled`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { SplitPane } from "../packages/ui-react/dist/index.js";
|
||||
|
||||
test("SplitPane exposes one controlled accessible separator", () => {
|
||||
const markup = renderToStaticMarkup(createElement(SplitPane, {
|
||||
primary: createElement("div", null, "Видео"),
|
||||
secondary: createElement("div", null, "3D"),
|
||||
primarySize: 64,
|
||||
onPrimarySizeChange: () => {},
|
||||
minPrimarySize: 25,
|
||||
minSecondarySize: 30,
|
||||
separatorLabel: "Изменить ширину представлений",
|
||||
}));
|
||||
|
||||
assert.match(markup, /class="nodedc-split-pane"/);
|
||||
assert.match(markup, /role="separator"/);
|
||||
assert.match(markup, /aria-orientation="vertical"/);
|
||||
assert.match(markup, /aria-valuemin="25"/);
|
||||
assert.match(markup, /aria-valuemax="70"/);
|
||||
assert.match(markup, /aria-valuenow="64"/);
|
||||
assert.match(markup, /aria-valuetext="64% \/ 36%"/);
|
||||
});
|
||||
|
||||
test("SplitPane can keep panel ownership stable without an inactive separator", () => {
|
||||
const markup = renderToStaticMarkup(createElement(SplitPane, {
|
||||
primary: createElement("div", null, "Видео"),
|
||||
secondary: createElement("div", null, "3D"),
|
||||
primarySize: 100,
|
||||
onPrimarySizeChange: () => {},
|
||||
separatorLabel: "Изменить ширину представлений",
|
||||
resizable: false,
|
||||
}));
|
||||
assert.match(markup, /data-pane="primary"/);
|
||||
assert.match(markup, /data-pane="secondary"/);
|
||||
assert.doesNotMatch(markup, /role="separator"/);
|
||||
});
|
||||
|
||||
test("SplitPane is registered, documented, cataloged and keyboard-addressable", async () => {
|
||||
const [component, styles, registrySource, docs, catalog] = await Promise.all([
|
||||
readFile(new URL("../packages/ui-react/src/SplitPane.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../packages/ui-core/styles.css", import.meta.url), "utf8"),
|
||||
readFile(new URL("../registry/components.json", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/COMPONENTS.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../apps/catalog/src/CatalogApp.tsx", import.meta.url), "utf8"),
|
||||
]);
|
||||
const registry = JSON.parse(registrySource);
|
||||
const entry = registry.components.find((item) => item.id === "split-pane");
|
||||
|
||||
assert.ok(entry?.exports.includes("SplitPane"));
|
||||
assert.deepEqual(entry?.variants, ["vertical", "horizontal"]);
|
||||
assert.match(component, /event\.key === "Home"/);
|
||||
assert.match(component, /event\.key === "End"/);
|
||||
assert.match(component, /setPointerCapture/);
|
||||
assert.match(component, /activePointerIdRef\.current !== event\.pointerId/);
|
||||
assert.match(component, /role="separator"/);
|
||||
assert.match(styles, /\.nodedc-split-pane__separator:focus-visible::before/);
|
||||
assert.match(docs, /## SplitPane/);
|
||||
assert.match(catalog, /<SplitPane/);
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
const source = readFileSync(
|
||||
new URL("../packages/ui-react/src/Toast.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
test("toast items own independent ten-second terminal lifetimes", () => {
|
||||
assert.match(source, /const DEFAULT_TOAST_DURATION_MS = 10_000/);
|
||||
assert.match(source, /function TimedToastCard/);
|
||||
assert.match(source, /item\.tone === "loading" \? null : DEFAULT_TOAST_DURATION_MS/);
|
||||
assert.match(source, /window\.setTimeout\(\(\) => dismissRef\.current\(item\.id\), duration\)/);
|
||||
assert.match(source, /\[item\.durationMs, item\.id, item\.tone\]/);
|
||||
assert.match(source, /items\.map\(\(item\) => <TimedToastCard key=\{item\.id\}/);
|
||||
assert.doesNotMatch(source, /items\.flatMap/);
|
||||
});
|
||||
Reference in New Issue
Block a user