feat: implement Cmd+Enter as commit shortcut in Source Control (#9773)

* feat: implement Cmd+Enter as commit shortcut in Source Control

* test: add unit tests for commit shortcut and tooltip formatting

* fix: address review feedback on modifier keys and test coverage

* test: split mac and windows/linux shortcut and keydown tests

---------

Co-authored-by: Andres Van Reepingen <andres.vanreepingen@datacamp.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
This commit is contained in:
Andres Van Reepingen 2026-07-26 11:39:30 +02:00 committed by GitHub
parent 6577b79e2e
commit 18295c27e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 156 additions and 5 deletions

View File

@ -1,6 +1,14 @@
import { describe, expect, it, vi } from 'vitest'
// @vitest-environment happy-dom
import React from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { renderToStaticMarkup } from 'react-dom/server'
import { CommitArea, ConflictSummaryCard, OperationBanner } from './SourceControl'
import { fireEvent, render } from '@testing-library/react'
import {
CommitArea,
ConflictSummaryCard,
handleSourceControlCommitShortcut,
OperationBanner
} from './SourceControl'
import {
resolveCommitAreaPrimaryAction,
type PrimaryActionInputs
@ -9,6 +17,23 @@ import { resolveDropdownItems, type DropdownActionKind } from './source-control-
import { TooltipProvider } from '@/components/ui/tooltip'
import { deriveSourceControlPushRecovery } from './source-control-push-recovery'
vi.mock('@/components/ui/tooltip', () => ({
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
TooltipTrigger: ({ children }: { children: React.ReactNode }) => <>{children}</>,
TooltipContent: ({ children, className }: { children: React.ReactNode; className?: string }) => (
<div className={className}>{children}</div>
),
TooltipProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>
}))
afterEach(() => {
vi.unstubAllGlobals()
})
function setUserAgent(userAgent: string): void {
vi.stubGlobal('navigator', { userAgent })
}
function buildInputs(overrides: Partial<PrimaryActionInputs> = {}): PrimaryActionInputs {
return {
stagedCount: 1,
@ -146,6 +171,102 @@ describe('CommitArea', () => {
expect(hasDisabledAttribute(firstButton(renderCommitArea(baseProps())))).toBe(false)
})
it('renders the Commit shortcut key indicator (⌘Enter) in primary button tooltip on macOS', () => {
const props = baseProps()
setUserAgent('Macintosh')
const markupMac = renderCommitArea({
...props,
primaryAction: { kind: 'commit', disabled: false, label: 'Commit', title: 'Commit changes' }
})
expect(markupMac).toContain('Commit changes')
expect(markupMac).toContain('⌘')
expect(markupMac).toContain('Enter')
})
it('renders the Commit shortcut key indicator (Ctrl+Enter) in primary button tooltip on Windows/Linux', () => {
const props = baseProps()
setUserAgent('Windows NT')
const markupWin = renderCommitArea({
...props,
primaryAction: { kind: 'commit', disabled: false, label: 'Commit', title: 'Commit changes' }
})
expect(markupWin).toContain('Commit changes')
expect(markupWin).toContain('Ctrl')
expect(markupWin).toContain('+')
expect(markupWin).toContain('Enter')
})
it('only handles Cmd+Enter when focus is within the Source Control sidebar', () => {
setUserAgent('Macintosh')
const onPrimaryAction = vi.fn()
const primaryAction = {
kind: 'commit' as const,
disabled: false
}
const { getByTestId } = render(
<>
<div
data-testid="source-control-sidebar"
onKeyDown={(event) =>
handleSourceControlCommitShortcut(event, primaryAction, onPrimaryAction)
}
>
<button type="button" data-testid="inside-sidebar">
Inside
</button>
</div>
<button type="button" data-testid="outside-sidebar">
Outside
</button>
</>
)
const outside = getByTestId('outside-sidebar')
outside.focus()
fireEvent.keyDown(outside, { key: 'Enter', metaKey: true })
expect(onPrimaryAction).not.toHaveBeenCalled()
const inside = getByTestId('inside-sidebar')
inside.focus()
fireEvent.keyDown(inside, { key: 'Enter', metaKey: true })
expect(onPrimaryAction).toHaveBeenCalledTimes(1)
})
it('handles Ctrl+Enter, but not Cmd+Enter, inside the sidebar on Windows/Linux', () => {
setUserAgent('Linux')
const onPrimaryAction = vi.fn()
const primaryAction = {
kind: 'commit' as const,
disabled: false
}
const { getByRole } = render(
<button
type="button"
onKeyDown={(event) =>
handleSourceControlCommitShortcut(event, primaryAction, onPrimaryAction)
}
>
Commit scope
</button>
)
const target = getByRole('button', { name: 'Commit scope' })
fireEvent.keyDown(target, { key: 'Enter', metaKey: true })
expect(onPrimaryAction).not.toHaveBeenCalled()
fireEvent.keyDown(target, { key: 'Enter', ctrlKey: true })
expect(onPrimaryAction).toHaveBeenCalledTimes(1)
})
it('does not render the Commit shortcut keys inside the primary button tooltip when the action is not commit', () => {
const props = baseProps()
const markup = renderCommitArea({
...props,
primaryAction: { kind: 'push', disabled: false, label: 'Push', title: 'Push changes' }
})
expect(markup).not.toContain('Enter')
})
it('disables the textarea while the commit is in flight', () => {
const markup = renderCommitArea({
...baseProps({ isCommitting: true }),

View File

@ -45,6 +45,8 @@ import { WORKSPACE_FILE_PATH_MIME } from '@/lib/workspace-file-drag'
import { isFolderRepo } from '../../../../shared/repo-kind'
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'
import { Button } from '@/components/ui/button'
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
import { getScreenSubmitModifierLabel, isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut'
import {
DropdownMenu,
DropdownMenuContent,
@ -4708,6 +4710,13 @@ function SourceControlInner(): React.JSX.Element {
runCreatePrIntent
])
const handleSourceControlKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>): void => {
handleSourceControlCommitShortcut(event, primaryAction, handlePrimaryClick)
},
[handlePrimaryClick, primaryAction]
)
const handleCreatePrHeaderClick = useCallback((): void => {
if (!createPrHeaderAction || createPrHeaderAction.disabled) {
return
@ -5448,7 +5457,11 @@ function SourceControlInner(): React.JSX.Element {
return (
<>
<div ref={setSourceControlRoot} className="relative flex h-full flex-col overflow-hidden">
<div
ref={setSourceControlRoot}
className="relative flex h-full flex-col overflow-hidden"
onKeyDown={handleSourceControlKeyDown}
>
<SourceControlHeaderToolbar
filterQuery={filterQuery}
filterExpanded={filterExpanded}
@ -6358,6 +6371,20 @@ function SourceControlInner(): React.JSX.Element {
const SourceControl = React.memo(SourceControlInner)
export default SourceControl
export function handleSourceControlCommitShortcut(
event: React.KeyboardEvent<HTMLElement>,
primaryAction: Pick<PrimaryAction, 'disabled' | 'kind'>,
onCommit: () => void
): void {
if (primaryAction.disabled || primaryAction.kind !== 'commit' || !isScreenSubmitShortcut(event)) {
return
}
// Why: the handler lives on the Source Control root, so the shortcut cannot fire from the editor, terminal, or another sidebar tab.
event.preventDefault()
event.stopPropagation()
onCommit()
}
type CommitAreaProps = {
worktreeId: string | null
groupId: string | null
@ -6690,8 +6717,11 @@ export function CommitArea({
</Button>
</span>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={6} className="max-w-72">
{primaryAction.title}
<TooltipContent side="top" sideOffset={6} className="flex max-w-72 items-center gap-2">
<span>{primaryAction.title}</span>
{primaryAction.kind === 'commit' ? (
<ShortcutKeyCombo keys={[getScreenSubmitModifierLabel(), 'Enter']} />
) : null}
</TooltipContent>
</Tooltip>
<DropdownMenu>