// @vitest-environment jsdom
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { describe, expect, it, vi } from 'vitest';
import type { ImportedDataset } from '../../src/domain/dataset';
import { rowsToCsv } from '../../src/exports/csv-export';
import { DefenderVulnerabilityPanel } from '../../src/features/defender-for-iot/DefenderVulnerabilityPanel';
import { makeSheet } from '../../src/importers/sheet-factory';
vi.mock('../../src/features/search-filter/WorkTimeslotControl', () => ({
WorkTimeslotControl: () =>
}));
function dataset(): ImportedDataset {
const summary = [
['Name', 'IP', 'Site', 'Sensor', 'TotalCVEs', 'Critical', 'High', 'Medium', 'Low'],
['device-a', '192.0.2.1', 'Site A', 'sensor-a', '1', '1', '0', '0', '0'],
['device-b', '192.0.2.2', 'Site B', 'sensor-b', '2', '1', '1', '0', '0']
];
const observationA = { name: 'device-a', ip: '192.0.2.1', site: 'Site A', sensor: 'sensor-a', cve: 'CVE-2026-1001', severity: 'Critical', description: 'Synthetic description A', additionalSourceFields: {}, source: { tableIndex: 1, rowIndex: 1 } };
const observationB = { name: 'device-b', ip: '192.0.2.2', site: 'Site B', sensor: 'sensor-b', cve: 'CVE-2026-1002', severity: 'High', description: 'Synthetic description B', additionalSourceFields: {}, source: { tableIndex: 1, rowIndex: 2 } };
const observationC = { name: 'device-b', ip: '192.0.2.2', site: 'Site B', sensor: 'sensor-b', cve: 'CVE-2026-1001', severity: 'Critical', description: 'Synthetic description A', additionalSourceFields: {}, source: { tableIndex: 1, rowIndex: 3 } };
const detail = [
['CVE', 'Device Identity', 'Name', 'IP', 'Site', 'Reporting Sensors', 'Severity', 'Beschreibung', 'Observation Count', 'Source Observations'],
['CVE-2026-1001', 'ip:192.0.2.1', 'device-a', '192.0.2.1', 'Site A', 'sensor-a', 'Critical', 'Synthetic description A', '1', JSON.stringify([observationA])],
['CVE-2026-1002', 'ip:192.0.2.2', 'device-b', '192.0.2.2', 'Site B', 'sensor-b', 'High', 'Synthetic description B', '1', JSON.stringify([observationB])],
['CVE-2026-1001', 'ip:192.0.2.2', 'device-b', '192.0.2.2', 'Site B', 'sensor-b', 'Critical', 'Synthetic description A', '1', JSON.stringify([observationC])]
];
return {
fileName: 'Synthetic project', fileSize: 1, lastModified: 0, format: 'Project ยท Microsoft Defender for IoT', rawText: '',
profileId: 'defender-for-iot-vulnerabilities', profileVersion: '1.0.0', scanTool: 'Microsoft Defender for IoT',
sheets: [
makeSheet('Vulnerabilities', detail, rowsToCsv(detail), 'synthetic.xlsx', { sourceTableIndex: 1, sourceSheet: 'Vulnerabilities', toolId: 'defender-for-iot-vulnerabilities' }),
makeSheet('Assets', summary, rowsToCsv(summary), 'synthetic.xlsx', { sourceTableIndex: 0, sourceSheet: 'Assets', toolId: 'defender-for-iot-vulnerabilities' })
]
};
}
function toggleButtons(container: HTMLElement): HTMLButtonElement[] {
return [...container.querySelectorAll('.defender-view .defender-row-toggle')];
}
function expectExpandedInline(container: HTMLElement, button: HTMLButtonElement) {
expect(button.getAttribute('aria-expanded')).toBe('true');
expect(button.querySelector('.triangle-icon-down')).not.toBeNull();
const detailRow = button.closest('tr')?.nextElementSibling;
expect(detailRow?.classList.contains('defender-detail-row')).toBe(true);
expect(detailRow?.querySelector(`#${button.getAttribute('aria-controls')}`)).not.toBeNull();
expect(container.querySelectorAll('.defender-view > .defender-table-wrap > .defender-table > tbody > .defender-detail-row')).toHaveLength(1);
}
function exerciseView(container: HTMLElement, buttons: HTMLButtonElement[]) {
expect(buttons).toHaveLength(2);
expect(buttons[0].querySelector('.triangle-icon-right')).not.toBeNull();
act(() => buttons[0].click());
expectExpandedInline(container, buttons[0]);
act(() => buttons[1].click());
expect(buttons[0].getAttribute('aria-expanded')).toBe('false');
expectExpandedInline(container, buttons[1]);
act(() => buttons[1].click());
expect(container.querySelector('.defender-detail-row')).toBeNull();
}
describe('Defender inline drilldowns', () => {
it('expands, moves, and collapses details beside CVE, device, and site rows', () => {
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const container = document.createElement('div');
const root = createRoot(container);
act(() => root.render());
const cveListHeaders = [...container.querySelectorAll('.defender-view > .defender-table-wrap > .defender-table > thead th')].map(header => header.textContent);
expect(cveListHeaders).toEqual(['CVE', 'Severity', 'Affected devices', 'Sites', 'Sensors']);
const cveButtons = toggleButtons(container);
exerciseView(container, cveButtons);
act(() => cveButtons[0].click());
expect(container.querySelector('.defender-detail-row')?.textContent).toContain('Synthetic description A');
expect(container.querySelector('#defender-cve-detail-0 details')).toBeNull();
const evidenceHeaders = [...container.querySelectorAll('#defender-cve-detail-0 > .defender-table-wrap > .defender-table > thead th')].map(header => header.textContent);
expect(evidenceHeaders).toEqual(['Device', 'IP', 'Site', 'Sensor']);
expect(container.querySelectorAll('#defender-cve-detail-0 > .defender-table-wrap > .defender-table > tbody > tr')).toHaveLength(2);
act(() => cveButtons[0].click());
const devicesTab = [...container.querySelectorAll('.defender-tabs button')].find(button => button.textContent === 'Devices');
act(() => devicesTab?.click());
const deviceButtons = toggleButtons(container);
exerciseView(container, deviceButtons);
act(() => deviceButtons[0].click());
const deviceCveHeaders = [...container.querySelectorAll('#defender-device-detail-0 > .defender-table-wrap > .defender-table > thead th')].map(header => header.textContent);
expect(deviceCveHeaders).toEqual(['CVE', 'Severity']);
act(() => deviceButtons[0].click());
const sitesTab = [...container.querySelectorAll('.defender-tabs button')].find(button => button.textContent === 'Sites');
act(() => sitesTab?.click());
exerciseView(container, toggleButtons(container));
act(() => root.unmount());
});
});