// Browser-only mounted regression checks. This page is outside the production // entry graph and replaces fetch with fixtures before mounting application UI. import { act, useEffect, useState } from 'react' import { createRoot } from 'react-dom/client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryState } from '@/components/query-state' import { AgentPolicySetsSortable } from '@/components/agents/agent-policy-sets-sortable' import { AgentDiagnosticsPanel } from '@/components/agents/agent-diagnostics-panel' import { TrafficTimeseriesChart } from '@/components/stats/traffic-timeseries-chart' import { PortalShell } from '@/components/layout/portal-shell' import { createRootRoute, createRouter, createMemoryHistory, RouterProvider } from '@tanstack/react-router' import { ensureAuthConfig } from '@/lib/auth' import { DEFAULT_APP_SWITCHER_CONFIG } from '@/lib/app-switcher-config' import type { AgentDiagnostics, TimeseriesPoint } from '@evofw/shared' import '@evofw/ui/globals.css' if (!import.meta.env.DEV) throw new Error('Development checks only') ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true const results = document.querySelector('#results')! const host = document.querySelector('#fixture')! const root = createRoot(host) const checks: string[] = [] function check(condition: unknown, label: string) { if (!condition) throw new Error(label) checks.push(`PASS ${label}`) results.textContent = checks.join('\n') } let mounts = 0 let unmounts = 0 function Editor() { const [value, setValue] = useState('initial') useEffect(() => { mounts++; return () => { unmounts++ } }, []) return setValue(e.target.value)} /> } try { const view = (failed: boolean) => ( ) await act(async () => root.render(view(false))) const input = host.querySelector('input')! input.focus() await act(async () => { Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!.call(input, 'unsaved') input.dispatchEvent(new Event('input', { bubbles: true })) }) await act(async () => root.render(view(true))) check(host.querySelector('input') === input, 'background failure preserves the input DOM node') check(document.activeElement === input, 'background failure preserves focus') check(input.value === 'unsaved', 'background failure preserves local edit state') check(mounts === 1 && unmounts === 0, 'background failure does not remount the editor') await act(async () => root.render(view(false))) check(host.querySelector('input') === input && input.value === 'unsaved', 'refresh recovery preserves the edit') let assignmentPuts = 0 const originalFetch = window.fetch window.fetch = async (request, init) => { const path = String(request) if (init?.method === 'PUT') assignmentPuts++ if (path.endsWith('/agents/fixture/policy-sets')) { return Response.json({ error: { message: 'Assignment fixture unavailable' } }, { status: 503 }) } if (path.endsWith('/policy-sets')) { return Response.json({ items: [{ id: 'catalog-set', name: 'Fixture set', enabled: true }] }) } throw new Error(`Unexpected fixture request: ${path}`) } const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }) await act(async () => { root.render() await new Promise((resolve) => setTimeout(resolve, 100)) }) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 100)) }) const add = [...host.querySelectorAll('button')].find((button) => button.textContent?.trim() === 'Добавить') check(host.textContent?.includes('Assignment fixture unavailable'), 'assignment GET failure displays its error') check(add?.disabled, 'assignment GET failure disables assignment writes') add?.click() check(assignmentPuts === 0, 'assignment GET failure cannot produce a replacement PUT') await act(async () => root.unmount()) qc.clear() // Import the router plugin's development split component so the test mounts // the real settings page without adding a production export or route. const splitUrl = '/src/routes/_auth/settings.tsx?tsr-split=component' const { component: SettingsPage } = await import(/* @vite-ignore */ splitUrl) let storedSettings = { enroll_seed: 'fixture-seed', evobgp_api_url: '', evobgp_api_token: '', agent_sync_interval_sec: '60', show_quick_actions: 'true' } let settingsOffline = false let saves = 0 window.fetch = async (request, init) => { if (!String(request).endsWith('/api/v1/settings')) throw new Error('Unexpected settings fixture request') if (init?.method === 'PUT') { storedSettings = JSON.parse(String(init.body)) saves++ return Response.json({ ok: true }) } return settingsOffline ? Response.json({ error: { message: 'Settings fixture offline' } }, { status: 503 }) : Response.json(storedSettings) } const settingsClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) const settingsRoot = createRoot(host) await act(async () => { settingsRoot.render() await new Promise((resolve) => setTimeout(resolve, 100)) }) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 100)) }) const seed = host.querySelector('#enroll_seed')! check(seed?.value === 'fixture-seed', 'real settings page hydrates the fixture') seed.focus() await act(async () => { Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!.call(seed, 'operator-unsaved-seed') seed.dispatchEvent(new Event('input', { bubbles: true })) }) check(host.textContent?.includes('Есть несохранённые изменения'), 'editing settings records dirty state') await act(async () => { settingsClient.setQueryData(['settings'], { ...storedSettings, enroll_seed: 'remote-refresh' }) }) check(seed.value === 'operator-unsaved-seed', 'settings background data cannot reset dirty fields') settingsOffline = true await act(async () => { await settingsClient.refetchQueries({ queryKey: ['settings'] }); await new Promise((resolve) => setTimeout(resolve, 30)) }) check(host.querySelector('#enroll_seed') === seed && seed.value === 'operator-unsaved-seed', 'settings refresh error preserves mounted dirty fields') check(document.activeElement === seed, 'settings refresh error preserves input focus') settingsOffline = false await act(async () => { await settingsClient.refetchQueries({ queryKey: ['settings'] }); await new Promise((resolve) => setTimeout(resolve, 30)) }) await act(async () => { host.querySelector('form')!.requestSubmit() await new Promise((resolve) => setTimeout(resolve, 100)) }) check(saves === 1 && storedSettings.enroll_seed === 'operator-unsaved-seed', 'settings save sends the operator edit once') check(seed.value === 'operator-unsaved-seed' && !host.textContent?.includes('Есть несохранённые изменения'), 'accepted settings become clean without reverting to stale cache') await act(async () => settingsRoot.unmount()) settingsClient.clear() const diagnostics: AgentDiagnostics = { desired_revision: 8, desired_hash: 'sha256:desired-fixture', applied_revision: 7, applied_hash: 'sha256:applied-fixture', agent_version: 'fixture-v2', capabilities: ['traffic-v2'], last_contact_at: '2026-10-24T00:05:00Z', last_telemetry_at: null, last_apply_at: null, apply_error: null, status: 'drift', telemetry_version: 2, quality: { partial: true, truncated: true, unsupported: ['accepted'], lost_reports: 2 }, } const diagnosticRoot = createRoot(host) await act(async () => diagnosticRoot.render()) check(host.textContent?.includes('Расхождение политики') && !host.textContent?.includes('Синхронизирован'), 'diagnostics displays authoritative drift instead of inferring synchronization from contact') check(host.textContent?.includes('Не получена') && host.textContent?.includes('Не подтверждено'), 'diagnostics distinguishes missing telemetry from missing apply acknowledgment') check(host.textContent?.includes('Неполный') && host.textContent?.includes('Обрезан') && host.textContent?.includes('Потеряно отчётов: 2'), 'diagnostics exposes partial, truncated and lost-report quality') const nullPoint: TimeseriesPoint = { timestamp: '2026-10-24T00:00:00.000Z', packets_dropped: null, packets_accepted: null, pps: null, coverage: 0, estimated: false, quality: { partial: false, truncated: false, unsupported: ['packets'], lost_reports: 0 } } await act(async () => diagnosticRoot.render()) check(host.textContent?.includes('Пропуски не означают нулевой трафик') && !host.querySelector('.recharts-surface'), 'missing packet measurements render an explicit gap state instead of a zero traffic chart') check(nullPoint.packets_dropped === null && nullPoint.packets_accepted === null, 'chart rendering leaves missing packet values null') await act(async () => diagnosticRoot.unmount()) let fullCatalogRequests = 0 let summaryRequests = 0 const previousToken = localStorage.getItem('fw_auth_token') localStorage.setItem('fw_auth_token', `fixture.${btoa(JSON.stringify({ sub: 'fixture', name: 'Fixture reader', apps: ['fw'], permissions: ['fw:agents:read'] }))}.fixture`) window.fetch = async (request) => { const path = String(request) if (path.endsWith('/api/auth/config')) return Response.json({ required: true, portal_url: 'https://fixture.invalid' }) if (path.endsWith('/api/v1/app-switcher')) return Response.json(DEFAULT_APP_SWITCHER_CONFIG) if (path.endsWith('/api/v1/ui/summary')) { summaryRequests++ return Response.json({ updated_at: '2026-10-24T00:00:00.000Z', agents: { approved: 0, online: 0, pending: 0, offline: 0 } }) } if (/\/api\/v1\/(agents|lists|policy-sets)$/.test(path)) fullCatalogRequests++ throw new Error(`Unexpected shell fixture request: ${path}`) } await ensureAuthConfig() const shellClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) const fixtureRoute = createRootRoute({ component: () =>

