diff --git a/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx b/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx index e29b68cba..40f9cab7f 100644 --- a/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx +++ b/frontend-modern/src/components/Alerts/Thresholds/sections/__tests__/CollapsibleSection.test.tsx @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createSignal } from 'solid-js'; import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library'; import { CollapsibleSection, SectionActionButton, NestedGroupHeader } from '../CollapsibleSection'; import { @@ -173,6 +174,47 @@ describe('CollapsibleSection', () => { expect(button).toHaveAttribute('aria-expanded', 'false'); }); + it('preserves an edited threshold across controlled collapse and reopen', () => { + const [collapsed, setCollapsed] = createSignal(true); + render(() => ( + + + + )); + + const disclosure = screen.getByRole('button', { name: 'Snapshot Age' }); + const panel = document.getElementById(disclosure.getAttribute('aria-controls')!); + expect(panel).toHaveAttribute('inert'); + expect(panel).toHaveAttribute('aria-hidden', 'true'); + + fireEvent.click(disclosure); + expect(disclosure).toHaveAttribute('aria-expanded', 'true'); + expect(panel).not.toHaveAttribute('inert'); + expect(panel).not.toHaveAttribute('aria-hidden'); + const input = screen.getByRole('spinbutton', { name: 'Warning days' }); + fireEvent.input(input, { target: { value: '14' } }); + + fireEvent.click(disclosure); + expect(disclosure).toHaveAttribute('aria-expanded', 'false'); + expect(panel).toHaveAttribute('inert'); + expect(panel).toHaveAttribute('aria-hidden', 'true'); + + fireEvent.click(disclosure); + expect(disclosure).toHaveAttribute('aria-expanded', 'true'); + expect(panel).not.toHaveAttribute('inert'); + expect(panel).not.toHaveAttribute('aria-hidden'); + expect(screen.getByRole('spinbutton', { name: 'Warning days' })).toBe(input); + expect(input).toHaveValue(14); + }); + it('shows "Disabled" badge when isGloballyDisabled is true', () => { render(() => ( diff --git a/tests/integration/scripts/entitlement-bootstrap.mjs b/tests/integration/scripts/entitlement-bootstrap.mjs index 194909786..834059cfe 100644 --- a/tests/integration/scripts/entitlement-bootstrap.mjs +++ b/tests/integration/scripts/entitlement-bootstrap.mjs @@ -5,6 +5,9 @@ import { spawn } from 'node:child_process'; const truthyValues = new Set(['1', 'true', 'yes', 'on']); const BILLING_PROFILES = { + // General-purpose organization/billing fixture, not a white-label runtime. + // white_label deliberately hides commercial routes via security/status; + // commercial suppression belongs in dedicated boundary scenarios. 'multi-tenant': { capabilities: [ 'advanced_reporting', @@ -25,7 +28,6 @@ const BILLING_PROFILES = { 'sso', 'unlimited', 'update_alerts', - 'white_label', ], limits: {}, meters_enabled: [], diff --git a/tests/integration/scripts/entitlement-bootstrap.test.mjs b/tests/integration/scripts/entitlement-bootstrap.test.mjs index 21cd0bb3f..3595bddbc 100644 --- a/tests/integration/scripts/entitlement-bootstrap.test.mjs +++ b/tests/integration/scripts/entitlement-bootstrap.test.mjs @@ -36,6 +36,16 @@ test('buildBillingState returns enterprise capabilities for multi-tenant profile assert.ok(state.capabilities.includes('rbac')); }); +test('general-purpose entitlement profiles keep commercial routes available', () => { + for (const profile of ['multi-tenant', 'infra']) { + const state = buildBillingState(profile); + assert.ok( + !state.capabilities.includes('white_label'), + `${profile} must not opt the shared runtime into commercial suppression`, + ); + } +}); + test('applyRequestedEntitlementProfile writes a billing state file when a path is provided', async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'pulse-entitlement-')); const billingPath = path.join(dir, 'billing.json'); diff --git a/tests/integration/tests/15-settings-shell-consistency.spec.ts b/tests/integration/tests/15-settings-shell-consistency.spec.ts index a77167bf9..e87e09689 100644 --- a/tests/integration/tests/15-settings-shell-consistency.spec.ts +++ b/tests/integration/tests/15-settings-shell-consistency.spec.ts @@ -21,18 +21,21 @@ const SETTINGS_SHELL_ROUTES = [ { route: '/settings/organization', title: 'Organization Overview', + mobileTitle: 'Overview', description: 'Review organization metadata, membership footprint, and ownership.', requiresMultiTenant: true, }, { route: '/settings/organization/access', title: 'Organization Access', + mobileTitle: 'Access', description: 'Manage organization invitations, member roles, and ownership transfers.', requiresMultiTenant: true, }, { route: '/settings/organization/billing', title: 'Billing & Usage', + mobileTitle: 'Billing', description: 'Review your organization plan, applicable usage policies, and subscription status for paid access.', requiresMultiTenant: true, @@ -50,13 +53,14 @@ const SETTINGS_SHELL_ROUTES = [ }, { route: '/settings/system-ai', + canonicalRoute: '/settings/pulse-intelligence/provider', title: 'Provider & Models', description: 'Configure providers, default models, provider health, budget, and usage for Pulse Intelligence.', }, { route: '/settings/system-updates', - title: 'Updates', + title: 'Pulse server updates', description: 'Manage Pulse server runtime version checks, update channels, and automatic updates. Agent updates stay under Infrastructure.', }, @@ -144,8 +148,13 @@ test.describe('Settings shell consistency', () => { await expect(navigation).toBeHidden(); } - const pageHeading = page.getByRole('heading', { level: 1, name: panel.title }); + // The compact mobile header uses the navigation label, not the + // desktop page title (SettingsPageShell + settingsNavCatalog). + const title = isMobile && 'mobileTitle' in panel ? panel.mobileTitle : panel.title; + const pageHeading = page.getByRole('heading', { level: 1, name: title, exact: true }); await expect(pageHeading, `${panel.route} should render the canonical page-shell heading`).toBeVisible(); + const canonicalRoute = 'canonicalRoute' in panel ? panel.canonicalRoute : panel.route; + await expect(page).toHaveURL(new RegExp(`${canonicalRoute}$`)); if (!isMobile) { // Panel descriptions are desktop-only copy (hidden sm:block). await expect(