feat(renderer): AppHeader wired to command store

- toggle sidebar/preview buttons dispatch through command store
- new 'shortcuts.show' command (Keyboard icon, opens shortcuts panel later)
- AppHeader.test.tsx updated: registers matching commands in beforeEach
- integration test fixed: 'Open folder' button now matches 2 (toolbar + sidebar)
- 155/155 tests pass
This commit is contained in:
Amit Haridas
2026-06-05 16:00:58 +05:30
parent 487ce2ad45
commit b1e16af62d
3 changed files with 56 additions and 7 deletions
+18 -5
View File
@@ -1,10 +1,12 @@
import { PanelLeft, PanelRight } from 'lucide-react'; import { PanelLeft, PanelRight, Keyboard } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ThemeToggle } from '@/components/theme-toggle'; import { ThemeToggle } from '@/components/theme-toggle';
import { useAppStore } from '@/stores/app-store'; import { useAppStore } from '@/stores/app-store';
import { useCommandStore } from '@/stores/command-store';
export function AppHeader() { export function AppHeader() {
const { sidebarVisible, previewVisible, toggleSidebar, togglePreview } = useAppStore(); const { sidebarVisible, previewVisible } = useAppStore();
const dispatch = useCommandStore((s) => s.dispatch);
return ( return (
<header className="flex h-14 items-center justify-between border-b border-border bg-card/40 px-4 backdrop-blur"> <header className="flex h-14 items-center justify-between border-b border-border bg-card/40 px-4 backdrop-blur">
@@ -21,7 +23,8 @@ export function AppHeader() {
size="icon" size="icon"
aria-label="Toggle sidebar" aria-label="Toggle sidebar"
aria-pressed={sidebarVisible} aria-pressed={sidebarVisible}
onClick={toggleSidebar} data-testid="header-toggle-sidebar"
onClick={() => dispatch('view.toggleSidebar')}
> >
<PanelLeft className="h-4 w-4" /> <PanelLeft className="h-4 w-4" />
</Button> </Button>
@@ -30,12 +33,22 @@ export function AppHeader() {
size="icon" size="icon"
aria-label="Toggle preview" aria-label="Toggle preview"
aria-pressed={previewVisible} aria-pressed={previewVisible}
onClick={togglePreview} data-testid="header-toggle-preview"
onClick={() => dispatch('view.togglePreview')}
> >
<PanelRight className="h-4 w-4" /> <PanelRight className="h-4 w-4" />
</Button> </Button>
<Button
variant="ghost"
size="icon"
aria-label="Keyboard shortcuts"
data-testid="header-shortcuts"
onClick={() => dispatch('shortcuts.show')}
>
<Keyboard className="h-4 w-4" />
</Button>
<ThemeToggle /> <ThemeToggle />
</div> </div>
</header> </header>
); );
} }
+34
View File
@@ -4,11 +4,21 @@ import userEvent from '@testing-library/user-event';
import { ThemeProvider } from '@/components/theme-provider'; import { ThemeProvider } from '@/components/theme-provider';
import { AppHeader } from '@/components/layout/AppHeader'; import { AppHeader } from '@/components/layout/AppHeader';
import { useAppStore } from '@/stores/app-store'; import { useAppStore } from '@/stores/app-store';
import { useCommandStore } from '@/stores/command-store';
describe('AppHeader', () => { describe('AppHeader', () => {
beforeEach(() => { beforeEach(() => {
localStorage.clear(); localStorage.clear();
useAppStore.setState({ sidebarVisible: true, previewVisible: true, zenMode: false }); useAppStore.setState({ sidebarVisible: true, previewVisible: true, zenMode: false });
useCommandStore.setState({ handlers: {} });
// Mirror what AppShell registers so dispatching actually does something.
useCommandStore.getState().register('view.toggleSidebar', () => {
useAppStore.getState().toggleSidebar();
});
useCommandStore.getState().register('view.togglePreview', () => {
useAppStore.getState().togglePreview();
});
useCommandStore.getState().register('shortcuts.show', () => {});
}); });
it('renders the app title', () => { it('renders the app title', () => {
@@ -30,4 +40,28 @@ describe('AppHeader', () => {
await userEvent.click(btn); await userEvent.click(btn);
expect(useAppStore.getState().sidebarVisible).toBe(false); expect(useAppStore.getState().sidebarVisible).toBe(false);
}); });
it('toggles preview when preview button clicked', async () => {
render(
<ThemeProvider defaultTheme="dark" attribute="class">
<AppHeader />
</ThemeProvider>
);
const btn = screen.getByRole('button', { name: /toggle preview/i });
await userEvent.click(btn);
expect(useAppStore.getState().previewVisible).toBe(false);
});
it('disables-shortcut button dispatches shortcuts.show', async () => {
render(
<ThemeProvider defaultTheme="dark" attribute="class">
<AppHeader />
</ThemeProvider>
);
const btn = screen.getByTestId('header-shortcuts');
await userEvent.click(btn);
// No observable side effect; the command was registered as a no-op.
// The test passes if no error is thrown and the click is processed.
expect(btn).toBeInTheDocument();
});
}); });
+4 -2
View File
@@ -71,9 +71,11 @@ describe('Phase 5 integration', () => {
it('opening a folder via the Open Folder button populates the tree', async () => { it('opening a folder via the Open Folder button populates the tree', async () => {
render(<AppShell />); render(<AppShell />);
const openBtn = screen.getByRole('button', { name: /open folder/i }); // Two buttons share the "Open folder" name (toolbar + sidebar).
// Either one triggers the same command, so click the first match.
const buttons = screen.getAllByRole('button', { name: /open folder/i });
await act(async () => { await act(async () => {
fireEvent.click(openBtn); fireEvent.click(buttons[0]);
}); });
// After openFolder, the tree should have children // After openFolder, the tree should have children
const state = useFileStore.getState(); const state = useFileStore.getState();