// @vitest-environment jsdom import { readFileSync } from 'node:fs'; import { act } from 'react'; import { createRoot } from 'react-dom/client'; import { describe, expect, it, vi } from 'vitest'; import { useContextClusterIndex } from '../../src/features/context-layer/use-context-cluster-index'; import { FindingReader } from '../../src/features/finding-reader/FindingReader'; import { makeSheet } from '../../src/importers/sheet-factory'; import { useWorkPackages } from '../../src/remediation/use-work-packages'; import { workRecordKey } from '../../src/remediation/work-package-index'; vi.mock('../../src/features/context-layer/use-context-cluster-index', () => ({ useContextClusterIndex: vi.fn(() => undefined) })); vi.mock('../../src/remediation/use-work-packages', () => ({ useWorkPackages: vi.fn() })); describe.each(['dark', 'light'])('Reader Work Package alignment (%s)', theme => { it('keeps packages and task details right aligned with or without Source Rule ID, including unavailable and unassigned states', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); document.documentElement.dataset.theme = theme; const style = document.createElement('style'); style.textContent = readFileSync('src/styles/components.css', 'utf8'); document.head.append(style); const host = document.createElement('div'); document.body.append(host); const root = createRoot(host); try { for (const withSource of [true, false]) { const record = makeSheet('Synthetic', [withSource ? ['Name', 'Status', 'Source Rule ID'] : ['Name', 'Status'], withSource ? ['Synthetic deviation', 'Failed', 'RULE-01'] : ['Synthetic deviation', 'Failed']], '', 'synthetic.csv').analysis.records[0]; const original = JSON.stringify(record); const workPackage = { id: 'synthetic', code: 'WP-01', title: 'Synthetic package', reason: 'Synthetic', clusterId: 'synthetic-cluster', clusterLabel: 'Synthetic long cluster name for wrapping', facts: { tasks: 6, findings: 6 }, tasks: Array.from({ length: 6 }, (_, i) => ({ id: `task-${i}`, findingKeys: i ? [] : [workRecordKey(record)] })) }; for (const state of [ { status: 'ready', index: { packageFor: () => workPackage } }, { status: 'loading' }, { status: 'ready', index: { packageFor: () => undefined, unassigned: () => true } } ]) { vi.mocked(useWorkPackages).mockReturnValue(state as never); await act(async () => root.render()); const row = host.querySelector('.reader-source-rule')!; const packageItem = row.querySelector('.finding-work-package__group, .finding-work-package')!; for (const width of [1200, 620, 320]) { host.style.width = `${width}px`; expect(getComputedStyle(packageItem).textAlign).toBe('right'); expect(getComputedStyle(packageItem).overflowWrap).toBe('anywhere'); expect(getComputedStyle(packageItem).flex).toBe('1 1 0px'); } expect(Boolean(row.querySelector('code'))).toBe(withSource); if (row.querySelector('[data-work-package-id]')) { expect(packageItem.textContent).toContain('One of 6 tasks'); for (const severity of ['critical', 'high', 'medium', 'undetermined']) { vi.mocked(useContextClusterIndex).mockReturnValue({ publication: { clusters: [{ id: 'synthetic-cluster', label: workPackage.clusterLabel, hasEvidence: severity !== 'undetermined', severity }] } } as never); await act(async () => root.render()); const square = row.querySelector('[data-finding-work-package-colour]')!; expect(square.getAttribute('data-finding-work-package-colour')).toBe(severity); expect(square.classList.contains('severity-' + severity)).toBe(true); expect(square.parentElement?.getAttribute('data-work-package-id')).toBe('synthetic'); expect(getComputedStyle(square).display).toBe('inline-block'); expect(row.textContent).toContain('One of 6 tasks'); } await act(async () => row.querySelector('a')!.click()); } expect(JSON.stringify(record)).toBe(original); } } } finally { vi.mocked(useContextClusterIndex).mockReturnValue(undefined); await act(async () => root.unmount()); host.remove(); style.remove(); vi.unstubAllGlobals(); } }); });