import { readFileSync } from 'node:fs'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } from 'vitest'; import { DatahubHistoryOverview } from '../../src/features/project-import/DatahubHistoryOverview'; const datahubStyles = readFileSync(new URL('../../src/styles/datahub-redesign.css', import.meta.url), 'utf8'); const datahubTableStyles = readFileSync(new URL('../../src/styles/datahub.css', import.meta.url), 'utf8'); const componentStyles = readFileSync(new URL('../../src/styles/components.css', import.meta.url), 'utf8'); const tileContractStyles = readFileSync(new URL('../../src/styles/tile-contract.css', import.meta.url), 'utf8'); const tokens = readFileSync(new URL('../../src/styles/ui-tokens.css', import.meta.url), 'utf8'); const overview = readFileSync(new URL('../../src/features/project-import/DatahubHistoryOverview.tsx', import.meta.url), 'utf8'); const donut = readFileSync(new URL('../../src/components/DonutMetricCard/DonutMetricCard.tsx', import.meta.url), 'utf8'); const datahub = readFileSync(new URL('../../src/features/project-import/DataHub.tsx', import.meta.url), 'utf8'); const baselines = readFileSync(new URL('../../src/features/project-import/MicrosoftBaselineLibrary.tsx', import.meta.url), 'utf8'); describe('Datahub centered reference rack layout', () => { it('uses the binding six-card donut design for the selected timeslot', () => { expect(datahubStyles).toContain('.datahub-page > .datahub-rack-module { width: min(1180px, 100%); margin-inline: auto; }'); expect(datahubStyles).toContain('.datahub-tenant-card-rack { display: grid; grid-template-columns: repeat(auto-fill, var(--tile-compact-width)); justify-content: start;'); expect(datahubStyles).toContain('.datahub-tenant-card.is-selected'); expect(datahubStyles).toContain('.datahub-tenant-processing-dot { position: absolute; z-index: 3; top: var(--datahub-readiness-dot-inset); left: var(--datahub-readiness-dot-inset);'); expect(datahubStyles).toContain('background: var(--state-online); pointer-events: none;'); expect(datahubStyles).toContain('.datahub-tenant-processing-dot[data-status="update"],'); expect(datahubStyles).toContain('.datahub-tenant-processing-dot[data-status="review"] { background: var(--warning); }'); expect(datahubStyles).toContain('.datahub-tenant-processing-dot[data-status="error"] { background: var(--state-offline); }'); expect(datahubStyles).toContain('.datahub-tenant-show[data-status="update"]'); expect(datahubStyles).toContain('border-color: var(--warning); color: var(--warning);'); expect(datahubStyles).toContain('outline: calc(var(--bw-emphasis) + var(--bw-hairline)) solid var(--tile-selected-border);'); expect(datahubStyles).toContain('outline-offset: calc(-1 * (var(--bw-emphasis) + var(--bw-hairline)));'); expect(datahubStyles).toContain('.datahub-tenant-card::before { content: none; }'); expect(datahubStyles).toContain('transition: background .12s ease, border-color .12s ease;'); expect(datahubStyles).toMatch(/\.datahub-tenant-card:hover,\s*\.datahub-tenant-card:focus-within\s*\{[^}]*border-color:\s*transparent;[^}]*background:\s*var\(--tile-surface-hover\);[^}]*box-shadow:\s*var\(--tile-card-shadow\);/s); expect(datahubStyles).not.toMatch(/\.datahub-tenant-card\s*\{[^}]*box-shadow/s); expect(datahubStyles).toContain('box-shadow: var(--tile-card-shadow);'); expect(datahubStyles).not.toContain('translateY(-2px)'); expect(datahubStyles).toContain('.datahub-tenant-select { position: absolute; inset: 0;'); expect(datahubStyles).toContain('grid-template-rows: minmax(0, 1fr) auto auto;'); expect(datahubStyles).toContain('font: var(--fw-semibold) calc(var(--donut-title-size) * 1.35)'); expect(datahubStyles).toContain('align-self: center; justify-self: center;'); expect(datahubStyles).toContain('text-align: center; text-overflow: ellipsis; white-space: normal;'); expect(datahubStyles).toContain('-webkit-line-clamp: 3;'); expect(datahubStyles).toContain('.datahub-tenant-summary { align-self: end;'); expect(datahubStyles).toContain('.datahub-tenant-latest { min-width: 0;'); expect(datahubStyles).toContain('opacity: .72;'); expect(datahubStyles).toContain('.datahub-tenant-show:active { position: absolute;'); expect(datahubStyles).toContain('background: var(--button-marked-bg);'); expect(datahubStyles).toContain('.datahub-tenant-add > span'); expect(datahubStyles).not.toContain('.datahub-reference-divider'); expect(componentStyles).toContain('Verbindlicher Donut-Standard: Referenz vom 2026-08-05.'); expect(componentStyles).toMatch(/\.donut-metric-rack\s*\{[^}]*grid-template-columns:\s*repeat\(auto-fill, var\(--tile-compact-width\)\);[^}]*justify-content:\s*start;/s); expect(tileContractStyles).toMatch(/@media \(min-width: 1101px\)\s*\{\s*\.datahub-reference-controls,\s*\.datahub-tenant-card-rack\s*\{[^}]*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);/s); expect(componentStyles).toContain('padding: var(--segment-inset);'); expect(componentStyles).not.toContain('.donut-metric-card::before'); expect(componentStyles).toContain('border-radius: var(--tile-radius);'); expect(componentStyles).toContain('background: var(--tile-surface);'); expect(componentStyles).toContain('.donut-metric-card__progress'); expect(componentStyles).not.toContain('stroke-linecap: round;'); expect(componentStyles).toContain('transform: rotate(-90deg);'); expect(componentStyles).toContain('.donut-metric-card--all { color: var(--donut-all); }'); expect(componentStyles).toContain('.donut-metric-card--deviation { color: var(--donut-deviation); }'); expect(componentStyles).toContain('.donut-metric-card--passed { color: var(--donut-passed); }'); expect(componentStyles).toContain('.donut-metric-card--not-applicable { color: var(--donut-not-applicable); }'); expect(componentStyles).toContain('.donut-metric-card--not-run { color: var(--donut-not-run); }'); expect(componentStyles).toContain('.donut-metric-card--error { color: var(--donut-error); }'); expect(tokens).toContain('--font-ui: "Inter Variable", sans-serif;'); expect(tokens).toContain('--font-donut-display: var(--font-ui);'); expect(tokens).toContain('--donut-rack-surface:'); expect(tokens).toContain('--donut-ring-size: 128px;'); expect(tokens).toContain('--donut-ring-stroke: 12px;'); expect(tokens).toContain('--datahub-readiness-dot-size: 14px;'); expect(tokens).toContain('--datahub-readiness-dot-inset: var(--space-1);'); expect(tokens).toContain('Donut-Standard: identische Geometrie, helle Referenzpalette.'); expect(tokens.match(/--segment-surface: transparent;/g)).toHaveLength(2); expect(tokens.match(/--segment-border: transparent;/g)).toHaveLength(2); expect(tokens).toContain('--donut-rack-surface: var(--segment-surface);'); expect(tokens).toContain('--donut-deviation: #9A6500;'); expect(donut).toContain(''); expect(donut).toContain("const dashArray = String(boundedPercentage) + ' ' + String(100 - boundedPercentage);"); expect(donut).toContain("const progressStyle = { '--donut-progress-dash': dashArray } as CSSProperties;"); expect(donut).toContain('key={label + \'-\' + value + \'-\' + boundedPercentage}'); expect(donut).not.toContain(' 0 ? 100 : 0} tone="all"'); expect(overview).toContain('DonutMetricCard className="datahub-reference-knob" label="Deviation"'); expect(overview).toContain('DonutMetricCard className="datahub-reference-knob" label="Passed"'); expect(overview).toContain('DonutMetricCard className="datahub-reference-knob" label="Not Applicable"'); expect(overview).toContain('DonutMetricCard className="datahub-reference-knob" label="Not Run"'); expect(overview).toContain('DonutMetricCard className="datahub-reference-knob" label="Error"'); expect(overview.match(/ onSelectProject(project.id)}'); expect(overview).toContain('className="btn datahub-tenant-show"'); expect(overview).toContain('datahub-tenant-add'); expect(overview).toContain('summarizeClientProcessingReadiness'); expect(overview).toContain('const activeProcessingRunIds = groups.flatMap(group => group.scanRunIds);'); expect(overview).toContain('activeProcessingRunIds'); expect(overview).toContain('data-status={processingSummary.state}'); expect(overview).not.toContain('EmptyTenantColumn'); expect(overview).not.toContain('datahub-history-grid'); expect(datahub).toContain('loadCanonicalProjectWorkspace('); expect(datahub).toContain('selectedTimeslot.assessmentIds'); expect(datahub).toContain('summarizeWorkspaceResults(sheet.analysis.records, profileForSheet(dataset, sheet))'); expect(datahub).toContain("'Reimport recommended'"); expect(datahub).toContain('className="tenant-processing-alert tenant-processing-alert-inline"'); expect(datahub).toContain('New ASSESSOR processing profile available'); expect(datahub).toContain("aria-label={`${report.tool.name}: ${processingHeading}`}"); expect(datahub).toContain("processing.state !== 'ready' && 0 && file.sha256), report.files.map(file => file.sha256)) === 'match'"); expect(datahub).toContain('adoptScanRunReplacement'); expect(datahub).toContain('const selectProject = useCallback((projectId: string) => {'); expect(datahub).toContain('setScanSelection({'); expect(datahub).toContain('onSelectProject={selectProject}'); expect(overview).toContain("selected || processingSummary.state === 'update' || processingSummary.state === 'review' || processingSummary.state === 'error'"); expect(overview).toContain('data-status={processingSummary.state}'); expect(datahub).toContain('setExpandedTimeslotIds(affectedTimeslotIds)'); expect(datahub).toContain('open={expandedTimeslotIds.includes(group.id)}'); expect(datahubTableStyles).toContain('.tenant-report-status[data-status="reimport-recommended"],'); expect(datahubTableStyles).toContain('.tenant-report-status[data-status="review-required"] { color: var(--warning); }'); expect(datahubTableStyles).toContain('.tenant-processing-alert[data-status="error"]'); expect(datahubTableStyles).toContain('.tenant-processing-alert-inline {'); expect(datahub).toContain('const SHOW_MITRE_MATRIX_LIBRARY = false;'); expect(datahub).toContain('{SHOW_MITRE_MATRIX_LIBRARY && .baseline-catalog { grid-area: 1 / 1 / 2 / -1; }'); expect(baselines).toContain('Imported Security Baselines'); expect(baselines).toContain('className="tenant-scan-history baseline-catalog"'); expect(baselines).not.toContain('className="tenant-scan-history baseline-catalog" open'); expect(baselines).toContain('className="tenant-history-meta"'); expect(baselines).toContain('className="tenant-history-meta baseline-title">{records.length.toLocaleString(\'en-US\')}Imported Security Baselines'); expect(baselines).toContain(''); expect(baselines).not.toContain('⌃'); expect(datahubTableStyles).toContain('.tenant-scan-history > .tenant-file-table {'); expect(datahubTableStyles).toContain('.viewer.app-area-data-hub { scrollbar-gutter: stable; }'); expect(datahubTableStyles).toContain('.tenant-scan-history .tenant-file-row {'); expect(datahubTableStyles).toContain('.tenant-scan-history .tenant-file-head {'); expect(datahubTableStyles).toContain('.tenant-scan-history .tenant-file-row b {'); expect(baselines).toContain('Upload Security Baselines (ZIP)'); expect(baselines).toContain('className="btn datahub-maintenance-action baseline-upload-action"'); expect(baselines).toContain('Actions'); expect(baselines).toContain('className="btn tenant-scan-delete"'); expect(baselines).toContain('Delete security baseline?'); expect(baselines).toContain('Baseline and derived evaluations will be deleted.'); expect(baselines).not.toContain('remain protected and cannot be deleted'); expect(baselines.indexOf('Imported Security Baselines')).toBeLessThan(baselines.indexOf('Upload Security Baselines (ZIP)')); }); it.each([ ['dead-letter', 'error', true], ['recovery-required', 'error', true], ['partial', 'ready', false], ['queued', 'processing', false], ['running', 'processing', false] ] as const)('shows actionable Manage only when needed for an unselected %s GPO client', (state, indicator, showManage) => { const markup = renderToStaticMarkup(createElement(DatahubHistoryOverview, { projects: [{ id: 'synthetic', name: 'Synthetic', description: '', attributes: {}, createdAt: '2026-09-06T00:00:00Z', updatedAt: '2026-09-06T00:00:00Z' }], projectData: {}, projectHistory: { synthetic: { version: 1, projectId: 'synthetic', assessments: [{ id: 'synthetic-slot', projectId: 'synthetic', name: 'Synthetic slot', effectiveAt: '2026-09-06T00:00:00Z', createdAt: '2026-09-06T00:00:00Z', updatedAt: '2026-09-06T00:00:00Z' }], scanRuns: [] } }, projectGpoData: { synthetic: [{ version: 1, artifactId: 'synthetic-artifact', fileName: 'synthetic.zip', sizeBytes: 1, submittedAt: '2026-09-06T00:00:00Z', updatedAt: '2026-09-06T00:00:00Z', state, attempts: 3, maxAttempts: 3, gpoCount: 0, backupInstanceCount: 0 }] }, selection: null, timeslotMetrics: null, loading: false, comparisonBusy: false, comparisonError: null, onCreate: () => undefined, onShow: () => undefined, onOpen: () => undefined, onSelectProject: () => undefined })); expect(markup).toContain(`datahub-tenant-processing-dot" data-status="${indicator}"`); expect(markup).toContain('aria-pressed="false"'); expect(markup.includes('>Manage')).toBe(showManage); expect(markup).toContain('datahub-tenant-open" type="button" disabled'); if (indicator !== 'ready') expect(markup).not.toContain('datahub-tenant-processing-dot" data-status="ready"'); }); it('renders only real client cards, exactly one selected client action, and one add card', () => { const project = (id: string, name: string) => ({ id, name, description: '', createdAt: '2026-08-22T08:00:00.000Z', updatedAt: '2026-08-22T08:00:00.000Z', attributes: {} }); const markup = renderToStaticMarkup(createElement(DatahubHistoryOverview, { projects: [project('project-a', 'Client A'), project('project-b', 'Client B')], projectData: {}, projectHistory: {}, projectGpoData: {}, selection: { projectId: 'project-b', slots: [] }, timeslotMetrics: null, loading: false, comparisonBusy: false, comparisonError: null, onCreate: () => undefined, onShow: () => undefined, onOpen: () => undefined, onSelectProject: () => undefined })); expect(markup.match(/aria-label="Mandant Client [AB] auswählen"/g)?.length).toBe(2); expect(markup.match(/aria-pressed="true"/g)?.length).toBe(1); expect(markup.match(/>Manage<\/button>/g)?.length).toBe(1); expect(markup.match(/>Open<\/button>/g)?.length).toBe(2); expect(markup.match(/title="No scan data available"/g)?.length).toBe(2); expect(markup).not.toContain('>Show'); expect(markup).toContain('aria-label="Mandant anlegen"'); expect(markup).not.toContain('Empty'); }); });