63 lines
3.5 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|