Files
NODEDC_DESIGN_GUIDELINE/scripts/activity-indicator-contract.test.mjs
T
Codex 8c53f73ee5 Standardize action and region loading states
Keep progress inside the initiating Button/IconButton or centered within a
LoadingRegion without changing layout or unmounting live content. Document
ownership and completion/error behavior in the registry and living catalog.

Validation: qualified NET02 DG build/typecheck/registry/loading tests;
existing browser geometry, theme and error lifecycle acceptance. All eleven
committed files match the qualified source artifact byte for byte.
2026-09-10 09:19:12 +03:00

66 lines
3.7 KiB
JavaScript

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 { ActivityIndicator, Button, IconButton, LoadingRegion } from "../packages/ui-react/dist/index.js";
test("ActivityIndicator separates decorative and announced progress", () => {
const decorative = renderToStaticMarkup(createElement(ActivityIndicator, { size: "compact" }));
const announced = renderToStaticMarkup(createElement(ActivityIndicator, { label: "Подключаем устройство" }));
assert.match(decorative, /class="nodedc-activity-indicator"/);
assert.match(decorative, /data-size="compact"/);
assert.match(decorative, /aria-hidden="true"/);
assert.doesNotMatch(decorative, /role="status"/);
assert.match(announced, /role="status"/);
assert.match(announced, /aria-label="Подключаем устройство"/);
assert.doesNotMatch(announced, /aria-hidden/);
assert.doesNotMatch(announced, /data-size=/);
});
test("ActivityIndicator is registered, cataloged and motion-safe", async () => {
const [styles, registrySource, docs, catalog] = await Promise.all([
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((component) => component.id === "activity-indicator");
assert.deepEqual(entry?.variants, ["default", "compact"]);
assert.ok(entry?.exports.includes("ActivityIndicator"));
assert.match(styles, /\.nodedc-activity-indicator\s*\{[\s\S]*?animation: nodedc-activity-indicator-spin/);
assert.match(styles, /\.nodedc-activity-indicator\[data-size="compact"\]/);
assert.match(styles, /@media \(prefers-reduced-motion: reduce\) \{\s*\.nodedc-activity-indicator,[\s\S]*?animation: none/);
assert.match(docs, /## ActivityIndicator/);
assert.match(catalog, /<ActivityIndicator label="Загружаем данные"/);
assert.match(catalog, /<Button loading>/);
assert.match(catalog, /<LoadingRegion loading/);
});
test("pending actions preserve their name and content while disabling only the pending control", () => {
for (const [Component, props] of [[Button, {}], [IconButton, {label: "Обновить"}]]) {
const busy = renderToStaticMarkup(createElement(Component, {...props, loading: true, disabled: false}, "Обновить"));
const idle = renderToStaticMarkup(createElement(Component, props, "Обновить"));
assert.match(busy, /disabled=""/);
assert.match(busy, /aria-busy="true"/);
assert.match(busy, /Обновить/);
assert.equal((busy.match(/class="nodedc-activity-indicator"/g) || []).length, 1);
assert.doesNotMatch(idle, /nodedc-action-loading|disabled=""/);
}
});
test("content remains mounted throughout loading and the status disappears on completion", () => {
const view = createElement("video", {"data-source": "synthetic"});
const pending = renderToStaticMarkup(createElement(LoadingRegion, {loading:true, label:"Ожидаем изображение"}, view));
const ready = renderToStaticMarkup(createElement(LoadingRegion, {loading:false, label:"Ожидаем изображение"}, view));
assert.match(pending, /<video data-source="synthetic"/);
assert.match(ready, /<video data-source="synthetic"/);
assert.equal((pending.match(/role="status"/g) || []).length, 1);
assert.doesNotMatch(ready, /role="status"|nodedc-activity-indicator/);
});