feat: strengthen module contracts and shared WebUI runtime

This commit is contained in:
2026-07-29 14:16:28 +02:00
parent 53e947935a
commit 68328f3d8e
57 changed files with 4358 additions and 371 deletions

View File

@@ -1,4 +1,5 @@
import type {
DashboardWidgetsUiCapability,
OrganizationFunctionActionContext,
OrganizationFunctionActionContribution,
PlatformWebModule
@@ -71,6 +72,51 @@ for (const testCase of cases) {
assert(uiCapability("files.fileExplorer", [access, files]) === filesCapability, "files capability should return the module-provided object");
assert(uiCapability("mail.profiles", [access, mail]) === mailCapability, "mail capability should return the module-provided object");
const configurableDashboardWidgets: DashboardWidgetsUiCapability = {
widgets: [
{
id: "files.recent",
surfaceId: "files.widget.recent",
title: "Recent files",
moduleId: "files",
supportedSizes: ["medium", "wide"],
defaultConfiguration: { limit: 10, includeFolders: false },
configurationFields: [
{
id: "limit",
label: "Items",
kind: "number",
min: 1,
max: 50
},
{
id: "includeFolders",
label: "Include folders",
kind: "boolean"
}
],
render: ({ configuration, effectiveView, instanceId }) =>
`${instanceId}:${effectiveView?.activeViewId ?? "full"}:${String(configuration.limit)}`
}
]
};
const dashboardProvider: PlatformWebModule = {
id: "dashboard-provider",
label: "Dashboard provider",
version: "test",
uiCapabilities: {
"dashboard.widgets": configurableDashboardWidgets
}
};
const resolvedDashboardWidgets = uiCapability<DashboardWidgetsUiCapability>(
"dashboard.widgets",
[dashboardProvider]
);
assert(
resolvedDashboardWidgets?.widgets[0].configurationFields?.length === 2,
"dashboard widget capabilities should retain provider-defined configuration"
);
const routes = routeContributionsForModules([access, files, mail, campaigns]).map((route) => route.path);
assert(routes.join(",") === "/campaigns,/files,/mail", "routes should aggregate and sort by contribution order");

View File

@@ -3,6 +3,14 @@ function assert(condition: unknown, message = "assertion failed"): void {
}
import { renderToStaticMarkup } from "react-dom/server";
import SearchableSelect, {
filterSearchableSelectOptions
} from "../src/components/SearchableSelect";
import {
ReferenceMultiSelect,
staticReferenceOptionProvider,
wildcardReferenceOption
} from "../src/components/ReferenceSelect";
import SelectionList, { SelectionListItem } from "../src/components/SelectionList";
import { PlatformLanguageProvider } from "../src/i18n/LanguageContext";
@@ -50,3 +58,87 @@ const nativeLabelMarkup = renderToStaticMarkup(
</SelectionList>
);
assert(nativeLabelMarkup.includes('aria-label="Already translated"'), "native accessible labels pass through unchanged");
const directoryOptions = [
{
value: "account-1",
label: "Ada Lovelace",
description: "ada@example.test"
},
{
value: "group-1",
label: "Finance",
searchText: "cost centre"
}
];
assert(
filterSearchableSelectOptions(directoryOptions, "example").length === 1,
"searchable selects match option descriptions"
);
assert(
filterSearchableSelectOptions(directoryOptions, "cost centre")[0]?.value ===
"group-1",
"searchable selects include explicit search aliases"
);
const searchableMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<SearchableSelect
id="assignment-target"
aria-label="Assignment target"
value="account-1"
options={directoryOptions}
onChange={() => undefined}
/>
</PlatformLanguageProvider>
);
assert(
searchableMarkup.includes('role="combobox"'),
"searchable selects expose combobox semantics"
);
assert(
searchableMarkup.includes('aria-controls="assignment-target-results"'),
"searchable selects associate their input and result list"
);
assert(
searchableMarkup.includes('value="Ada Lovelace"'),
"searchable selects render labels instead of technical ids"
);
const referenceMarkup = renderToStaticMarkup(
<PlatformLanguageProvider>
<ReferenceMultiSelect
id="policy-references"
aria-label="Policy references"
values={["group-1", "legacy-*", "removed"]}
onChange={() => undefined}
selectedOptions={[
{
value: "group-1",
label: "Finance",
description: "Access group"
}
]}
provider={staticReferenceOptionProvider([
{
value: "group-1",
label: "Finance",
description: "Access group"
}
])}
createCustomOption={(value) => wildcardReferenceOption(value)}
/>
</PlatformLanguageProvider>
);
assert(
referenceMarkup.includes("Finance"),
"reference multi-selects render known labels"
);
assert(
referenceMarkup.includes("Pattern"),
"reference multi-selects distinguish wildcard patterns"
);
assert(
referenceMarkup.includes("Unavailable reference"),
"reference multi-selects retain unavailable values"
);