feat: consolidate shared UI and harden browser authority for release
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
DashboardWidgetsUiCapability,
|
||||
OrganizationFunctionActionContext,
|
||||
OrganizationFunctionActionContribution,
|
||||
PlatformNavItem,
|
||||
PlatformWebModule
|
||||
} from "../src/types";
|
||||
import {
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
visibleRoutesForProjection
|
||||
} from "../src/platform/views";
|
||||
import { groupNavigationItems } from "../src/platform/productAreas";
|
||||
import { inheritedNavigationLayout, materializeNavigationLayout, moveNavigationEntry, navigationEditorOrder } from "../src/components/navigationPreferenceLayout";
|
||||
import {
|
||||
composeProductSurfaces,
|
||||
projectProductNavigation
|
||||
@@ -197,6 +199,24 @@ assert(
|
||||
"flat navigation should retain every authorized destination"
|
||||
);
|
||||
|
||||
const railItems = [
|
||||
{ to: "/files", label: "Files", surfaceId: "files", navigationId: "files", navigationCustomLayout: true, navigationLayoutSource: "tenant", navigationSection: { id: "separator:a", label: "A" } },
|
||||
{ to: "/mail", label: "Mail", surfaceId: "mail", navigationId: "messages", navigationLocked: true, navigationCustomLayout: true, navigationLayoutSource: "tenant", navigationSection: { id: "separator:b", label: "B" } }
|
||||
];
|
||||
assert(groupNavigationItems(railItems, []).map((group) => group.label).join() === "A,B", "explicit separators survive module-default grouping and filtering");
|
||||
const viewNavigation = { contract_version: "1" as const, order: ["messages", "separator:view", "files"], hidden: ["mail"], separators: [{ id: "separator:view", label: "View" }] };
|
||||
const viewGroups = groupNavigationItems(railItems, [], { navigation: viewNavigation });
|
||||
assert(viewGroups[0]?.items[0]?.to === "/mail", "View layout supports navigation aliases and cannot hide locked module");
|
||||
assert(viewGroups[1]?.label === "View" && viewGroups.flatMap((group) => group.items).length === 2, "View separators retain every authorized item once");
|
||||
assert(groupNavigationItems(railItems.map((item) => ({ ...item, navigationLayoutSource: "user" })), [], { navigation: viewNavigation })[0]?.items[0]?.to === "/files", "personal layout takes precedence over View presentation order");
|
||||
assert(groupNavigationItems(railItems.map((item) => ({ ...item, navigationVisibilitySource: "user" })), [], { navigation: { ...viewNavigation, hidden: ["files"] } }).flatMap((group) => group.items).some((item) => item.to === "/files"), "legacy personal visibility-only preferences also take precedence over View navigation hiding");
|
||||
const inheritedRail = inheritedNavigationLayout(railItems, "system", []);
|
||||
assert(inheritedRail.order.join() === "files,messages", "system baseline ignores current tenant grouping");
|
||||
const materialized = materializeNavigationLayout({ contract_version: "1", order: ["mail", "files"], hidden: [] }, { contract_version: "1", order: ["separator:work", "files", "mail"], hidden: [], separators: [{ id: "separator:work", label: "Work" }] });
|
||||
assert(materialized.order.join() === "separator:work,mail,files", "legacy drafts gain inherited separator without losing their item order");
|
||||
assert(moveNavigationEntry(["a", "separator:b", "c"], "c", "a").join() === "c,a,separator:b", "drag reorder treats separators as ordinary ordered entries");
|
||||
assert(navigationEditorOrder(railItems, { contract_version: "1", order: ["files", "messages"], hidden: ["files"], separators: [] }).join() === "messages", "removed modules leave editor list while remaining available for re-add");
|
||||
|
||||
const productSurfaceExplanation = {
|
||||
reason: "authorization" as const,
|
||||
title: "Messages are unavailable",
|
||||
@@ -293,6 +313,25 @@ const mailOnlyNavigation = projectProductNavigation(
|
||||
messageSurfaceModules,
|
||||
{ scopes: ["mail:mailbox:read"] } as AuthInfo
|
||||
);
|
||||
|
||||
const reorderedOwners: PlatformNavItem[] = [
|
||||
{ to: "/postbox", label: "Postbox", order: 0, navigationId: "postbox.navigation.postbox", surfaceId: "postbox.navigation.postbox", anyOf: ["postbox:message:read"], navigationLocked: true, navigationLockSource: "system", navigationCustomLayout: true, navigationSection: { id: "separator:locked", label: "Locked group" } },
|
||||
{ to: "/files", label: "Files", order: 1 },
|
||||
{ to: "/mail", label: "Mail", order: 2, navigationId: "mail.navigation.mail", surfaceId: "mail.navigation.mail", anyOf: ["mail:mailbox:read"] }
|
||||
];
|
||||
const reorderedProductNavigation = projectProductNavigation(reorderedOwners, messageSurfaceModules, { scopes: ["mail:mailbox:read", "postbox:message:read"] } as AuthInfo);
|
||||
const reorderedMessages = reorderedProductNavigation.primaryItems[0];
|
||||
assert(reorderedProductNavigation.primaryItems.map((item) => item.to).join() === "/messages,/files", "composed placement follows earliest effective owner order rather than fixed contributor priority");
|
||||
assert(reorderedMessages.navigationSection?.id === "separator:locked", "composed entry keeps the earliest owner separator");
|
||||
assert(reorderedMessages.navigationAliases?.includes("mail.navigation.mail") && reorderedMessages.navigationAliases?.includes("postbox.navigation.postbox"), "composition retains every consumed owner navigation ID for layout aliases");
|
||||
assert(reorderedMessages.navigationLocked && reorderedMessages.navigationLockSource === "system", "a secondary owner's system lock survives composition with an unlocked primary owner");
|
||||
assert(composeProductSurfaces(messageSurfaceModules)[0].contributors[0].moduleId === "mail", "custom rail placement never changes operational contributor priority");
|
||||
const lockedAfterView = groupNavigationItems(reorderedProductNavigation.primaryItems, [], { navigation: { contract_version: "1", order: [], hidden: ["communication.messages", "mail.navigation.mail", "postbox.navigation.postbox"], separators: [] } });
|
||||
assert(lockedAfterView.flatMap((group) => group.items).some((item) => item.to === "/messages"), "View aliases cannot hide a composed entry locked by any consumed authorized owner");
|
||||
const aliasOrdered = groupNavigationItems(reorderedProductNavigation.primaryItems, [], { navigation: { contract_version: "1", order: ["/files", "separator:view", "mail.navigation.mail"], hidden: [], separators: [{ id: "separator:view", label: "View group" }] } });
|
||||
assert(aliasOrdered[0]?.items[0]?.to === "/files" && aliasOrdered[1]?.items[0]?.to === "/messages" && aliasOrdered[1]?.label === "View group", "View ordering recognizes non-placement owner aliases and assigns the requested separator exactly once");
|
||||
const unauthorizedLockedOwner = projectProductNavigation(reorderedOwners, messageSurfaceModules, { scopes: ["mail:mailbox:read"] } as AuthInfo).primaryItems.find((item) => item.to === "/messages");
|
||||
assert(unauthorizedLockedOwner?.navigationLocked === false && !unauthorizedLockedOwner.navigationAliases?.includes("postbox.navigation.postbox"), "unavailable optional owners contribute neither aliases nor lock metadata");
|
||||
assert(
|
||||
mailOnlyNavigation.primaryItems.map((item) => item.to).join(",") === "/messages",
|
||||
"composition should remain stable when only one optional contributor is authorized"
|
||||
|
||||
Reference in New Issue
Block a user