Files
ulweb/web/tests/s3-portal.test.mjs
2026-08-23 20:54:07 -05:00

152 lines
5.0 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const webRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const read = (path) => readFileSync(resolve(webRoot, path), "utf8");
const require = createRequire(import.meta.url);
function loadTsxComponent(path) {
const typescript = require("typescript");
const output = typescript.transpileModule(read(path), {
compilerOptions: {
esModuleInterop: true,
jsx: typescript.JsxEmit.ReactJSX,
module: typescript.ModuleKind.CommonJS,
target: typescript.ScriptTarget.ES2020,
},
}).outputText;
const loadedModule = { exports: {} };
new Function("require", "module", "exports", output)(
require,
loadedModule,
loadedModule.exports,
);
return loadedModule.exports;
}
const migratedPresentation = [
"components/Layout.tsx",
"components/PortalUi.tsx",
"pages/index.tsx",
"pages/jobs/[jobId].tsx",
];
test("migrated shell and job pages use reusable classes without inline presentation", () => {
for (const path of migratedPresentation) {
const source = read(path);
assert.doesNotMatch(
source,
/\bstyle\s*=/,
`${path} must not use inline style props`,
);
assert.doesNotMatch(
source,
/#[\da-f]{3,8}\b/i,
`${path} must not define visual color literals`,
);
}
});
test("the authenticated shell keeps organization and navigation context accessible", () => {
const layout = read("components/Layout.tsx");
assert.match(layout, /Active organization/);
assert.match(layout, /activeOrg\.role/);
assert.match(layout, /aria-current=/);
assert.match(layout, /Skip to main content/);
assert.match(layout, /aria-label="Primary navigation"/);
assert.match(layout, /src="\/um-trace-mark\.png"/);
assert.doesNotMatch(layout, />\s*UL\s*</);
});
test("the portal uses the cropped UM foreground without nested icon padding", () => {
const layout = read("components/Layout.tsx");
const css = read("styles/global.css");
assert.match(layout, /src="\/um-trace-mark\.png"/);
assert.doesNotMatch(layout, /um-trace-icon\.svg/);
assert.match(css, /\.ul-shell__brand-mark\s*\{[^}]*object-fit:\s*contain;/s);
assert.match(css, /\.ul-shell__brand-mark\s*\{[^}]*padding:\s*0\.375rem;/s);
});
test("status presentation always combines a readable label with an icon", () => {
const components = read("components/PortalUi.tsx");
for (const status of [
"OPEN",
"IN_PROGRESS",
"COMPLETED",
"CANCELLED",
"LIVE",
"OFFLINE",
"ERROR",
]) {
assert.match(
components,
new RegExp(`${status}: \\{ icon: .+, label: .+, tone:`),
);
}
assert.match(components, /ul-status-badge__icon/);
assert.match(components, /aria-hidden="true"/);
});
test("status and error-state components render accessible text and semantics", () => {
const React = require("react");
const { renderToStaticMarkup } = require("react-dom/server");
const { PageState, StatusBadge } = loadTsxComponent(
"components/PortalUi.tsx",
);
const status = renderToStaticMarkup(
React.createElement(StatusBadge, { status: "IN_PROGRESS" }),
);
assert.match(status, />In progress</);
assert.match(status, /ul-status-badge--warning/);
assert.match(status, /aria-hidden="true"/);
const error = renderToStaticMarkup(
React.createElement(PageState, {
kind: "error",
title: "Jobs could not be loaded",
}),
);
assert.match(error, /role="alert"/);
assert.match(error, /Jobs could not be loaded/);
});
test("responsive, focus, touch-target, and reduced-motion rules are present", () => {
const css = read("styles/global.css");
assert.ok(css.startsWith('@import "./tokens.css";'));
for (const token of [
"--ul-color-canvas",
"--ul-color-primary",
"--ul-color-map",
"--ul-touch-minimum",
"--ul-type-family-sans",
]) {
assert.match(css, new RegExp(`var\\(${token}\\)`));
}
assert.match(css, /:focus-visible/);
assert.match(css, /min-height:\s*var\(--ul-touch-minimum\)/);
assert.match(css, /@media \(max-width: 1200px\)/);
assert.match(css, /@media \(max-width: 900px\)/);
assert.match(css, /@media \(max-width: 600px\)/);
assert.match(css, /@media \(prefers-reduced-motion: reduce\)/);
});
test("job pages preserve organization-scoped APIs, streaming, and provider-neutral map usage", () => {
const jobs = read("pages/index.tsx");
const detail = read("pages/jobs/[jobId].tsx");
const dispatcher = read("components/map/JobMap.tsx");
const stream = read("lib/use-job-stream.ts");
assert.match(jobs, /\/api\/orgs\/\$\{activeOrg\.org\.id\}\/jobs/);
assert.match(detail, /\/api\/orgs\/\$\{orgId\}\/jobs\/\$\{jobId\}/);
assert.match(detail, /<JobMap/);
assert.doesNotMatch(detail, /EsriJobMap/);
assert.match(dispatcher, /dynamic\(\(\) => import\('\.\/esri\/EsriJobMap'\)/);
assert.match(stream, /channel: `job:\$\{jobId\}`/);
assert.match(stream, /return connectionState/);
});