Assessor_2.0/tests/features/sidebar-severity-metrics.te...

135 lines
5.5 KiB
TypeScript

import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import { makeSheet } from '../../src/importers/sheet-factory';
import { genericProfile } from '../../src/profiles/generic/generic-profile';
import { summarizeSeverities, toggleSeveritySelection } from '../../src/features/sidebar/SidebarSeverityMetrics';
import { deriveDynamicFacets } from '../../src/features/search-filter/dynamic-facets';
import { compactSeveritySelection, DynamicFilterControls } from '../../src/features/search-filter/DynamicFilterControls';
const rows = [
['Title', 'Severity', 'Result'],
['Deviation 1', 'Critical', 'Failed'],
['Deviation 2', 'HIGH', 'Failed'],
['Deviation 3', 'hoch', 'Investigate'],
['Passed', 'Medium', 'Passed'],
['Deviation 4', 'Low', 'Failed'],
['Deviation 5', 'Info', 'Failed'],
['Unrated deviation', 'Unrated', 'Failed']
];
function options(statusFilter = 'deviation', facetFilters = {}) {
return {
sheet: makeSheet('Synthetic findings', rows, '', 'synthetic.csv'),
dataset: null,
activeProfile: genericProfile,
reviews: {},
statusFilter,
facetFilters,
search: '',
actionOnly: false
};
}
describe('sidebar severity metrics', () => {
it('uses Deviation as the diagram basis and excludes unknown source values', () => {
const summary = summarizeSeverities(options());
expect(summary?.metrics.map(metric => [metric.severity, metric.count, metric.percentage])).toEqual([
['critical', 1, 20],
['high', 2, 40],
['medium', 0, 0],
['low', 1, 20],
['info', 1, 20]
]);
expect(summary?.metrics.map(metric => metric.severity)).not.toContain('unknown');
});
it('follows an explicit user status filter without changing it', () => {
const summary = summarizeSeverities(options('all'));
expect(summary?.metrics.find(metric => metric.severity === 'medium')).toMatchObject({ count: 1 });
});
it('exposes exactly the five known severity filter values', () => {
const base = options();
const summary = summarizeSeverities(base);
if (!summary) throw new Error('Synthetic severity facet missing');
expect(summary.facetId).toBeTruthy();
expect(summary.metrics.map(metric => metric.severity)).toEqual([
'critical', 'high', 'medium', 'low', 'info'
]);
});
it('toggles multiple severities in canonical display order', () => {
expect(toggleSeveritySelection([], 'high')).toEqual(['high']);
expect(toggleSeveritySelection(['high'], 'critical')).toEqual(['critical', 'high']);
expect(toggleSeveritySelection(['critical', 'high'], 'high')).toEqual(['critical']);
expect(toggleSeveritySelection(['critical'], 'critical')).toEqual([]);
});
it('renders a compact multi-severity label in the filter control', () => {
const base = options();
const summary = summarizeSeverities(base);
if (!summary) throw new Error('Synthetic severity facet missing');
const severityFacet = deriveDynamicFacets(base.sheet, null, genericProfile, {})
.find(facet => facet.id === summary.facetId);
if (!severityFacet) throw new Error('Synthetic severity facet missing');
const html = renderToStaticMarkup(
<DynamicFilterControls
facets={[severityFacet]}
filters={{
[summary.facetId]: {
values: ['info', 'low', 'medium', 'high', 'critical']
}
}}
statusFilter="deviation"
onStatusFilter={() => undefined}
onFilter={() => undefined}
/>
);
expect(compactSeveritySelection(['info', 'critical', 'medium'])).toBe('Crit-Med-Inf');
expect(html).toContain('Crit-High-Med-Low-Inf');
expect(html).toContain('value="__severity_multi__" selected=""');
});
it('highlights a status only when it differs from the profile default', () => {
const base = options();
const statusFacet = deriveDynamicFacets(base.sheet, null, genericProfile, {})
.find(facet => facet.source === 'profile-status');
if (!statusFacet) throw new Error('Synthetic status facet missing');
const renderStatus = (statusFilter: string) => renderToStaticMarkup(
<DynamicFilterControls
facets={[statusFacet]}
filters={{}}
statusFilter={statusFilter}
defaultStatusFilter="deviation"
onStatusFilter={() => undefined}
onFilter={() => undefined}
/>
);
expect(renderStatus('deviation')).not.toContain('is-filtered');
expect(renderStatus('all')).toContain('is-filtered');
});
it('marks Source: Scan as the final right-edge filter control', () => {
const scanSheet = makeSheet('Synthetic findings', [
['Title', 'Severity', 'Source: Scan'],
['One', 'High', 'Scanner A'],
['Two', 'Low', 'Scanner B']
], '', 'synthetic.csv');
const facets = deriveDynamicFacets(scanSheet, null, genericProfile, {});
const html = renderToStaticMarkup(<DynamicFilterControls facets={facets} filters={{}} statusFilter="all"
onStatusFilter={() => undefined} onFilter={() => undefined} />);
expect(facets.at(-1)?.label).toBe('Source: Scan');
expect(html).toContain('dynamic-facet-select is-source-scan');
expect(html.lastIndexOf('aria-label="Source: Scan"')).toBeGreaterThan(html.indexOf('aria-label="Primary filters"'));
});
it('does not render a rack when the active sheet has no severity field', () => {
const missing = options();
missing.sheet = makeSheet('Synthetic findings', [['Title', 'Result'], ['One', 'Failed']], '', 'synthetic.csv');
expect(summarizeSeverities(missing)).toBeUndefined();
});
});