// @vitest-environment jsdom
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { useApp } from '../../src/app/AppProvider';
import { DashboardPanel } from '../../src/features/dashboard/DashboardPanel';
import { DatasetViewer } from '../../src/features/dataset-viewer/DatasetViewer';
import { SearchFilterBar } from '../../src/features/search-filter/SearchFilterBar';
import { appendPolicyObservationsToDataset } from '../../src/features/project-import/policy-observations-to-dataset';
import { genericProfile } from '../../src/profiles/generic/generic-profile';
import { makeSheet } from '../../src/importers/sheet-factory';
import { filterAndSortRecords } from '../../src/features/search-filter/filter-records';
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
import type { StoredPolicyEvaluation } from '../../src/domain/policy-evaluation';
import type { ViewMode } from '../../src/app/app-state';
import type { DynamicFacetFilter } from '../../src/features/search-filter/dynamic-facets';
vi.mock('../../src/app/AppProvider', () => {
const useApp = vi.fn();
return { useApp, useOptionalApp: useApp };
});
vi.mock('../../src/features/search-filter/WorkTimeslotControl', () => ({ WorkTimeslotControl: () => Synthetic timeslot }));
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); Reflect.deleteProperty(HTMLElement.prototype, 'scrollIntoView'); });
function fixture(version: '1.3.3' | '1.4.0' | '1.9.0') {
vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} });
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', { value: vi.fn(), configurable: true });
const observation = { sourceKind: 'policy-observation', sourceResultId: 'observation', projectId: 'synthetic',
assessmentId: 'synthetic-slot', title: 'Synthetic setting', importedAt: '2026-09-01T00:00:00Z',
policyScope: 'computer', policyIdentityKey: 'identity', policyIdentityStatus: 'resolved',
policyBackupInstanceId: 'backup', policyDeliveryMechanism: 'registry-policy',
policyValues: [{ kind: 'registry-policy', key: 'Software\\Policies\\Synthetic', valueName: 'Mode',
operation: 'set-value', typeCode: 4, data: { kind: 'uint32', value: 0 } }]
} as unknown as UnifiedResultProjection;
const evaluation = { run: { evaluator: { id: 'policy-baseline-evaluation', version }, completedAt: '2026-09-02T00:00:00Z' },
results: [{ id: 'comparison', observationIds: ['observation'], baselineId: 'baseline', baselineLabel: 'Synthetic baseline',
baselineState: 'explicit-recommendation', classification: 'confirmed_configured_deviation', customerState: 'configured',
identity: { id: 'identity', scope: 'computer', target: { kind: 'registry-value', hive: 'HKEY_LOCAL_MACHINE',
key: 'Software\\Policies\\Synthetic', valueName: 'Mode' } }, diagnosticCodes: [],
comparator: { version: 1, comparator: { kind: 'equality' }, relation: 'different',
actual: { kind: 'integer', value: '0' }, baseline: { kind: 'integer', value: '1' }, diagnosticCodes: [] }
}]
} as unknown as StoredPolicyEvaluation;
const dataset = appendPolicyObservationsToDataset(null, 'Synthetic', [observation], [evaluation], new Map())!;
const state = { dataset, activeSheet: dataset.sheets[0], activeProfile: genericProfile, reviews: {},
facetFilters: {}, statusFilter: 'deviation', search: '', actionOnly: false, pathFindingIds: [],
sortColumn: '', sortDirection: 'asc', titleColumn: 'Title', selectedRecordId: '', view: 'table',
setActiveSheet: vi.fn(), setSelectedRecord: vi.fn(), updateReview: vi.fn(),
setView: vi.fn((value: ViewMode) => { state.view = value; }),
setStatusFilter: vi.fn((value: string) => { state.statusFilter = value; }),
setSearch: vi.fn((value: string) => { state.search = value; }),
setActionOnly: vi.fn((value: boolean) => { state.actionOnly = value; }),
setPathFindingIds: vi.fn((value: string[]) => { state.pathFindingIds = value; }),
resetFilters: vi.fn(() => { state.facetFilters = {}; state.statusFilter = 'deviation'; }),
setFacetFilter: vi.fn((id: string, value: DynamicFacetFilter) => {
state.facetFilters = { ...state.facetFilters, [id]: value };
})
} as unknown as ReturnType;
vi.mocked(useApp).mockImplementation(() => state);
return { state, evaluation };
}
describe('saved GPO evaluation visibility after an evaluator update', () => {
it('shows a current backup-scope absence when the user selects Not configured', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const { state, evaluation } = fixture('1.9.0');
evaluation.results[0].customerState = 'missing';
evaluation.results[0].observationIds = [];
evaluation.results[0].comparator.actual = { kind: 'none' };
evaluation.results[0].baselineVariantAssessment = {
version: 1, scope: 'backup-configuration', decision: 'all-fail', actualValues: [], diagnosticCodes: [], variants: []
};
evaluation.results[0].diagnosticCodes = ['policy-evaluation-customer-absence-scope-limited'];
const dataset = appendPolicyObservationsToDataset(null, 'Synthetic', [], [evaluation], new Map())!;
state.dataset = dataset;
state.activeSheet = dataset.sheets[0];
state.statusFilter = 'all';
const container = document.createElement('div');
const root = createRoot(container);
try {
await act(async () => root.render(<>>));
const status = container.querySelector('select[aria-label="Result Status"]')!;
expect([...status.options].map(option => option.value)).toContain('not-configured');
await act(async () => {
status.value = 'not-configured';
status.dispatchEvent(new Event('change', { bubbles: true }));
});
await act(async () => root.render(<>>));
expect(state.statusFilter).toBe('not-configured');
expect(filterAndSortRecords(state.activeSheet, state)).toHaveLength(1);
expect(container.querySelector('.work-filter-empty')).toBeNull();
expect(container.textContent).toContain('Not configured');
} finally { await act(async () => root.unmount()); }
});
it('distinguishes a current unresolved comparison from an outdated evaluation and missing evidence', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const { state } = fixture('1.4.0');
const sheet = makeSheet('GPOs', [['Title', 'Category', 'Status', 'Source: Scan'],
['Synthetic mixed variants', 'GPOs', 'Review required', 'GPOs']], '', 'Synthetic', { toolId: 'group-policy' });
state.activeSheet = sheet;
state.dataset = { ...state.dataset!, sheets: [sheet] };
const container = document.createElement('div');
const root = createRoot(container);
try {
await act(async () => root.render());
expect(container.querySelector('.client-overall-status')).toBeNull();
expect(container.querySelector('.client-assessment-gaps')).toBeNull();
expect(container.querySelector('.client-priority-findings')).not.toBeNull();
} finally { await act(async () => root.unmount()); }
});
it.each(['1.3.3', '1.4.0'] as const)('opens the saved %s comparison from Client Info instead of an empty Deviation view', async version => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const { state, evaluation } = fixture(version);
const original = JSON.stringify(evaluation);
const container = document.createElement('div');
const root = createRoot(container);
const openWork = vi.fn();
try {
await act(async () => root.render());
expect(container.querySelector('.client-overall-status')).toBeNull();
expect(container.querySelector('.client-assessment-gaps')).toBeNull();
// Seit 2026-09-25 liegen die Quellbeitraege im Results-Fenster neben Overall Score.
const overallResults = container.querySelector('.client-overall button[aria-haspopup="dialog"]')!;
await act(async () => overallResults.click());
const source = document.body.querySelector('.floating-data-window .client-source-gpo')!;
expect(source.textContent).toContain('Re-evaluation required');
expect(source.textContent).not.toContain('0 baseline deviations');
await act(async () => source.click());
expect(openWork).toHaveBeenCalledOnce();
expect(state.statusFilter).toBe('all');
expect(state.facetFilters['provenance:source-scan']?.values).toHaveLength(1);
state.view = 'table';
await act(async () => root.render(<>>));
expect(container.textContent).toContain('Review required');
expect(container.textContent).toContain(`Fail · Engine ${version}`);
const status = container.querySelector('select[aria-label="Result Status"]')!;
expect([...status.options].map(option => option.value)).toEqual(['all', 'deviation', 'passed', 'not-configured', 'no-baseline-rule', 'not-comparable', 'conflicts', 'duplicates']);
expect(filterAndSortRecords(state.activeSheet, { ...state, statusFilter: 'passed' })).toHaveLength(0);
expect(filterAndSortRecords(state.activeSheet, { ...state, statusFilter: 'deviation' })).toHaveLength(0);
expect(JSON.stringify(evaluation)).toBe(original);
} finally { await act(async () => root.unmount()); }
});
it.each(['records', 'columns', 'table', 'raw'] as const)('searches all findings from the empty %s Work view and clears the GPO source filter', async view => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const { state } = fixture('1.4.0');
state.view = view;
const container = document.createElement('div');
const root = createRoot(container);
try {
await act(async () => root.render(<>>));
const source = container.querySelector('select[aria-label="Source: Scan"]')!;
await act(async () => {
source.value = source.options[1].value;
source.dispatchEvent(new Event('change', { bubbles: true }));
});
await act(async () => root.render(<>>));
expect(container.querySelector('[aria-label="ASSESSOR – No dataset open"]')).toBeNull();
expect(container.textContent).toContain('No results match the current filters');
await act(async () => [...container.querySelectorAll('.work-filter-empty button')]
.find(button => button.textContent === 'Search all Findings')!.click());
await act(async () => root.render(<>>));
expect(state.statusFilter).toBe('all');
expect(state.facetFilters['provenance:source-scan']).toBeUndefined();
expect(container.querySelector('.work-filter-empty')).toBeNull();
expect(filterAndSortRecords(state.activeSheet, state)).toHaveLength(1);
expect(container.textContent).toContain('Mode');
expect(state.view).toBe(view);
} finally { await act(async () => root.unmount()); }
});
});