feat: strengthen module contracts and shared WebUI runtime
This commit is contained in:
@@ -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");
|
||||
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user