feat(web): rationalize settings IA into personal-preferences shell with admin rehoming [SPRINT-026]
Settings shell now owns only personal user preferences (appearance, language, layout, AI assistant). All 14 admin/tenant/ops leaves converted to controlled redirects pointing at their canonical owners (Administration, Setup, Ops). Language merged into user-preferences. Identity-providers rehomed from settings to administration as canonical owner. Navigation config updated. 22 new route tests added. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -637,7 +637,7 @@ export const NAVIGATION_GROUPS: NavGroup[] = [
|
||||
{
|
||||
id: 'identity-providers',
|
||||
label: 'Identity Providers',
|
||||
route: '/settings/identity-providers',
|
||||
route: '/administration/identity-providers',
|
||||
icon: 'id-card',
|
||||
requiredScopes: ['ui.admin'],
|
||||
tooltip: 'Configure external identity providers (LDAP, SAML, OIDC)',
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
/**
|
||||
* Settings Page Component (Shell)
|
||||
* Sprint: SPRINT_20260118_002_FE_settings_consolidation (SETTINGS-001)
|
||||
* Settings Page Component (Personal Preferences Shell)
|
||||
* Sprint: SPRINT_20260308_026_FE_settings_information_architecture_rationalization
|
||||
*
|
||||
* Shell page with sidebar navigation for all settings sections.
|
||||
* The Settings shell now owns only personal user preferences.
|
||||
* Admin, tenant, and operations configuration have been rehomed
|
||||
* to their canonical owners (Setup, Administration, Ops).
|
||||
*/
|
||||
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Settings Page Component (Shell)
|
||||
*
|
||||
* Navigation is handled by the global sidebar.
|
||||
* This shell provides the content area for settings sub-routes.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-settings-page',
|
||||
imports: [RouterOutlet],
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
/**
|
||||
* Settings Routes
|
||||
* Sprint: SPRINT_20260118_002_FE_settings_consolidation
|
||||
* Settings Routes — Personal Preferences Shell
|
||||
* Sprint: SPRINT_20260308_026_FE_settings_information_architecture_rationalization
|
||||
*
|
||||
* Settings now owns only personal user preferences (appearance, language,
|
||||
* layout, AI assistant). All admin, tenant, and operations configuration
|
||||
* leaves have been rehomed to their canonical owners with backward-compatible
|
||||
* redirects preserved for legacy bookmarks.
|
||||
*/
|
||||
|
||||
import { inject } from '@angular/core';
|
||||
import { Router, Routes } from '@angular/router';
|
||||
|
||||
function redirectToCanonicalSetup(path: string) {
|
||||
function redirectToCanonical(path: string) {
|
||||
return ({
|
||||
params,
|
||||
queryParams,
|
||||
@@ -38,125 +43,15 @@ export const SETTINGS_ROUTES: Routes = [
|
||||
import('./settings-page.component').then(m => m.SettingsPageComponent),
|
||||
data: {},
|
||||
children: [
|
||||
// -----------------------------------------------------------------------
|
||||
// Personal preferences (canonical owner: Settings)
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: '',
|
||||
title: 'Integrations',
|
||||
title: 'User Preferences',
|
||||
loadComponent: () =>
|
||||
import('./integrations/integrations-settings-page.component').then(m => m.IntegrationsSettingsPageComponent),
|
||||
data: { breadcrumb: 'Integrations' },
|
||||
},
|
||||
{
|
||||
path: 'integrations',
|
||||
title: 'Integrations',
|
||||
loadComponent: () =>
|
||||
import('./integrations/integrations-settings-page.component').then(m => m.IntegrationsSettingsPageComponent),
|
||||
data: { breadcrumb: 'Integrations' },
|
||||
},
|
||||
{
|
||||
path: 'integrations/:id',
|
||||
title: 'Integration Detail',
|
||||
loadComponent: () =>
|
||||
import('./integrations/integration-detail-page.component').then(m => m.IntegrationDetailPageComponent),
|
||||
data: { breadcrumb: 'Integration Detail' },
|
||||
},
|
||||
{
|
||||
path: 'configuration-pane',
|
||||
title: 'Configuration Pane',
|
||||
loadComponent: () =>
|
||||
import('../configuration-pane/components/configuration-pane.component').then(m => m.ConfigurationPaneComponent),
|
||||
data: { breadcrumb: 'Configuration Pane' },
|
||||
},
|
||||
{
|
||||
path: 'release-control',
|
||||
title: 'Release Control',
|
||||
loadComponent: () =>
|
||||
import('./release-control/release-control-settings-page.component').then(m => m.ReleaseControlSettingsPageComponent),
|
||||
data: { breadcrumb: 'Release Control' },
|
||||
},
|
||||
{
|
||||
path: 'trust',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust/issuers',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing/issuers'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust/:page/:child',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing/:page/:child'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust/:page',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing/:page'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust-signing',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust-signing/:page',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing/:page'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust-signing/:page/:child',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonicalSetup('/setup/trust-signing/:page/:child'),
|
||||
data: { breadcrumb: 'Trust & Signing' },
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'security-data',
|
||||
title: 'Security Data',
|
||||
loadComponent: () =>
|
||||
import('./security-data/security-data-settings-page.component').then(m => m.SecurityDataSettingsPageComponent),
|
||||
data: { breadcrumb: 'Security Data' },
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
title: 'Identity & Access',
|
||||
loadComponent: () =>
|
||||
import('./admin/admin-settings-page.component').then(m => m.AdminSettingsPageComponent),
|
||||
data: { breadcrumb: 'Identity & Access' },
|
||||
},
|
||||
{
|
||||
path: 'admin/:page',
|
||||
title: 'Identity & Access',
|
||||
loadComponent: () =>
|
||||
import('./admin/admin-settings-page.component').then(m => m.AdminSettingsPageComponent),
|
||||
data: { breadcrumb: 'Identity & Access' },
|
||||
},
|
||||
{
|
||||
path: 'branding',
|
||||
title: 'Tenant & Branding',
|
||||
loadComponent: () =>
|
||||
import('./branding/branding-settings-page.component').then(m => m.BrandingSettingsPageComponent),
|
||||
data: { breadcrumb: 'Tenant & Branding' },
|
||||
},
|
||||
{
|
||||
path: 'usage',
|
||||
title: 'Usage & Limits',
|
||||
loadComponent: () =>
|
||||
import('./usage/usage-settings-page.component').then(m => m.UsageSettingsPageComponent),
|
||||
data: { breadcrumb: 'Usage & Limits' },
|
||||
},
|
||||
{
|
||||
path: 'notifications',
|
||||
title: 'Notifications',
|
||||
loadComponent: () =>
|
||||
import('./notifications/notifications-settings-page.component').then(m => m.NotificationsSettingsPageComponent),
|
||||
data: { breadcrumb: 'Notifications' },
|
||||
import('./user-preferences/user-preferences-page.component').then(m => m.UserPreferencesPageComponent),
|
||||
data: { breadcrumb: 'User Preferences' },
|
||||
},
|
||||
{
|
||||
path: 'user-preferences',
|
||||
@@ -165,12 +60,15 @@ export const SETTINGS_ROUTES: Routes = [
|
||||
import('./user-preferences/user-preferences-page.component').then(m => m.UserPreferencesPageComponent),
|
||||
data: { breadcrumb: 'User Preferences' },
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Merged personal preference leaves (redirects to user-preferences)
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: 'language',
|
||||
title: 'Language',
|
||||
loadComponent: () =>
|
||||
import('./language/language-settings-page.component').then(m => m.LanguageSettingsPageComponent),
|
||||
data: { breadcrumb: 'Language' },
|
||||
redirectTo: 'user-preferences',
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'ai-preferences',
|
||||
@@ -178,35 +76,143 @@ export const SETTINGS_ROUTES: Routes = [
|
||||
redirectTo: 'user-preferences',
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Admin/tenant config redirects -> canonical administration owner
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: 'policy',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('./policy/policy-governance-settings-page.component').then(m => m.PolicyGovernanceSettingsPageComponent),
|
||||
data: { breadcrumb: 'Policy Governance' },
|
||||
path: 'admin',
|
||||
title: 'Identity & Access',
|
||||
redirectTo: redirectToCanonical('/administration/admin'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'offline',
|
||||
title: 'Offline Settings',
|
||||
loadComponent: () =>
|
||||
import('../offline-kit/components/offline-dashboard.component').then(m => m.OfflineDashboardComponent),
|
||||
data: { breadcrumb: 'Offline Settings' },
|
||||
path: 'admin/:page',
|
||||
title: 'Identity & Access',
|
||||
redirectTo: redirectToCanonical('/administration/admin/:page'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'branding',
|
||||
title: 'Tenant & Branding',
|
||||
redirectTo: redirectToCanonical('/console/admin/branding'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'usage',
|
||||
title: 'Usage & Limits',
|
||||
redirectTo: redirectToCanonical('/setup/usage'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'notifications',
|
||||
title: 'Notifications',
|
||||
redirectTo: redirectToCanonical('/setup/notifications'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'identity-providers',
|
||||
title: 'Identity Providers',
|
||||
loadComponent: () =>
|
||||
import('./identity-providers/identity-providers-settings-page.component').then(
|
||||
(m) => m.IdentityProvidersSettingsPageComponent,
|
||||
),
|
||||
data: { breadcrumb: 'Identity Providers' },
|
||||
redirectTo: redirectToCanonical('/administration/identity-providers'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
title: 'System',
|
||||
loadComponent: () =>
|
||||
import('./system/system-settings-page.component').then(m => m.SystemSettingsPageComponent),
|
||||
data: { breadcrumb: 'System' },
|
||||
redirectTo: redirectToCanonical('/administration/system'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'security-data',
|
||||
title: 'Security Data',
|
||||
redirectTo: redirectToCanonical('/administration/security-data'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Operations config redirects -> canonical ops/setup owners
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: 'integrations',
|
||||
title: 'Integrations',
|
||||
redirectTo: redirectToCanonical('/setup/integrations'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'integrations/:id',
|
||||
title: 'Integration Detail',
|
||||
redirectTo: redirectToCanonical('/setup/integrations/:id'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'policy',
|
||||
title: 'Policy Governance',
|
||||
redirectTo: redirectToCanonical('/ops/policy/governance'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'offline',
|
||||
title: 'Offline Settings',
|
||||
redirectTo: redirectToCanonical('/administration/offline'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'release-control',
|
||||
title: 'Release Control',
|
||||
redirectTo: redirectToCanonical('/setup/topology/environments'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'configuration-pane',
|
||||
title: 'Configuration Pane',
|
||||
redirectTo: redirectToCanonical('/ops/platform-setup'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Trust redirects (already existed, preserved)
|
||||
// -----------------------------------------------------------------------
|
||||
{
|
||||
path: 'trust',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust/issuers',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing/issuers'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust/:page/:child',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing/:page/:child'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust/:page',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing/:page'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust-signing',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust-signing/:page',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing/:page'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
{
|
||||
path: 'trust-signing/:page/:child',
|
||||
title: 'Trust & Signing',
|
||||
redirectTo: redirectToCanonical('/setup/trust-signing/:page/:child'),
|
||||
pathMatch: 'full' as const,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -351,11 +351,15 @@ export const ADMINISTRATION_ROUTES: Routes = [
|
||||
pathMatch: 'full',
|
||||
},
|
||||
|
||||
// Legacy alias: /administration/identity-providers → /settings/identity-providers
|
||||
// Identity Providers — canonical owner (rehomed from /settings/identity-providers)
|
||||
{
|
||||
path: 'identity-providers',
|
||||
redirectTo: '/settings/identity-providers',
|
||||
pathMatch: 'full',
|
||||
title: 'Identity Providers',
|
||||
data: { breadcrumb: 'Identity Providers' },
|
||||
loadComponent: () =>
|
||||
import('../features/settings/identity-providers/identity-providers-settings-page.component').then(
|
||||
(m) => m.IdentityProvidersSettingsPageComponent
|
||||
),
|
||||
},
|
||||
|
||||
// A7 — System
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Settings IA Rationalization Tests
|
||||
* Sprint: SPRINT_20260308_026_FE_settings_information_architecture_rationalization
|
||||
*
|
||||
* Verifies that the Settings shell now owns only personal preferences,
|
||||
* admin/tenant/ops leaves redirect to canonical owners, and legacy
|
||||
* bookmarks resolve through controlled redirects.
|
||||
*/
|
||||
|
||||
import { SETTINGS_ROUTES } from '../../app/features/settings/settings.routes';
|
||||
|
||||
describe('Settings IA rationalization', () => {
|
||||
const root = SETTINGS_ROUTES[0];
|
||||
const children = root?.children ?? [];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Personal preferences are the canonical default
|
||||
// ---------------------------------------------------------------------------
|
||||
it('defaults to user-preferences as the settings landing page', () => {
|
||||
const defaultRoute = children.find((r) => r.path === '');
|
||||
expect(defaultRoute).toBeDefined();
|
||||
expect(defaultRoute?.title).toBe('User Preferences');
|
||||
expect(typeof defaultRoute?.loadComponent).toBe('function');
|
||||
});
|
||||
|
||||
it('mounts user-preferences as a named route', () => {
|
||||
const route = children.find((r) => r.path === 'user-preferences');
|
||||
expect(route).toBeDefined();
|
||||
expect(route?.title).toBe('User Preferences');
|
||||
expect(typeof route?.loadComponent).toBe('function');
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Merged personal preference leaves redirect to user-preferences
|
||||
// ---------------------------------------------------------------------------
|
||||
it('redirects /settings/language to user-preferences', () => {
|
||||
const route = children.find((r) => r.path === 'language');
|
||||
expect(route).toBeDefined();
|
||||
expect(route?.redirectTo).toBe('user-preferences');
|
||||
});
|
||||
|
||||
it('redirects /settings/ai-preferences to user-preferences', () => {
|
||||
const route = children.find((r) => r.path === 'ai-preferences');
|
||||
expect(route).toBeDefined();
|
||||
expect(route?.redirectTo).toBe('user-preferences');
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Admin/tenant config leaves redirect to canonical administration
|
||||
// ---------------------------------------------------------------------------
|
||||
it('redirects /settings/admin to canonical administration', () => {
|
||||
const route = children.find((r) => r.path === 'admin');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
expect(route?.pathMatch).toBe('full');
|
||||
});
|
||||
|
||||
it('redirects /settings/admin/:page to canonical administration', () => {
|
||||
const route = children.find((r) => r.path === 'admin/:page');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/branding to canonical console admin branding', () => {
|
||||
const route = children.find((r) => r.path === 'branding');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/identity-providers to canonical administration', () => {
|
||||
const route = children.find((r) => r.path === 'identity-providers');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/system to canonical administration', () => {
|
||||
const route = children.find((r) => r.path === 'system');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/security-data to canonical administration', () => {
|
||||
const route = children.find((r) => r.path === 'security-data');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Operations config leaves redirect to canonical setup/ops
|
||||
// ---------------------------------------------------------------------------
|
||||
it('redirects /settings/integrations to canonical setup', () => {
|
||||
const route = children.find((r) => r.path === 'integrations');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/integrations/:id to canonical setup', () => {
|
||||
const route = children.find((r) => r.path === 'integrations/:id');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/usage to canonical setup', () => {
|
||||
const route = children.find((r) => r.path === 'usage');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/notifications to canonical setup', () => {
|
||||
const route = children.find((r) => r.path === 'notifications');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/policy to canonical ops policy governance', () => {
|
||||
const route = children.find((r) => r.path === 'policy');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/offline to canonical administration', () => {
|
||||
const route = children.find((r) => r.path === 'offline');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/release-control to canonical setup topology', () => {
|
||||
const route = children.find((r) => r.path === 'release-control');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
it('redirects /settings/configuration-pane to canonical ops platform-setup', () => {
|
||||
const route = children.find((r) => r.path === 'configuration-pane');
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route?.redirectTo).toBe('function');
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trust redirects preserved from previous sprint
|
||||
// ---------------------------------------------------------------------------
|
||||
it('preserves trust/* redirects to /setup/trust-signing', () => {
|
||||
const trustRoot = children.find((r) => r.path === 'trust');
|
||||
expect(trustRoot).toBeDefined();
|
||||
expect(typeof trustRoot?.redirectTo).toBe('function');
|
||||
|
||||
const trustIssuers = children.find((r) => r.path === 'trust/issuers');
|
||||
expect(trustIssuers).toBeDefined();
|
||||
|
||||
const trustPage = children.find((r) => r.path === 'trust/:page');
|
||||
expect(trustPage).toBeDefined();
|
||||
|
||||
const trustPageChild = children.find((r) => r.path === 'trust/:page/:child');
|
||||
expect(trustPageChild).toBeDefined();
|
||||
});
|
||||
|
||||
it('preserves trust-signing/* redirects to /setup/trust-signing', () => {
|
||||
const ts = children.find((r) => r.path === 'trust-signing');
|
||||
expect(ts).toBeDefined();
|
||||
expect(typeof ts?.redirectTo).toBe('function');
|
||||
|
||||
const tsPage = children.find((r) => r.path === 'trust-signing/:page');
|
||||
expect(tsPage).toBeDefined();
|
||||
|
||||
const tsPageChild = children.find((r) => r.path === 'trust-signing/:page/:child');
|
||||
expect(tsPageChild).toBeDefined();
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No loadComponent routes remain for admin/ops pages
|
||||
// ---------------------------------------------------------------------------
|
||||
it('contains no loadComponent routes for admin/ops leaves', () => {
|
||||
const adminOpsLeaves = [
|
||||
'integrations', 'integrations/:id', 'admin', 'admin/:page',
|
||||
'branding', 'usage', 'notifications', 'security-data', 'policy',
|
||||
'offline', 'system', 'identity-providers', 'release-control',
|
||||
'configuration-pane',
|
||||
];
|
||||
|
||||
for (const path of adminOpsLeaves) {
|
||||
const route = children.find((r) => r.path === path);
|
||||
if (route) {
|
||||
expect(route.loadComponent).toBeUndefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Route count validation
|
||||
// ---------------------------------------------------------------------------
|
||||
it('has the expected number of child routes', () => {
|
||||
// 2 personal preference routes + 2 merged redirects + 8 admin redirects
|
||||
// + 6 ops redirects + 7 trust redirects = 25
|
||||
expect(children.length).toBe(25);
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { routes } from '../../app/app.routes';
|
||||
import { ADVISORY_AI_API, type AdvisoryAiApi } from '../../app/core/api/advisory-ai.client';
|
||||
import type { RemediationPrSettings } from '../../app/core/api/advisory-ai.models';
|
||||
import { RemediationPrSettingsComponent } from '../../app/features/settings/remediation-pr-settings.component';
|
||||
@@ -23,46 +22,33 @@ describe('unified-settings-page behavior', () => {
|
||||
localStorage.removeItem('stellaops.remediation-pr.preferences');
|
||||
});
|
||||
|
||||
it('declares canonical /administration route and keeps /settings redirect alias', () => {
|
||||
const settingsAlias = routes.find((route) => route.path === 'settings');
|
||||
expect(settingsAlias).toBeDefined();
|
||||
expect(settingsAlias?.redirectTo).toBe('/administration');
|
||||
|
||||
const administrationRoute = routes.find((route) => route.path === 'administration');
|
||||
expect(administrationRoute).toBeDefined();
|
||||
expect(typeof administrationRoute?.loadChildren).toBe('function');
|
||||
|
||||
it('mounts personal preferences as the settings default and redirects admin leaves', () => {
|
||||
const root = SETTINGS_ROUTES.find((route) => route.path === '');
|
||||
expect(root).toBeDefined();
|
||||
const childPaths = (root?.children ?? []).map((child) => child.path);
|
||||
|
||||
expect(childPaths).toEqual([
|
||||
'',
|
||||
'integrations',
|
||||
'integrations/:id',
|
||||
'configuration-pane',
|
||||
'release-control',
|
||||
'trust',
|
||||
'trust/:page',
|
||||
'security-data',
|
||||
'admin',
|
||||
'admin/:page',
|
||||
'branding',
|
||||
'usage',
|
||||
'notifications',
|
||||
'ai-preferences',
|
||||
'policy',
|
||||
'offline',
|
||||
'system',
|
||||
]);
|
||||
// The default route is now user-preferences, not integrations
|
||||
const defaultChild = (root?.children ?? []).find((child) => child.path === '');
|
||||
expect(defaultChild?.title).toBe('User Preferences');
|
||||
expect(typeof defaultChild?.loadComponent).toBe('function');
|
||||
|
||||
const brandingRoute = (root?.children ?? []).find((child) => child.path === 'branding');
|
||||
expect(brandingRoute?.title).toBe('Tenant & Branding');
|
||||
expect(brandingRoute?.data?.['breadcrumb']).toBe('Tenant & Branding');
|
||||
// user-preferences is also available as a named route
|
||||
expect(childPaths).toContain('user-preferences');
|
||||
|
||||
const offlineRoute = (root?.children ?? []).find((child) => child.path === 'offline');
|
||||
expect(offlineRoute?.title).toBe('Offline Settings');
|
||||
expect(offlineRoute?.data?.['breadcrumb']).toBe('Offline Settings');
|
||||
// Admin/ops leaves are redirects, not loadComponent pages
|
||||
const adminRedirects = ['admin', 'branding', 'integrations', 'notifications', 'usage', 'system', 'offline', 'policy', 'security-data', 'identity-providers'];
|
||||
for (const path of adminRedirects) {
|
||||
const route = (root?.children ?? []).find((child) => child.path === path);
|
||||
expect(route).toBeDefined();
|
||||
expect(route?.loadComponent).toBeUndefined();
|
||||
}
|
||||
|
||||
// Language and ai-preferences redirect to user-preferences
|
||||
const langRoute = (root?.children ?? []).find((child) => child.path === 'language');
|
||||
expect(langRoute?.redirectTo).toBe('user-preferences');
|
||||
|
||||
const aiRoute = (root?.children ?? []).find((child) => child.path === 'ai-preferences');
|
||||
expect(aiRoute?.redirectTo).toBe('user-preferences');
|
||||
});
|
||||
|
||||
it('renders settings shell container', async () => {
|
||||
|
||||
Reference in New Issue
Block a user