83 lines
5.4 KiB
TypeScript
83 lines
5.4 KiB
TypeScript
// @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(<section className="viewer app-area-dashboard"><ClientInfoTabs tabs={[
|
|
{ id: 'overview', label: 'Overview', content: Array.from({ length: 100 }, (_, i) => <p key={i}>Synthetic row {i}</p>) },
|
|
{ id: 'security', label: 'Security' }
|
|
]}/></section>));
|
|
const wrapper = host.querySelector<HTMLElement>('.client-info-tabs')!;
|
|
const tablist = host.querySelector<HTMLElement>('[role="tablist"]')!;
|
|
const panels = host.querySelectorAll<HTMLElement>('[role="tabpanel"]');
|
|
const tabs = host.querySelectorAll<HTMLButtonElement>('[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(<ClientInfoTabs tabs={[
|
|
{ id: 'overview', label: 'Overview', content: <input aria-label="Synthetic overview state" defaultValue="retained"/> },
|
|
{ id: 'security', label: 'Security' },
|
|
{ id: 'future', label: 'Future', content: 'Synthetic future panel' }
|
|
]}/>));
|
|
const tabs = Array.from(container.querySelectorAll<HTMLButtonElement>('[role="tab"]'));
|
|
const panels = Array.from(container.querySelectorAll<HTMLElement>('[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(); }
|
|
});
|
|
});
|