test(desktop): cover short actions labels and tip-less close buttons

This commit is contained in:
Brooklyn Nicholson 2026-07-27 22:03:37 -05:00
parent 970c4b5336
commit 015353d970
6 changed files with 19 additions and 53 deletions

View file

@ -29,7 +29,7 @@ vi.mock('@/i18n', () => ({
projects: {
copyPath: 'Copy path',
deleteConfirm: 'This cannot be undone.',
menu: 'Project actions',
menu: 'Actions',
menuAddFolder: 'Add folder',
menuAppearance: 'Appearance',
menuDelete: 'Delete',
@ -91,7 +91,7 @@ describe('ProjectMenu', () => {
it('does not wrap the kebab trigger in a Tip', () => {
render(<ProjectMenu isActive={false} project={project} />)
const button = screen.getByRole('button', { name: 'Project actions' })
const button = screen.getByRole('button', { name: 'Actions' })
expect(tipTrigger(button)).toBeNull()
})
@ -101,7 +101,7 @@ describe('ProjectMenu', () => {
it('opens the appearance popover through the kebab trigger when anchorRef is absent', async () => {
render(<ProjectMenu isActive={false} project={project} />)
const trigger = screen.getByRole('button', { name: 'Project actions' })
const trigger = screen.getByRole('button', { name: 'Actions' })
openTriggerMenu(trigger)

View file

@ -11,7 +11,7 @@ vi.mock('@/i18n', () => ({
sidebar: {
projects: {
copyPath: 'Copy path',
menu: 'Project actions',
menu: 'Actions',
removeWorktree: 'Remove worktree',
reveal: 'Reveal in file manager',
startWork: 'New worktree'
@ -66,7 +66,7 @@ describe('WorkspaceMenu', () => {
it('does not wrap the kebab trigger in a Tip', () => {
render(<WorkspaceMenu onRemove={vi.fn()} path="/repo/lane" />)
const button = screen.getByRole('button', { name: 'Project actions' })
const button = screen.getByRole('button', { name: 'Actions' })
expect(tipTrigger(button)).toBeNull()
})
})

View file

@ -24,7 +24,6 @@ vi.mock('@/i18n', () => ({
sidebar: {
projects: { menuAppearance: 'Appearance', noColor: 'No color' },
row: {
actionsFor: (title: string) => `Actions for ${title}`,
archive: 'Archive',
branchFrom: 'Branch from here',
copyId: 'Copy ID',
@ -33,10 +32,11 @@ vi.mock('@/i18n', () => ({
hideTabBar: 'Hide tab bar',
pin: 'Pin',
rename: 'Rename',
renameDesc: 'Rename this session',
renameDesc: 'Leave empty to clear.',
renameFailed: 'Rename failed',
renameTitle: 'Rename session',
renamed: 'Renamed',
sessionActions: 'Session actions',
unpin: 'Unpin',
untitledPlaceholder: 'Untitled'
}
@ -74,7 +74,7 @@ vi.mock('@/store/windows', () => ({
function renderMenu() {
return render(
<SessionActionsMenu sessionId="s1" title="My session">
<button aria-label="Actions for My session" type="button">
<button aria-label="Session actions" type="button">
</button>
</SessionActionsMenu>
@ -85,7 +85,7 @@ describe('SessionActionsMenu', () => {
it('opens the dropdown on click without a tooltip on the kebab', async () => {
renderMenu()
const trigger = screen.getByRole('button', { name: 'Actions for My session' })
const trigger = screen.getByRole('button', { name: 'Session actions' })
expect(trigger.closest('[data-slot="tooltip-trigger"]')).toBeNull()

View file

@ -18,13 +18,13 @@ vi.mock('@/i18n', () => ({
t: {
sidebar: {
row: {
actionsFor: (title: string) => `Actions for ${title}`,
ageMin: 'm',
ageNow: 'now',
backgroundRunning: 'Running in background',
finishedUnread: 'Finished',
handoffOrigin: (platform: string) => `Started on ${platform}`,
needsInput: 'Needs input',
sessionActions: 'Session actions',
sessionRunning: 'Running',
waitingForAnswer: 'Waiting for answer'
}
@ -131,7 +131,7 @@ describe('SidebarSessionRow', () => {
/>
)
const kebab = screen.getByRole('button', { name: 'Actions for Hermes doctor health check results' })
const kebab = screen.getByRole('button', { name: 'Session actions' })
expect(tipTrigger(kebab)).toBeNull()
})

View file

@ -297,7 +297,7 @@ describe('ToolsetConfigPanel', () => {
fireEvent.click(elevenlabs)
// Open the credential actions menu (Radix opens on pointerdown), then "Set".
const trigger = await screen.findByRole('button', { name: /Actions for ELEVENLABS_API_KEY/ })
const trigger = await screen.findByRole('button', { name: /^Actions$/ })
fireEvent.pointerDown(trigger, { button: 0, ctrlKey: false, pointerType: 'mouse' })
fireEvent.click(await screen.findByRole('menuitem', { name: 'Set' }))
@ -655,7 +655,7 @@ describe('ToolsetConfigPanel', () => {
// Save a key: the pill must go Ready from the local envState patch even
// though the (now stale) server status still says needs_keys.
const trigger = await screen.findByRole('button', { name: /Actions for ELEVENLABS_API_KEY/ })
const trigger = await screen.findByRole('button', { name: /^Actions$/ })
fireEvent.pointerDown(trigger, { button: 0, ctrlKey: false, pointerType: 'mouse' })
fireEvent.click(await screen.findByRole('menuitem', { name: 'Set' }))
fireEvent.change(await screen.findByPlaceholderText('ElevenLabs API key'), { target: { value: 'sk-live' } })
@ -929,7 +929,7 @@ describe('ToolsetConfigPanel', () => {
const { ToolsetConfigPanel } = await import('./toolset-config-panel')
render(<ToolsetConfigPanel onConfiguredChange={vi.fn()} toolset="tts" />)
const trigger = await screen.findByRole('button', { name: /Actions for ELEVENLABS_API_KEY/ })
const trigger = await screen.findByRole('button', { name: /^Actions$/ })
fireEvent.pointerDown(trigger, { button: 0, ctrlKey: false, pointerType: 'mouse' })
fireEvent.click(await screen.findByRole('menuitem', { name: 'Manage in API Keys' }))
@ -949,7 +949,7 @@ describe('ToolsetConfigPanel', () => {
fireEvent.click(elevenLabs)
await waitFor(() => expect(elevenLabs.getAttribute('aria-pressed')).toBe('true'))
const trigger = await screen.findByRole('button', { name: /Actions for ELEVENLABS_API_KEY/ })
const trigger = await screen.findByRole('button', { name: /^Actions$/ })
fireEvent.pointerDown(trigger, { button: 0, ctrlKey: false, pointerType: 'mouse' })
await screen.findByRole('menuitem', { name: 'Set' })

View file

@ -1,4 +1,4 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Dialog, DialogContent, DialogTitle, preventCloseButtonAutoFocus } from './dialog'
@ -51,7 +51,7 @@ describe('DialogContent close button', () => {
expect(onOpenChange).toHaveBeenCalledWith(false)
})
it('does not show the tooltip immediately on open when the dialog opts out of autofocus (no hover/focus yet)', async () => {
it('close button has no tip (X is the label)', () => {
render(
<Dialog open>
<DialogContent onOpenAutoFocus={preventCloseButtonAutoFocus}>
@ -60,11 +60,8 @@ describe('DialogContent close button', () => {
</Dialog>
)
// Radix would otherwise autofocus the close button on open (this dialog has
// no input), which also triggers the tooltip via focus. Dialogs with no
// input (e.g. the updates overlay) opt into `preventCloseButtonAutoFocus`
// explicitly — this is no longer dialog.tsx's default for every dialog.
expect(screen.getByRole('button')).toBeTruthy()
const close = screen.getByRole('button', { name: /close/i })
expect(close.closest('[data-slot="tooltip-trigger"]')).toBeNull()
expect(screen.queryByRole('tooltip')).toBeNull()
})
@ -105,35 +102,4 @@ describe('DialogContent close button', () => {
expect(event.defaultPrevented).toBe(true)
})
// Skipped: pre-existing test, unrelated to the onOpenAutoFocus scoping this
// file is actually about (that's fully covered by the three tests above).
// The tooltip's open transition is driven by a real, un-act()-wrapped timer
// inside Radix/Tip, and on the Linux CI runner it consistently never fires
// within any timeout tried (1000ms/3000ms), while passing reliably in a full
// local run on Windows — an environment-specific flake, not a regression
// from this change. Needs its own investigation (e.g. Radix/jsdom version
// pinning, timer/act handling) rather than a timeout bump.
it.skip('shows the tooltip on focus (Radix opens on focus as well as hover; jsdom cannot reliably simulate real pointer hover)', async () => {
render(
<Dialog open>
{/* No input here, so without this opt-out Radix's real autofocus would
land on the close button on mount and race with the manual
fireEvent.focus below (same reason updates-overlay.tsx opts out). */}
<DialogContent onOpenAutoFocus={preventCloseButtonAutoFocus}>
<DialogTitle>Test dialog</DialogTitle>
</DialogContent>
</Dialog>
)
const closeButton = screen.getByRole('button', { name: /close/i })
closeButton.focus()
await waitFor(
() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip.textContent).toMatch(/close/i)
},
{ timeout: 3000 }
)
})
})