diff --git a/apps/desktop/src/app/right-sidebar/index.test.tsx b/apps/desktop/src/app/right-sidebar/index.test.tsx new file mode 100644 index 00000000000..07a0fbb0435 --- /dev/null +++ b/apps/desktop/src/app/right-sidebar/index.test.tsx @@ -0,0 +1,75 @@ +import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import type { HermesReadDirResult } from '@/global' +import { $connection, setCurrentCwd } from '@/store/session' + +import { resetProjectTreeState } from './files/use-project-tree' + +import { RightSidebarPane } from './index' + +const readDir = vi.fn<(path: string) => Promise>() +const selectPaths = vi.fn() + +function ok(entries: { name: string; path: string; isDirectory: boolean }[]): HermesReadDirResult { + return { entries } +} + +function installBridge() { + ;( + window as unknown as { + hermesDesktop: { + readDir: typeof readDir + selectPaths: typeof selectPaths + } + } + ).hermesDesktop = { readDir, selectPaths } +} + +describe('RightSidebarPane', () => { + beforeEach(() => { + $connection.set(null) + resetProjectTreeState() + setCurrentCwd('/repo') + readDir.mockReset() + selectPaths.mockReset() + readDir.mockResolvedValue(ok([{ name: 'README.md', path: '/repo/README.md', isDirectory: false }])) + selectPaths.mockResolvedValue(['/repo-next']) + installBridge() + }) + + afterEach(() => { + cleanup() + $connection.set(null) + setCurrentCwd('') + resetProjectTreeState() + delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop + }) + + it('refreshes the current tree without opening the folder picker', async () => { + const onChangeCwd = vi.fn() + + render() + + await waitFor(() => expect(screen.getByRole('button', { name: 'Refresh tree' }).hasAttribute('disabled')).toBe(false)) + + readDir.mockClear() + + fireEvent.click(screen.getByRole('button', { name: 'Refresh tree' })) + + await waitFor(() => expect(readDir).toHaveBeenCalledWith('/repo')) + expect(selectPaths).not.toHaveBeenCalled() + + fireEvent.click(screen.getByRole('button', { name: 'Open folder' })) + + await waitFor(() => + expect(selectPaths).toHaveBeenCalledWith({ + defaultPath: '/repo', + directories: true, + multiple: false, + title: 'Change working directory' + }) + ) + await waitFor(() => expect(onChangeCwd).toHaveBeenCalledWith('/repo-next')) + }) +}) diff --git a/apps/desktop/src/app/right-sidebar/index.tsx b/apps/desktop/src/app/right-sidebar/index.tsx index 8a77dbc9844..21085912fc6 100644 --- a/apps/desktop/src/app/right-sidebar/index.tsx +++ b/apps/desktop/src/app/right-sidebar/index.tsx @@ -126,12 +126,12 @@ interface FilesystemTabProps extends FileTreeBodyProps { onRefresh: () => void } -// Sidebar palette + hover-reveal: refresh tracks label hover; collapse-all -// stays visible while any folder is expanded. +// Sidebar palette + hover-reveal: header actions stay reachable while moving +// from the project label to the action buttons. const HEADER_ACTION_CLASS = 'text-sidebar-foreground/70 hover:bg-sidebar-accent! hover:text-sidebar-accent-foreground! focus-visible:ring-sidebar-ring' -const HEADER_ACTION_LABEL_REVEAL = `${HEADER_ACTION_CLASS} pointer-events-none opacity-0 transition-opacity focus-visible:pointer-events-auto focus-visible:opacity-100 peer-focus-visible/project-label:pointer-events-auto peer-focus-visible/project-label:opacity-100 peer-hover/project-label:pointer-events-auto peer-hover/project-label:opacity-100` +const HEADER_ACTION_LABEL_REVEAL = `${HEADER_ACTION_CLASS} pointer-events-none opacity-0 transition-opacity focus-visible:pointer-events-auto focus-visible:opacity-100 group-focus-within/project-header:pointer-events-auto group-focus-within/project-header:opacity-100 group-hover/project-header:pointer-events-auto group-hover/project-header:opacity-100` function FilesystemTab({ canCollapse, @@ -158,7 +158,7 @@ function FilesystemTab({ return (
-
+