diff --git a/src/renderer/components/GitSidebar/GitSidebar.css b/src/renderer/components/GitSidebar/GitSidebar.css index bda17b4..05b73cf 100644 --- a/src/renderer/components/GitSidebar/GitSidebar.css +++ b/src/renderer/components/GitSidebar/GitSidebar.css @@ -36,6 +36,61 @@ padding: 0 12px 8px; } +.git-sidebar-icon-button { + width: 20px; + height: 20px; + padding: 0; +} + +.git-sidebar-icon-button svg { + width: 16px; + height: 16px; +} + +.git-action-branch-line, +.git-action-stem, +.git-action-arrow, +.git-action-prune-mark { + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + fill: none; +} + +.git-action-branch-line { + stroke-width: 1.3; + opacity: 0.9; +} + +.git-action-branch-dot { + fill: currentColor; +} + +.git-action-stem { + stroke-width: 1.4; +} + +.git-action-stem--dotted { + stroke-dasharray: 1.4 1.8; +} + +.git-action-stem--solid { + stroke-dasharray: none; +} + +.git-action-arrow { + stroke-width: 1.4; +} + +.git-action-prune-mark { + stroke-width: 1.3; +} + +.git-sidebar-icon-button:disabled { + opacity: 0.45; + cursor: not-allowed; +} + .git-sidebar-section { display: flex; flex-direction: column; diff --git a/src/renderer/components/GitSidebar/GitSidebar.tsx b/src/renderer/components/GitSidebar/GitSidebar.tsx index 0e6be39..9713b34 100644 --- a/src/renderer/components/GitSidebar/GitSidebar.tsx +++ b/src/renderer/components/GitSidebar/GitSidebar.tsx @@ -491,35 +491,93 @@ export const GitSidebar: React.FC = () => {
{actionLoading && ( diff --git a/tests/renderer/components/GitSidebar.test.tsx b/tests/renderer/components/GitSidebar.test.tsx index f53cab4..945f5c0 100644 --- a/tests/renderer/components/GitSidebar.test.tsx +++ b/tests/renderer/components/GitSidebar.test.tsx @@ -661,6 +661,63 @@ describe('GitSidebar', () => { }); }); + it('renders repo actions as icon-only buttons with hover tooltips', async () => { + (window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({ + isRepo: true, + rootPath: '/repo/path', + currentBranch: 'main', + hasRemote: true, + }); + + render(); + + const repoActions = await screen.findByRole('group', { name: /repository actions/i }); + const fetchButton = within(repoActions).getByRole('button', { name: /^fetch$/i }); + const pullButton = within(repoActions).getByRole('button', { name: /^pull$/i }); + const pushButton = within(repoActions).getByRole('button', { name: /^push$/i }); + const pruneButton = within(repoActions).getByRole('button', { name: /^prune lfs$/i }); + + expect(fetchButton).toHaveAttribute('title', 'Fetch'); + expect(pullButton).toHaveAttribute('title', 'Pull'); + expect(pushButton).toHaveAttribute('title', 'Push'); + expect(pruneButton).toHaveAttribute('title', 'Prune LFS'); + + expect(within(repoActions).queryByText('Fetch')).not.toBeInTheDocument(); + expect(within(repoActions).queryByText('Pull')).not.toBeInTheDocument(); + expect(within(repoActions).queryByText('Push')).not.toBeInTheDocument(); + expect(within(repoActions).queryByText('Prune LFS')).not.toBeInTheDocument(); + }); + + it('uses unified branch baseline icons with action-specific arrow direction styles', async () => { + (window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({ + isRepo: true, + rootPath: '/repo/path', + currentBranch: 'main', + hasRemote: true, + }); + + render(); + + const repoActions = await screen.findByRole('group', { name: /repository actions/i }); + const fetchButton = within(repoActions).getByRole('button', { name: /^fetch$/i }); + const pullButton = within(repoActions).getByRole('button', { name: /^pull$/i }); + const pushButton = within(repoActions).getByRole('button', { name: /^push$/i }); + + const fetchIcon = within(fetchButton).getByTestId('git-action-icon-fetch'); + expect(within(fetchIcon).getByTestId('git-action-branch-line')).toBeInTheDocument(); + expect(within(fetchIcon).getByTestId('git-action-branch-dot')).toBeInTheDocument(); + expect(within(fetchIcon).getByTestId('git-action-stem-fetch')).toHaveClass('git-action-stem--dotted'); + expect(within(fetchIcon).getByTestId('git-action-arrow-fetch')).toHaveClass('git-action-arrow--towards-branch'); + + const pullIcon = within(pullButton).getByTestId('git-action-icon-pull'); + expect(within(pullIcon).getByTestId('git-action-stem-pull')).toHaveClass('git-action-stem--solid'); + expect(within(pullIcon).getByTestId('git-action-arrow-pull')).toHaveClass('git-action-arrow--towards-branch'); + + const pushIcon = within(pushButton).getByTestId('git-action-icon-push'); + expect(within(pushIcon).getByTestId('git-action-stem-push')).toHaveClass('git-action-stem--solid'); + expect(within(pushIcon).getByTestId('git-action-arrow-push')).toHaveClass('git-action-arrow--away-branch'); + }); + it('shows in-progress feedback while prune lfs is running', async () => { let resolvePrune: ((value: { success: boolean; dryRun: boolean; verifyRemote: boolean; recentCommitsToKeep: number }) => void) | null = null; (window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({ @@ -684,7 +741,7 @@ describe('GitSidebar', () => { }); expect(screen.getByRole('status')).toHaveTextContent(/pruning local git lfs cache/i); - expect(screen.getByRole('button', { name: /pruning/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /prune lfs/i })).toBeDisabled(); await act(async () => { resolvePrune?.({ success: true, dryRun: false, verifyRemote: true, recentCommitsToKeep: 2 }); @@ -818,7 +875,7 @@ describe('GitSidebar', () => { }); expect(screen.getByRole('status')).toHaveTextContent(/pushing commits to remote/i); - expect(screen.getByRole('button', { name: /pushing/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /^push$/i })).toBeDisabled(); await act(async () => { resolvePush?.({ success: true });