// @vitest-environment jsdom import { act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, expect, it, vi } from 'vitest'; const appState = vi.hoisted(() => ({ current: {} as Record })); vi.mock('../../src/app/AppProvider', () => ({ useApp: () => appState.current, useOptionalApp: () => appState.current, RetainedAppState: ({ children }: { children: unknown }) => children })); vi.mock('../../src/remediation/use-work-packages', () => ({ useWorkPackages: vi.fn() })); vi.mock('../../src/features/project-import/DataHub', () => ({ DataHub: () =>
Data Hub
})); vi.mock('../../src/features/dashboard/DashboardPanel', () => ({ DashboardPanel: () =>
Synthetic Client Info
})); vi.mock('../../src/features/db-structure/DbStructurePanel', () => ({ DbStructurePanel: () => null })); vi.mock('../../src/features/results-browser/ResultsBrowser', () => ({ ResultsBrowser: () => null })); vi.mock('../../src/features/search-filter/SearchFilterBar', () => ({ SearchFilterBar: () => null, SearchTextControl: () => null, SheetAndSearchControls: () => null, WorkTimeslotControl: () => null })); vi.mock('../../src/features/search-filter/use-filtered-records', () => ({ useFilteredRecords: () => [] })); // Work stub: the real ring and focus provider decide what is shown; the stub only reports it. vi.mock('../../src/features/dataset-viewer/DatasetViewer', async () => { const related = await import('../../src/features/related-findings/RelatedFindings'); return { DatasetViewer: () => { const context = related.useRelatedFindings(); const sheet = (appState.current.activeSheet as { analysis: { records: Array<{ id: string; title: string }> } }); const shown = context?.selection ? sheet.analysis.records.filter(record => context.selection!.group.recordIds.includes(record.id)) : sheet.analysis.records; return
{shown.map(record =>
{record.title}
)}
; } }; }); vi.mock('../../src/features/mitre-graph/ObsidianGraphView', () => ({ ObsidianGraphView: () => null })); vi.mock('../../src/features/actionlist/ActionlistControls', () => ({ ActionlistControls: () => null })); vi.mock('../../src/features/import-workflow/ImportWorkflowDialog', () => ({ ImportWorkflowDialog: () => null })); vi.mock('../../src/features/sidebar/SidebarSecurityDomains', () => ({ SidebarSecurityDomains: () => null })); vi.mock('../../src/features/view-bookmarks/ViewBookmarks', () => ({ ViewBookmarks: () => null })); vi.mock('../../src/features/mitre-graph/PathfinderFilterTrail', () => ({ PathfinderFilterTrail: () => null })); vi.mock('../../src/features/about/AboutDialog', () => ({ AboutDialog: () => null })); import { App } from '../../src/app/App'; import { RelatedFindingsProvider } from '../../src/features/related-findings/RelatedFindings'; import { useWorkPackages } from '../../src/remediation/use-work-packages'; import { workRecordKey } from '../../src/remediation/work-package-index'; import { makeSheet } from '../../src/importers/sheet-factory'; afterEach(() => { vi.unstubAllGlobals(); vi.clearAllMocks(); delete document.documentElement.dataset.theme; }); it.each(['dark', 'light'])('ends a same-change focus when another area is opened and stays in that area (%s)', async theme => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => setTimeout(() => callback(0), 0)); vi.stubGlobal('cancelAnimationFrame', clearTimeout); document.documentElement.dataset.theme = theme; const sheet = makeSheet('Synthetic', [['Name', 'Status', 'Severity'], ['Synthetic template allows requester SAN', 'Failed', 'High'], ['Synthetic authentication template subject can be modified', 'Failed', 'High'], ['Synthetic unrelated finding', 'Failed', 'Low']], '', 'synthetic.csv', { isProjectSheet: true }); const records = sheet.analysis.records; records.forEach((record, index) => { record.findingOccurrenceId = `synthetic-occurrence-${index}`; }); const pkg = { id: 'package', code: 'WP-01', title: 'Synthetic', clusterLabel: 'Synthetic', facts: { tasks: 1, findings: 2 }, tasks: [{ id: 'same-template-change', redundant: true, findingKeys: records.slice(0, 2).map(workRecordKey) }] }; vi.mocked(useWorkPackages).mockReturnValue({ status: 'ready', index: { packages: [pkg], packageFor: () => pkg, unassigned: () => false, facet: { id: 'remediation:work-package', label: 'Work Package', values: [], valuesFor: () => [] } } } as never); const applyViewBookmark = vi.fn(); const resetFilters = vi.fn(); const dataset = { sheets: [sheet], profileId: 'generic', fileName: 'Synthetic' }; appState.current = { dataset, activeSheet: sheet, activeSheetId: sheet.id, reviews: {}, snapshot: () => null, theme, setTheme: vi.fn(), delimiter: ',', setDelimiter: vi.fn(), view: 'records', setView: vi.fn(), actionOnly: false, sidebarCollapsed: false, toggleSidebar: vi.fn(), toggleWorkspace: vi.fn(), backendStatus: 'connected', targetProjectId: 'synthetic-project', activeScanRunIds: ['synthetic-run'], activeAssessmentIds: ['synthetic-slot'], workScopeId: 'synthetic-scope', search: 'synthetic search', statusFilter: 'deviation', sortColumn: undefined, sortDirection: 'asc', titleColumn: undefined, facetFilters: {}, pathFindingIds: [], selectedRecordId: records[2].id, resetFilters, applyViewBookmark, setSelectedRecord: vi.fn(), setPathFindingIds: vi.fn(), setStatusFilter: vi.fn(), setFacetFilter: vi.fn(), setActiveSheet: vi.fn(), openProject: vi.fn(), setTargetProjectId: vi.fn(), importWorkflow: { phase: 'idle' } }; const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); const nav = (label: string) => [...container.querySelectorAll('.sidebar-tab-btn')].find(button => button.textContent === label)!; const work = () => container.querySelector('[data-testid="work"]'); try { await act(async () => root.render()); await act(async () => nav('Work').click()); expect(work()!.querySelectorAll('.record-card')).toHaveLength(3); await act(async () => work()!.querySelector('.related-finding-ring')!.click()); expect(work()!.dataset.focused).toBe('true'); expect(work()!.querySelectorAll('.record-card')).toHaveLength(2); await act(async () => nav('Client Info').click()); await act(async () => new Promise(resolve => setTimeout(resolve, 0))); // The focus ends, the previous Work state is restored and the chosen area stays open. expect(applyViewBookmark).toHaveBeenCalledOnce(); expect(applyViewBookmark.mock.calls[0][0]).toMatchObject({ search: 'synthetic search', statusFilter: 'deviation', view: 'records', selectedRecordId: records[2].id }); expect(nav('Client Info').classList.contains('active')).toBe(true); expect(container.querySelector('[data-testid="client-info"]')).not.toBeNull(); expect(!work() || Boolean(work()!.closest('[hidden]'))).toBe(true); await act(async () => nav('Work').click()); expect(work()!.dataset.focused).toBeUndefined(); expect(work()!.querySelectorAll('.record-card')).toHaveLength(3); expect(work()!.querySelectorAll('.related-finding-ring[aria-pressed="true"]')).toHaveLength(0); } finally { await act(async () => root.unmount()); container.remove(); } });