// @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()); } }); });