// @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 { FindingReader } from '../../src/features/finding-reader/FindingReader'; import { makeSheet } from '../../src/importers/sheet-factory'; import { defaultReview } from '../../src/domain/review'; const css = readFileSync('src/styles/components.css', 'utf8'); const record = makeSheet('Synthetic', [ ['Name', 'Result', 'Severity', 'Description', 'ShortName'], ['Synthetic LDAP signing finding', 'IOE Found', 'High', 'Synthetic evidence', 'SI000160'] ], '', 'synthetic.csv', { toolId: 'purple-knight' }).analysis.records[0]; describe.each(['dark', 'light'])('Finding actions (%s)', theme => { it('keeps ordered icon-only actions together through narrow resizing, bookmark changes and Results opening', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); document.documentElement.dataset.theme = theme; const style = document.createElement('style'); style.textContent = css; document.head.append(style); const host = document.createElement('div'); document.body.append(host); const root = createRoot(host); const onReview = vi.fn(); const onResults = vi.fn(); const render = async (include: boolean, resultsOpen = false) => { await act(async () => root.render()); }; try { await render(false); for (const width of [1200, 620, 320]) { host.style.width = `${width}px`; window.dispatchEvent(new Event('resize')); const group = host.querySelector('.reader-primary-actions')!; expect([...group.children].map(element => element.tagName)).toEqual(['A', 'BUTTON', 'BUTTON']); expect(group.textContent).toBe('AI searchResults'); expect(getComputedStyle(group).flexFlow).toBe('row nowrap'); expect(getComputedStyle(group).flex).toBe('0 0 auto'); for (const action of group.children) expect(getComputedStyle(action).whiteSpace).toBe('nowrap'); } const bookmark = host.querySelector('.bookmark-btn')!; expect(bookmark.getAttribute('aria-label')).toBe('Bookmark'); expect(bookmark.textContent).toBe(''); expect(bookmark.querySelector('svg')).not.toBeNull(); expect(bookmark.getAttribute('aria-pressed')).toBe('false'); await act(async () => bookmark.click()); expect(onReview).toHaveBeenLastCalledWith({ include: true }); await render(true); expect(bookmark.getAttribute('aria-pressed')).toBe('true'); await act(async () => bookmark.click()); expect(onReview).toHaveBeenLastCalledWith({ include: false }); await act(async () => host.querySelector('.results-detail-btn')!.click()); expect(onResults).toHaveBeenCalledTimes(1); await render(true, true); expect(host.querySelector('.results-detail-btn')!.classList.contains('is-open')).toBe(true); expect(host.querySelector('.reader-primary-actions')!.textContent).toBe('AI searchResults'); } finally { await act(async () => root.unmount()); host.remove(); style.remove(); vi.unstubAllGlobals(); } }); });