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, //); assert.match(catalog, / { 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, /