From ae954f776ae672231cf82df2dfa36fe567703cba Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 04:03:21 +0100 Subject: [PATCH] test(web): guard navigation continuity during sync reconnects Issue #1899 reports navigation disappearing when backend health changes to sync reconnecting on v6.4.1. Protect the isolated shell transition with stable link, label, icon and focus assertions rather than assuming the badge unit test covers navigation. This does not reproduce the reporter's browser or socket environment and makes no production fix claim. Validation: AppLayout, useAppRuntimeState and ConnectionStatusBadge focused suites pass 46 tests. Temporarily hiding desktop navigation during sync reconnecting makes the new test fail; the mutation was restored and the suites rerun successfully. Change-source: pulse-maintainer --- .../src/__tests__/AppLayout.test.tsx | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/frontend-modern/src/__tests__/AppLayout.test.tsx b/frontend-modern/src/__tests__/AppLayout.test.tsx index f75732333..6d25667ee 100644 --- a/frontend-modern/src/__tests__/AppLayout.test.tsx +++ b/frontend-modern/src/__tests__/AppLayout.test.tsx @@ -2,6 +2,8 @@ import { cleanup, fireEvent, render, screen, waitFor, within } from '@solidjs/te import { Route, Router, useNavigate } from '@solidjs/router'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { State } from '@/types/api'; +import { createSignal, type Accessor } from 'solid-js'; +import type { AppConnectionStatus } from '@/useAppRuntimeState'; import type { Resource } from '@/types/resource'; import { AppLayout, @@ -72,6 +74,12 @@ const renderLayout = ( initialPath = '/settings/infrastructure', platformVisibility?: PlatformNavigationVisibility, tokenScopes: string[] = ['settings:read'], + connectionStatus: Accessor = () => ({ + kind: 'connected', + label: 'Connected', + detail: 'Backend and live data stream are connected.', + tone: 'healthy', + }), ) => { window.history.replaceState({}, '', initialPath); const RouteStateProbe = () => { @@ -87,12 +95,7 @@ const renderLayout = ( }; const LayoutRoute = () => ( ({ - kind: 'connected', - label: 'Connected', - detail: 'Backend and live data stream are connected.', - tone: 'healthy', - })} + connectionStatus={connectionStatus} lastUpdateText={() => ''} versionInfo={() => ({ @@ -223,6 +226,47 @@ describe('AppLayout navigation icons', () => { expect(container).toHaveTextContent('Infrastructure body'); }); + it('retains navigation labels, icons and focus as backend health changes to sync reconnecting', async () => { + // #1899: isolate the shell status transition from inventory changes. This + // is not a reproduction of the reporter's socket/proxy or CSS environment. + const [connectionStatus, setConnectionStatus] = createSignal({ + kind: 'backend-healthy', + label: 'Backend healthy', + detail: 'Backend is healthy, but the live data stream is not connected.', + tone: 'warning', + }); + renderLayout( + platformResources(), + '/settings/infrastructure', + undefined, + ['settings:read'], + connectionStatus, + ); + const navigation = screen.getByRole('navigation', { name: 'Primary navigation' }); + const links = within(navigation).getAllByRole('link'); + const labels = links.map((link) => link.textContent); + const proxmox = getInfrastructureLink('Proxmox'); + proxmox.focus(); + expect(proxmox).toHaveFocus(); + + for (const status of [ + { kind: 'sync-reconnecting', label: 'Sync reconnecting', tone: 'warning' }, + { kind: 'connected', label: 'Connected', tone: 'healthy' }, + { kind: 'sync-reconnecting', label: 'Sync reconnecting', tone: 'warning' }, + ] as const) { + setConnectionStatus({ ...status, detail: status.label }); + await waitFor(() => expect(screen.getByText(status.label)).toBeInTheDocument()); + expect(navigation).toBeInTheDocument(); + const currentLinks = within(navigation).getAllByRole('link'); + expect(currentLinks.map((link) => link.textContent)).toEqual(labels); + currentLinks.forEach((link, index) => { + expect(link).toBe(links[index]); + expect(link.querySelector('svg')).toBeTruthy(); + }); + expect(proxmox).toHaveFocus(); + } + }); + it('surfaces canonical Patrol attention as a count without renaming Patrol', () => { patrolAttentionMockState.activeCount = 2; renderLayout();