Read-only summary fixture

}) const fixtureRouter = createRouter({ routeTree: fixtureRoute, history: createMemoryHistory({ initialEntries: ['/'] }) }) const shellRoot = createRoot(host) await act(async () => { shellRoot.render() await new Promise((resolve) => setTimeout(resolve, 100)) }) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 100)) }) check(summaryRequests === 1 && fullCatalogRequests === 0, 'closed shell reads one summary without fetching full agent/list/policy catalogs') const monitor = [...host.querySelectorAll('button')].find((button) => button.getAttribute('aria-label')?.startsWith('Состояние системы:'))! await act(async () => { monitor.click(); await new Promise((resolve) => setTimeout(resolve, 50)) }) check(document.body.textContent?.includes('Нет одобренных') && !document.body.textContent?.includes('Списки IP'), 'permission-omitted summary sections stay hidden instead of showing fabricated zero metrics') check(!host.textContent?.includes('Списки') && !host.textContent?.includes('Наборы правил'), 'read-only agent permission hides unrelated navigation sections') await act(async () => shellRoot.unmount()) shellClient.clear() if (previousToken === null) localStorage.removeItem('fw_auth_token') else localStorage.setItem('fw_auth_token', previousToken) window.fetch = originalFetch results.dataset.status = 'passed' } catch (error) { results.textContent = `${checks.join('\n')}\nFAIL ${error instanceof Error ? error.stack : String(error)}` results.dataset.status = 'failed' }