feat(sprint-3): refresh responsive UlHub portal
This commit is contained in:
140
web/tests/s3-portal.test.mjs
Normal file
140
web/tests/s3-portal.test.mjs
Normal file
@@ -0,0 +1,140 @@
|
||||
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"/);
|
||||
});
|
||||
|
||||
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/);
|
||||
});
|
||||
Reference in New Issue
Block a user