Assessor_2.0/tests/features/finding-reader-actions.test...

63 lines
3.5 KiB
TypeScript

// @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(<FindingReader record={record} title={record.title}
review={{ ...defaultReview(), include }} linkedSheets={[]} resultReferences={[]} search=""
position={0} recordCount={1} toolId="purple-knight" onPrevious={vi.fn()} onNext={vi.fn()}
onReview={onReview} onResults={onResults} resultsOpen={resultsOpen}/>));
};
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<HTMLElement>('.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<HTMLButtonElement>('.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<HTMLButtonElement>('.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();
}
});
});