// @vitest-environment jsdom import { act, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { expect, it, vi } from 'vitest'; import { useApp } from '../../src/app/AppProvider'; import { SidebarSecurityDomains } from '../../src/features/sidebar/SidebarSecurityDomains'; import { comparePublishedClusters, contextClusterNumber } from '../../src/features/sidebar/context-cluster-display'; import { contextClusterSeverities } from '../../src/assessment-context/context-cluster-severity'; import { makeSheet } from '../../src/importers/sheet-factory'; import { genericProfile } from '../../src/profiles/generic/generic-profile'; vi.mock('../../src/app/AppProvider', () => ({ useApp: vi.fn() })); vi.mock('../../src/app/engine-preferences', () => ({ useEnginePreferences: () => ({ context: true, remediation: true, report: false }) })); const fixtures = vi.hoisted(() => ({ packages: [ { id: 'p1', code: 'WP-01', clusterLabel: 'AD Persistence Indicators', clusterId: 'ad-persistence-indicators' }, { id: 'p2', code: 'WP-02', clusterLabel: 'AD Persistence Indicators', clusterId: 'ad-persistence-indicators' }, { id: 'p3', code: 'WP-01', clusterLabel: 'AD Privileged Account Exposure', clusterId: 'ad-privileged-account-exposure' }, { id: 'p0', code: 'WP-01', clusterLabel: 'Credential Relay Exposure', clusterId: 'credential-relay-exposure' } ], clusters: [ { id: 'ad-privileged-account-exposure', severity: 'critical', hasEvidence: true }, { id: 'ad-persistence-indicators', severity: 'high', hasEvidence: true } ], facets: [ { id: 'derived:security-domain', values: [] }, { id: 'context:cluster', values: [ { value: 'ad-persistence-indicators', label: 'AD Persistence Indicators', count: 8 }, { value: 'ad-privileged-account-exposure', label: 'AD Privileged Account Exposure', count: 9 } ] }, { id: 'remediation:work-package', values: [ { value: 'p2', label: 'AD Persistence Indicators → WP-02', count: 2 }, { value: 'p1', label: 'AD Persistence Indicators → WP-01', count: 6 }, { value: 'p3', label: 'AD Privileged Account Exposure → WP-01', count: 9 }, { value: 'p0', label: 'Credential Relay Exposure → WP-01', count: 1 }, { value: '__unassigned__', label: 'Unassigned', count: 3 } ] } ] })); vi.mock('../../src/features/context-layer/use-context-cluster-index', () => ({ useContextClusterIndex: () => ({ publication: { clusters: fixtures.clusters } }) })); vi.mock('../../src/remediation/use-work-packages', () => ({ useWorkPackages: () => ({ status: 'ready', index: { packages: fixtures.packages } }) })); vi.mock('../../src/features/search-filter/work-facets', () => ({ selectWorkFacets: () => fixtures.facets })); it('numbers the complete published heatmap in order and renumbers when its scope order changes', () => { const publication = contextClusterSeverities.map(cluster => ({ id: cluster.clusterId })).reverse(); const numbers = publication.map(cluster => contextClusterNumber(cluster.id, publication)); expect(numbers.every(Boolean)).toBe(true); expect(new Set(numbers).size).toBe(numbers.length); expect(numbers).toEqual(publication.map((_, index) => String(index + 1).padStart(2, '0'))); expect(contextClusterNumber(publication[0].id, publication)).toBe('01'); expect(contextClusterNumber(publication[0].id, [...publication].reverse())).toBe('22'); expect(contextClusterNumber('ad-persistence-indicators')).toBeUndefined(); }); it.each(['light', 'dark'])('orders both actual filters by displayed Context severity, retaining numeric AP order and stable identifiers (%s)', async theme => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); document.documentElement.dataset.theme = theme; const saved = structuredClone(fixtures); const medium = 'ad-reconnaissance-exposure', neutral = 'cloud-privileged-access-exposure'; fixtures.clusters.push({id:'credential-relay-exposure',severity:'high',hasEvidence:true}, {id:medium,severity:'medium',hasEvidence:true},{id:neutral,severity:'critical',hasEvidence:false}); // Published heatmap order is authoritative, including equal-severity ties. [fixtures.clusters[1], fixtures.clusters[2]] = [fixtures.clusters[2], fixtures.clusters[1]]; fixtures.packages.push({id:'p4',code:'WP-01',clusterId:medium,clusterLabel:'AD Reconnaissance Exposure'}, {id:'p5',code:'WP-01',clusterId:neutral,clusterLabel:'Cloud Privileged Access Exposure'}); fixtures.facets[1].values.push({value:'credential-relay-exposure',label:'Credential Relay Exposure',count:1}, {value:medium,label:'AD Reconnaissance Exposure',count:4},{value:neutral,label:'Cloud Privileged Access Exposure',count:1}); fixtures.facets[2].values.push({value:'p4',label:'AD Reconnaissance Exposure → WP-01',count:4}, {value:'p5',label:'Cloud Privileged Access Exposure → WP-01',count:1}); const host=document.createElement('div');document.body.append(host);const root=createRoot(host); const select=vi.fn();const sheet=makeSheet('Synthetic',[['Name'],['Original']],'','synthetic.csv'); vi.mocked(useApp).mockReturnValue({dataset:null,activeProfile:genericProfile,reviews:{},statusFilter:'all',facetFilters:{},search:'',actionOnly:false,setFacetFilter:select} as unknown as ReturnType); try { await act(async()=>root.render()); const expected=['01 · AD Privileged Account Exposure','02 · Credential Relay Exposure','03 · AD Persistence Indicators','04 · AD Reconnaissance Exposure','05 · Cloud Privileged Access Exposure']; expect([...host.querySelectorAll('[data-filter-id="context:cluster"] .work-filter-value > span')].map(node=>node.textContent)).toEqual(expected); expect([...host.querySelectorAll('.work-package-cluster-head > span:first-child')].map(node=>node.textContent)).toEqual(expected); const rows=[...host.querySelectorAll('[data-filter-id="remediation:work-package"] .work-filter-value')]; expect(rows.map(row=>row.getAttribute('aria-label'))).toEqual([ expected[0]+' → WP-01: 9',expected[1]+' → WP-01: 1',expected[2]+' → WP-01: 6',expected[2]+' → WP-02: 2',expected[3]+' → WP-01: 4',expected[4]+' → WP-01: 1','Unassigned: 3']); await act(async()=>rows[3].click());expect(select).toHaveBeenLastCalledWith('remediation:work-package',{values:['p2']}); expect([...host.querySelectorAll('.work-package-cluster-colour')].map(node=>node.className)).toEqual(['critical','high','high','medium','undetermined'].map(value=>'work-package-cluster-colour severity-'+value)); expect(comparePublishedClusters('credential-relay-exposure','ad-persistence-indicators',fixtures.clusters as never)).toBeLessThan(0); }finally{await act(async()=>root.unmount());host.remove();Object.assign(fixtures,saved);vi.unstubAllGlobals();delete document.documentElement.dataset.theme;} }); it.each(['light', 'dark'])('shows one published cluster colour and matching cluster numbers across package selection and floating panels (%s)', async theme => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); document.documentElement.dataset.theme = theme; const sheet = makeSheet('Synthetic', [['Name', 'Severity'], ['Declaration', 'Critical'], ['Observation', 'Low']], '', 'synthetic.csv'); const original = JSON.stringify(sheet.analysis.records); const host = document.createElement('div'); document.body.append(host); const root = createRoot(host); function Harness() { const [facetFilters, setFilters] = useState['facetFilters']>({}); vi.mocked(useApp).mockReturnValue({ dataset: null, activeProfile: genericProfile, reviews: {}, statusFilter: 'all', facetFilters, search: '', actionOnly: false, setFacetFilter: (id, value) => setFilters(current => { const next = { ...current }; if (value) next[id] = value; else delete next[id]; return next; }) } as ReturnType); return ; } try { await act(async () => root.render()); const packageRows = () => Array.from(document.querySelectorAll('[data-filter-id="remediation:work-package"] .work-filter-value')); expect(packageRows().map(row => row.querySelector('span')?.textContent)).toEqual([ 'WP-01', 'WP-01', 'WP-02', 'WP-01', 'Unassigned']); expect(Array.from(document.querySelectorAll('.work-package-cluster-colour')).map(marker => marker.className)).toEqual([ 'work-package-cluster-colour severity-critical', 'work-package-cluster-colour severity-high', 'work-package-cluster-colour severity-undetermined']); expect(document.querySelectorAll('.work-package-heatmap-segment')).toHaveLength(0); expect(document.querySelector('[data-filter-id="context:cluster"] .work-filter-value span')?.textContent).toBe('01 · AD Privileged Account Exposure'); expect(packageRows()[1].querySelector('strong')?.textContent).toBe('6'); await act(async () => packageRows()[0].click()); expect(packageRows()[0].getAttribute('aria-pressed')).toBe('true'); expect(packageRows()[0].querySelector('span')?.textContent).toBe('WP-01'); await act(async () => document.querySelector('[aria-label="Detach filters"]')!.click()); expect(document.querySelector('.work-filter-panel.is-floating')).not.toBeNull(); expect(packageRows()[0].getAttribute('aria-pressed')).toBe('true'); expect(packageRows().every(row => row.querySelectorAll('.work-package-cluster-colour').length === 0)).toBe(true); expect(Array.from(document.querySelectorAll('.work-package-cluster-head > span:first-child')).map(node => node.textContent)).toEqual(['01 · AD Privileged Account Exposure', '02 · AD Persistence Indicators', 'Credential Relay Exposure']); expect(packageRows()[0].getAttribute('aria-label')).toBe('01 · AD Privileged Account Exposure → WP-01: 9'); await act(async () => packageRows()[0].click()); expect(packageRows().every(row => row.getAttribute('aria-pressed') === 'false')).toBe(true); await act(async () => packageRows()[4].click()); expect(packageRows()[4].getAttribute('aria-pressed')).toBe('true'); await act(async () => document.querySelector('[aria-label="Dock filters"]')!.click()); expect(packageRows()[4].getAttribute('aria-pressed')).toBe('true'); expect(JSON.stringify(sheet.analysis.records)).toBe(original); } finally { await act(async () => root.unmount()); host.remove(); vi.unstubAllGlobals(); delete document.documentElement.dataset.theme; } });