codev / desktop /src /components /shared /RepositoryLaunchControls.test.tsx
chenbhao's picture
feat: desktop
1f21206
Raw
History Blame
5.47 kB
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import type { ComponentProps } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import '@testing-library/jest-dom'
const viewportMocks = vi.hoisted(() => ({
isMobile: false,
isTauri: false,
}))
const apiMocks = vi.hoisted(() => ({
getRepositoryContext: vi.fn(),
}))
vi.mock('../../hooks/useMobileViewport', () => ({
useMobileViewport: () => viewportMocks.isMobile,
}))
vi.mock('../../lib/desktopRuntime', () => ({
isTauriRuntime: () => viewportMocks.isTauri,
}))
vi.mock('../../api/sessions', () => ({
sessionsApi: {
getRepositoryContext: apiMocks.getRepositoryContext,
},
}))
vi.mock('./DirectoryPicker', () => ({
DirectoryPicker: ({ value }: { value: string }) => (
<button type="button">Project {value}</button>
),
}))
vi.mock('../../i18n', () => ({
useTranslation: () => (key: string) => ({
'common.loading': 'Loading',
'repoLaunch.checkedOut': 'Checked out',
'repoLaunch.checkedOutWarning': 'Branch is checked out elsewhere',
'repoLaunch.currentBranch': 'Current branch',
'repoLaunch.dirtyWarning': 'Dirty worktree',
'repoLaunch.localBranch': 'Local branch',
'repoLaunch.missingWorkdir': 'Missing working directory',
'repoLaunch.noBranch': 'No branch',
'repoLaunch.noBranchMatch': 'No matching branches',
'repoLaunch.remoteBranch': 'Remote branch',
'repoLaunch.searchBranch': 'Search branches',
'repoLaunch.selectBranch': 'Select branch',
'repoLaunch.selectWorktree': 'Select worktree mode',
'repoLaunch.worktreeCurrent': 'Current worktree',
'repoLaunch.worktreeIsolated': 'Isolated worktree',
'tabs.close': 'Close',
}[key] ?? key),
}))
import { RepositoryLaunchControls } from './RepositoryLaunchControls'
const okRepositoryContext = {
state: 'ok' as const,
root: '/repo',
currentBranch: 'main',
defaultBranch: 'main',
dirty: false,
branches: [
{
name: 'main',
current: true,
local: true,
remote: false,
checkedOut: false,
remoteRef: null,
worktreePath: null,
},
{
name: 'feature/h5',
current: false,
local: true,
remote: false,
checkedOut: false,
remoteRef: null,
worktreePath: null,
},
],
}
function renderControls(props: Partial<ComponentProps<typeof RepositoryLaunchControls>> = {}) {
const defaultProps: ComponentProps<typeof RepositoryLaunchControls> = {
workDir: '/repo',
onWorkDirChange: vi.fn(),
branch: 'main',
onBranchChange: vi.fn(),
useWorktree: false,
onUseWorktreeChange: vi.fn(),
}
return render(<RepositoryLaunchControls {...defaultProps} {...props} />)
}
async function openBranchMenu() {
const trigger = await screen.findByRole('button', { name: 'Select branch: main' })
fireEvent.click(trigger)
return trigger
}
describe('RepositoryLaunchControls', () => {
beforeEach(() => {
viewportMocks.isMobile = false
viewportMocks.isTauri = false
apiMocks.getRepositoryContext.mockReset()
apiMocks.getRepositoryContext.mockResolvedValue(okRepositoryContext)
Element.prototype.scrollIntoView = vi.fn()
})
it('keeps the desktop branch dropdown when not in mobile browser mode', async () => {
renderControls()
await openBranchMenu()
const listbox = await screen.findByRole('listbox', { name: 'Select branch' })
expect(listbox).toBeInTheDocument()
expect(screen.queryByRole('dialog', { name: 'Select branch' })).not.toBeInTheDocument()
expect(listbox.parentElement?.className).toContain('w-[390px]')
})
it('uses the flatter desktop bar when embedded in a composer', async () => {
renderControls({ placement: 'composer' })
const trigger = await screen.findByRole('button', { name: 'Select branch: main' })
const bar = trigger.parentElement
expect(bar).toHaveClass('min-h-[44px]', 'bg-transparent')
expect(bar).not.toHaveClass('rounded-b-xl')
expect(bar).not.toHaveClass('bg-[var(--color-surface-container-low)]')
})
it('uses the full-width mobile bottom sheet in H5 mobile browser mode', async () => {
viewportMocks.isMobile = true
viewportMocks.isTauri = false
renderControls()
await openBranchMenu()
const dialog = await screen.findByRole('dialog', { name: 'Select branch' })
expect(dialog).toHaveClass('inset-x-0')
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument()
expect(screen.getByRole('listbox', { name: 'Select branch' })).toBeInTheDocument()
})
it('does not use the H5 mobile sheet inside Tauri even on a narrow viewport', async () => {
viewportMocks.isMobile = true
viewportMocks.isTauri = true
renderControls()
await openBranchMenu()
const listbox = await screen.findByRole('listbox', { name: 'Select branch' })
expect(listbox).toBeInTheDocument()
expect(screen.queryByRole('dialog', { name: 'Select branch' })).not.toBeInTheDocument()
})
it('keeps keyboard branch selection working from the search field', async () => {
const onBranchChange = vi.fn()
renderControls({ onBranchChange })
await openBranchMenu()
const input = await screen.findByPlaceholderText('Search branches')
fireEvent.keyDown(input, { key: 'ArrowDown' })
fireEvent.keyDown(input, { key: 'Enter' })
await waitFor(() => {
expect(onBranchChange).toHaveBeenCalledWith('feature/h5')
})
})
})