fix: toggle active header chat panels (#1317)

This commit is contained in:
Brad Groux 2026-09-02 18:26:51 -05:00 • committed by GitHub
parent 8706b2b765
commit 9529d7e59a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 14 deletions

View file

@ -1,10 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, screen, waitFor } from '@testing-library/react';
import { cleanup, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ViewProvider } from '@/contexts/ViewContext';
import { KeyboardProvider } from '@/hooks/useKeyboard';
import { Header } from '@/components/layout/Header';
import { DesktopBottomPanel } from '@/components/layout/DesktopBottomPanel';
import { DesktopShellProvider } from '@/components/layout/DesktopShellContext';
import { UserMenu } from '@/components/layout/UserMenu';
import { WorkspaceSwitcher } from '@/components/layout/WorkspaceSwitcher';
@ -137,7 +138,7 @@ function renderHeaderChrome() {
);
}
function renderDesktopHeaderChrome() {
function renderDesktopHeaderChrome(options: { withBottomPanel?: boolean } = {}) {
Object.defineProperty(window, 'veritasDesktop', {
configurable: true,
value: {
@ -151,6 +152,7 @@ function renderDesktopHeaderChrome() {
<ViewProvider>
<DesktopShellProvider>
<Header />
{options.withBottomPanel && <DesktopBottomPanel />}
</DesktopShellProvider>
</ViewProvider>
</KeyboardProvider>
@ -159,7 +161,22 @@ function renderDesktopHeaderChrome() {
describe('layout chrome Mantine migration', () => {
beforeEach(() => {
const storage = new Map<string, string>();
Object.defineProperty(window, 'localStorage', {
configurable: true,
value: {
get length() {
return storage.size;
},
clear: () => storage.clear(),
getItem: (key: string) => storage.get(key) ?? null,
key: (index: number) => Array.from(storage.keys())[index] ?? null,
removeItem: (key: string) => storage.delete(key),
setItem: (key: string, value: string) => storage.set(key, value),
} satisfies Storage,
});
vi.clearAllMocks();
window.localStorage.clear();
window.history.replaceState({}, '', '/');
window.HTMLElement.prototype.scrollIntoView = vi.fn();
});
@ -257,12 +274,52 @@ describe('layout chrome Mantine migration', () => {
expect(screen.getByRole('button', { name: 'Collapse left sidebar' })).toBeDefined();
expect(screen.getByRole('button', { name: 'Expand right sidebar' })).toBeDefined();
expect(screen.getByRole('button', { name: 'Open chat dock' })).toBeDefined();
expect(screen.getByRole('button', { name: 'Board Chat' })).toBeDefined();
expect(screen.getByRole('button', { name: 'Squad Chat' })).toBeDefined();
expect(screen.getByRole('button', { name: 'Open Board Chat' })).toBeDefined();
expect(screen.getByRole('button', { name: 'Open Squad Chat' })).toBeDefined();
const brandIcon = screen
.getByRole('button', { name: 'Refresh page' })
.querySelector('img[src="/icons/pwa-icon-192.png"]');
expect(brandIcon).toBeDefined();
});
it.each(['right', 'bottom'] as const)(
'toggles and switches the header chat controls with the %s dock',
async (dockPosition) => {
window.localStorage.setItem('veritas.workbench.dockPosition', dockPosition);
const user = userEvent.setup();
renderDesktopHeaderChrome({ withBottomPanel: true });
const openBoard = screen.getByRole('button', { name: 'Open Board Chat' });
const openSquad = screen.getByRole('button', { name: 'Open Squad Chat' });
expect(openBoard.getAttribute('aria-pressed')).toBe('false');
expect(openSquad.getAttribute('aria-pressed')).toBe('false');
await user.click(openBoard);
const closeBoard = screen.getByRole('button', { name: 'Close Board Chat' });
expect(closeBoard.getAttribute('aria-pressed')).toBe('true');
expect(screen.getByRole('button', { name: 'Switch to Squad Chat' })).toBeDefined();
await user.click(closeBoard);
expect(screen.getByRole('button', { name: 'Open Board Chat' })).toBeDefined();
await user.click(screen.getByRole('button', { name: 'Open Board Chat' }));
await user.click(screen.getByRole('button', { name: 'Switch to Squad Chat' }));
const closeSquad = screen.getByRole('button', { name: 'Close Squad Chat' });
expect(closeSquad.getAttribute('aria-pressed')).toBe('true');
expect(screen.getByRole('button', { name: 'Switch to Board Chat' })).toBeDefined();
await user.click(closeSquad);
await waitFor(() => {
const board = screen.getByRole('button', { name: 'Open Board Chat' });
const squad = screen.getByRole('button', { name: 'Open Squad Chat' });
expect([board, squad]).toContain(document.activeElement);
});
await user.click(screen.getByRole('button', { name: 'Open Board Chat' }));
const dock = screen.getByRole('region', { name: `Workbench ${dockPosition} dock` });
await user.click(within(dock).getByRole('button', { name: `Close ${dockPosition} dock` }));
expect(screen.getByRole('button', { name: 'Open Board Chat' })).toBeDefined();
}
);
});

View file

@ -166,6 +166,23 @@ export function Header() {
openBottomPanel,
toggleBottomPanel,
} = useDesktopShell();
const usesWorkbenchChat = isDesktopClient || supportsWorkbenchPanel;
const boardChatActive = usesWorkbenchChat && bottomPanel === 'board-chat';
const squadChatActive = usesWorkbenchChat && bottomPanel === 'squad-chat';
const boardChatAction = !usesWorkbenchChat
? 'Open Board Chat'
: boardChatActive
? 'Close Board Chat'
: bottomPanel
? 'Switch to Board Chat'
: 'Open Board Chat';
const squadChatAction = !usesWorkbenchChat
? 'Open Squad Chat'
: squadChatActive
? 'Close Squad Chat'
: bottomPanel
? 'Switch to Squad Chat'
: 'Open Squad Chat';
const toggleView = useCallback(
(nextView: NavigationItem['view']) => setView(view === nextView ? 'board' : nextView),
@ -196,6 +213,14 @@ export function Header() {
setChatOpen(true);
}, [isDesktopClient, markPanelLoaded, openBottomPanel, supportsWorkbenchPanel]);
const toggleChatPanel = useCallback(() => {
if (usesWorkbenchChat) {
toggleBottomPanel('board-chat');
return;
}
openChatPanel();
}, [openChatPanel, toggleBottomPanel, usesWorkbenchChat]);
const openSearchDialog = useCallback(
(preset?: SearchPreset) => {
markPanelLoaded('search');
@ -214,6 +239,14 @@ export function Header() {
setSquadChatOpen(true);
}, [isDesktopClient, markPanelLoaded, openBottomPanel, supportsWorkbenchPanel]);
const toggleSquadChatPanel = useCallback(() => {
if (usesWorkbenchChat) {
toggleBottomPanel('squad-chat');
return;
}
openSquadChatPanel();
}, [openSquadChatPanel, toggleBottomPanel, usesWorkbenchChat]);
const openSettingsDialog = useCallback(
(section?: string) => {
markPanelLoaded('settings');
@ -504,22 +537,24 @@ export function Header() {
{!isCompactHeader && (
<>
<ActionIcon
variant="subtle"
color="gray"
variant={boardChatActive ? 'light' : 'subtle'}
color={boardChatActive ? 'veritas' : 'gray'}
size={32}
onClick={openChatPanel}
aria-label="Board Chat"
title="Board Chat"
onClick={toggleChatPanel}
aria-label={boardChatAction}
aria-pressed={boardChatActive}
title={boardChatAction}
>
<MessageSquare className="h-4 w-4" aria-hidden="true" />
</ActionIcon>
<ActionIcon
variant="subtle"
color="gray"
variant={squadChatActive ? 'light' : 'subtle'}
color={squadChatActive ? 'veritas' : 'gray'}
size={32}
onClick={openSquadChatPanel}
aria-label="Squad Chat"
title="Squad Chat — Agent communication"
onClick={toggleSquadChatPanel}
aria-label={squadChatAction}
aria-pressed={squadChatActive}
title={squadChatAction}
>
<Users className="h-4 w-4" aria-hidden="true" />
</ActionIcon>