Merge pull request #1958 from rcourtman/maintainer/20260907T131524Z

Protect alert settings navigation and threshold edits from regressions
This commit is contained in:
pulse-triage[bot]
2026-09-07 15:54:14 +01:00
committed by GitHub
4 changed files with 66 additions and 3 deletions
@@ -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(() => (
<CollapsibleSection
id="snapshots"
title="Snapshot Age"
collapsed={collapsed()}
onToggle={setCollapsed}
>
<label>
Warning days
<input type="number" value="7" />
</label>
</CollapsibleSection>
));
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(() => (
<CollapsibleSection id="test" title="Title" isGloballyDisabled={true}>
@@ -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: [],
@@ -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');
@@ -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(