// @vitest-environment jsdom import { act } from 'react'; import { readFileSync } from 'node:fs'; import { createRoot } from 'react-dom/client'; import { describe, expect, it, vi } from 'vitest'; import { ClientInfoTabs } from '../../src/features/dashboard/ClientInfoTabs'; describe.each(['dark', 'light'])('Client Info attached tabs (%s)', theme => { it('keeps the tablist outside the scrolling panel and preserves the scrolled Overview through a tab switch', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); document.documentElement.dataset.theme = theme; const style = document.createElement('style'); style.textContent = readFileSync('src/styles/datahub.css', 'utf8') + readFileSync('src/styles/layout.css', 'utf8'); document.head.append(style); const host = document.createElement('div'); document.body.append(host); host.className = 'workspace workspace-dashboard'; const root = createRoot(host); try { await act(async () => root.render(

Synthetic row {i}

) }, { id: 'security', label: 'Security' } ]}/>
)); const wrapper = host.querySelector('.client-info-tabs')!; const tablist = host.querySelector('[role="tablist"]')!; const panels = host.querySelectorAll('[role="tabpanel"]'); const tabs = host.querySelectorAll('[role="tab"]'); expect(getComputedStyle(wrapper).height).toBe('100%'); expect(getComputedStyle(wrapper).overflow).toBe('hidden'); expect(getComputedStyle(tablist).flex).toBe('0 0 auto'); expect(getComputedStyle(tablist).paddingTop).toBe('0px'); expect(getComputedStyle(tabs[0]).borderTopLeftRadius).toBe('var(--panel-radius)'); expect(getComputedStyle(tabs[1]).borderTopLeftRadius).not.toBe('var(--panel-radius)'); expect(readFileSync('src/styles/layout.css', 'utf8')).toContain('.app-header-panel, .viewer { border-radius: var(--panel-radius); }'); expect(readFileSync('src/styles/ui-tokens.css', 'utf8')).toContain('--panel-radius: 10px;'); expect(getComputedStyle(host).overflow).toBe('visible'); expect(getComputedStyle(host.querySelector('.viewer')!).overflow).toBe('visible'); expect(readFileSync('src/styles/layout.css', 'utf8')).toMatch(/\.viewer\.app-area-dashboard\s*\{[^}]*margin-top:\s*calc\(10px - var\(--client-info-tab-height\)\)/); expect(getComputedStyle(panels[0]).overflowY).toBe('auto'); expect(panels[0].contains(tablist)).toBe(false); panels[0].scrollTop = 400; await act(async () => panels[0].dispatchEvent(new Event('scroll'))); expect(tabs[0].getAttribute('aria-selected')).toBe('true'); await act(async () => tabs[1].click()); expect(panels[1].hidden).toBe(false); await act(async () => tabs[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }))); expect(panels[0].hidden).toBe(false); expect(panels[0].scrollTop).toBe(400); expect(getComputedStyle(tablist).display).toBe('flex'); } finally { await act(async () => root.unmount()); host.remove(); style.remove(); vi.unstubAllGlobals(); } }); it('starts on Overview, opens an empty Security panel and preserves Overview through mouse and keyboard switches', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); document.documentElement.dataset.theme = theme; const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); try { await act(async () => root.render( }, { id: 'security', label: 'Security' }, { id: 'future', label: 'Future', content: 'Synthetic future panel' } ]}/>)); const tabs = Array.from(container.querySelectorAll('[role="tab"]')); const panels = Array.from(container.querySelectorAll('[role="tabpanel"]')); expect(tabs.map(tab => tab.className)).toEqual(Array(3).fill('client-info-tab')); expect(tabs.map(tab => tab.getAttribute('aria-selected'))).toEqual(['true', 'false', 'false']); const input = container.querySelector('input')!; input.value = 'changed'; await act(async () => tabs[1].click()); expect(panels.map(panel => panel.hidden)).toEqual([true, false, true]); expect(panels[1].childNodes).toHaveLength(0); expect(tabs[1].getAttribute('aria-controls')).toBe(panels[1].id); await act(async () => tabs[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))); expect(panels[2].hidden).toBe(false); expect(document.activeElement).toBe(tabs[2]); await act(async () => tabs[2].dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }))); expect(panels[0].hidden).toBe(false); expect(input.value).toBe('changed'); expect(tabs.map(tab => tab.tabIndex)).toEqual([0, -1, -1]); await act(async () => tabs[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }))); expect(panels[2].hidden).toBe(false); } finally { await act(async () => root.unmount()); container.remove(); vi.unstubAllGlobals(); } }); }); describe('Client Info lazy engine tabs', () => { it('mounts a lazy tab only while it is selected, so hidden engine tabs start no requests', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const mounted = vi.fn(); function Probe() { mounted(); return

Synthetic engine tab

; } const host = document.createElement('div'); document.body.append(host); const root = createRoot(host); try { await act(async () => root.render(Synthetic overview

}, { id: 'compliance', label: 'Compliance', content: , lazy: true } ]}/>)); const tabs = host.querySelectorAll('[role="tab"]'); const panels = host.querySelectorAll('[role="tabpanel"]'); expect(mounted).not.toHaveBeenCalled(); expect(panels[1].childNodes).toHaveLength(0); await act(async () => tabs[1].click()); expect(mounted).toHaveBeenCalled(); expect(panels[1].textContent).toBe('Synthetic engine tab'); await act(async () => tabs[0].click()); expect(panels[1].childNodes).toHaveLength(0); } finally { await act(async () => root.unmount()); host.remove(); vi.unstubAllGlobals(); } }); });