Merge pull request #76536 from NousResearch/bb/composer-path-copy
The branch-bar path copy confirms in place
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { atom } from 'nanostores'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { $notifications, clearNotifications } from '@/store/notifications'
|
||||
|
||||
vi.mock('@/store/coding-status', () => ({
|
||||
registerRepoStatusCwd: () => undefined,
|
||||
repoStatusForCwd: () =>
|
||||
@@ -54,4 +56,38 @@ describe('CodingStatusRow', () => {
|
||||
// The glyph button fills the row's existing 3.5 leading slot exactly.
|
||||
expect(container.querySelector('button[class~="size-3.5"]')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('parks the copy glyph against the end of the path, not the end of the row', () => {
|
||||
render(<CodingStatusRow onOpen={() => undefined} repoPath="/Users/someone/www/repo" />)
|
||||
|
||||
const path = screen.getByText('~/www/repo')
|
||||
|
||||
// The path sizes to its content and the glyph is its immediate sibling, so
|
||||
// the pair reads as one unit. `flex-1` belongs to the wrapper (which holds
|
||||
// the row's slack open) — on the label it stretched the text and pushed the
|
||||
// glyph out to the kebab.
|
||||
expect(path.classList.contains('flex-1')).toBe(false)
|
||||
expect(path.parentElement?.classList.contains('flex-1')).toBe(true)
|
||||
expect(path.nextElementSibling?.tagName).toBe('BUTTON')
|
||||
})
|
||||
|
||||
it('copies the absolute cwd inline — checkmark feedback, no toast', async () => {
|
||||
const writeText = vi.fn().mockResolvedValue(undefined)
|
||||
Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } })
|
||||
clearNotifications()
|
||||
|
||||
render(<CodingStatusRow onOpen={() => undefined} repoPath="/Users/someone/www/repo" />)
|
||||
|
||||
// Painted tildified, copied raw.
|
||||
expect(screen.getByText('~/www/repo')).toBeTruthy()
|
||||
|
||||
const copy = screen.getByRole('button', { name: 'Copy Path' })
|
||||
|
||||
fireEvent.click(copy)
|
||||
|
||||
await waitFor(() => expect(writeText).toHaveBeenCalledWith('/Users/someone/www/repo'))
|
||||
// Confirmation is the button turning into a checkmark, not a notification.
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'Copied' })).toBeTruthy())
|
||||
expect($notifications.get()).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
} from '@/components/ui/actions-menu'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Codicon } from '@/components/ui/codicon'
|
||||
import { CopyButton } from '@/components/ui/copy-button'
|
||||
import { DiffCount } from '@/components/ui/diff-count'
|
||||
import type { HermesGitBranch } from '@/global'
|
||||
import { useI18n } from '@/i18n'
|
||||
import { displayPath } from '@/lib/display-path'
|
||||
import { registerRepoStatusCwd, repoStatusForCwd, repoWorktreesForCwd } from '@/store/coding-status'
|
||||
import { copyFilePath } from '@/store/file-actions'
|
||||
import { notifyError } from '@/store/notifications'
|
||||
import { $newWorktreeRequest } from '@/store/projects'
|
||||
|
||||
@@ -242,29 +242,30 @@ export const CodingStatusRow = memo(function CodingStatusRow({
|
||||
|
||||
{/* Worktree path + copy — plain muted text, not a chip. Always in the
|
||||
flex so hover doesn't reflow the row; opacity alone reveals the
|
||||
pair. `displayPath` collapses home → ~; copy still takes the
|
||||
real absolute path. */}
|
||||
pair. The path sizes to its content (the `flex-1` lives on the
|
||||
wrapper) so the glyph sits against the end of the text instead of
|
||||
drifting to the far edge of the row. `displayPath` collapses
|
||||
home → ~; the copy still takes the real absolute path, and it's
|
||||
the shared `CopyButton` so it confirms with the same inline
|
||||
checkmark as every other copy in the app. */}
|
||||
{resolvedRepoPath && (
|
||||
<div className="flex min-w-0 flex-1 items-center gap-0.5 opacity-0 transition-opacity group-hover/status-row:opacity-100 group-focus-within/status-row:opacity-100">
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate font-mono text-[0.62rem] leading-4 text-muted-foreground/50"
|
||||
className="min-w-0 truncate font-mono text-[0.62rem] leading-4 text-muted-foreground/50"
|
||||
data-slot="coding-status-cwd"
|
||||
>
|
||||
{displayPath(resolvedRepoPath)}
|
||||
</span>
|
||||
<Button
|
||||
aria-label={fileMenu.copyPath}
|
||||
<CopyButton
|
||||
appearance="icon"
|
||||
buttonSize="icon-xs"
|
||||
className="pointer-events-none size-4 shrink-0 text-muted-foreground/50 hover:text-foreground group-hover/status-row:pointer-events-auto group-focus-within/status-row:pointer-events-auto"
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
void copyFilePath(resolvedRepoPath)
|
||||
}}
|
||||
size="icon-xs"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Codicon name="copy" size="0.7rem" />
|
||||
</Button>
|
||||
iconClassName="size-3"
|
||||
label={fileMenu.copyPath}
|
||||
side="top"
|
||||
stopPropagation
|
||||
text={resolvedRepoPath}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user