Complete multi-pane broadcast, standard terminal right-click actions, batch host selection with progress, and native ssh_close_shell so closing a pane tears down the remote shell. Co-authored-by: Cursor <cursoragent@cursor.com>
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import { render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import BatchCommandDialog from '../../src/components/sessions/BatchCommandDialog'
|
|
import type { Session } from '../../src/stores/sessionStore'
|
|
import { mockDesktopApi } from '../setup'
|
|
|
|
const sessions: Session[] = [
|
|
{ id: 'host-a', name: 'Host A', host: '10.0.0.1', port: 22, username: 'root', authType: 'key' },
|
|
{ id: 'host-b', name: 'Host B', host: '10.0.0.2', port: 2222, username: 'ops', authType: 'password' },
|
|
]
|
|
|
|
describe('BatchCommandDialog', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
window.opsxApi = mockDesktopApi
|
|
vi.mocked(mockDesktopApi.sessionGetById).mockImplementation(async (id) => ({
|
|
success: true,
|
|
session: sessions.find((session) => session.id === id),
|
|
}))
|
|
vi.mocked(mockDesktopApi.sshConnect).mockImplementation(async (config) => ({
|
|
success: true,
|
|
connectionId: `connection-${config.host}`,
|
|
}))
|
|
vi.mocked(mockDesktopApi.sshExec).mockImplementation(async (connectionId, command) => ({
|
|
success: true,
|
|
result: `${connectionId}: ${command}`,
|
|
}))
|
|
})
|
|
|
|
it('executes the same command on every selected host and disconnects transient sessions', async () => {
|
|
const user = userEvent.setup()
|
|
render(<BatchCommandDialog isOpen sessions={sessions} onClose={vi.fn()} />)
|
|
|
|
await user.type(screen.getByLabelText('远程命令'), 'date')
|
|
await user.click(screen.getByRole('button', { name: '执行到 2 台' }))
|
|
|
|
await waitFor(() => expect(mockDesktopApi.sshExec).toHaveBeenCalledTimes(2))
|
|
expect(mockDesktopApi.sshExec).toHaveBeenCalledWith('connection-10.0.0.1', 'date')
|
|
expect(mockDesktopApi.sshExec).toHaveBeenCalledWith('connection-10.0.0.2', 'date')
|
|
await waitFor(() => expect(mockDesktopApi.sshDisconnect).toHaveBeenCalledTimes(2))
|
|
expect(await screen.findByText('成功 2')).toBeVisible()
|
|
expect(screen.getByText('connection-10.0.0.1: date')).toBeVisible()
|
|
expect(screen.getByText('connection-10.0.0.2: date')).toBeVisible()
|
|
})
|
|
|
|
it('starts with the sidebar selection and lets the user adjust targets in the dialog', async () => {
|
|
const user = userEvent.setup()
|
|
render(
|
|
<BatchCommandDialog
|
|
isOpen
|
|
sessions={sessions}
|
|
initialSelectedIds={['host-a']}
|
|
onClose={vi.fn()}
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByRole('checkbox', { name: /Host A/ })).toHaveAttribute('aria-checked', 'true')
|
|
expect(screen.getByRole('checkbox', { name: /Host B/ })).toHaveAttribute('aria-checked', 'false')
|
|
await user.click(screen.getByRole('checkbox', { name: /Host B/ }))
|
|
expect(screen.getByRole('button', { name: '执行到 2 台' })).toBeVisible()
|
|
|
|
await user.type(screen.getByLabelText('筛选主机'), '10.0.0.1')
|
|
expect(screen.getByRole('checkbox', { name: /Host A/ })).toBeVisible()
|
|
expect(screen.queryByRole('checkbox', { name: /Host B/ })).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('clears the command when reopened so sensitive input is not retained', async () => {
|
|
const user = userEvent.setup()
|
|
const { rerender } = render(
|
|
<BatchCommandDialog isOpen sessions={sessions} onClose={vi.fn()} />
|
|
)
|
|
|
|
const command = screen.getByLabelText('远程命令')
|
|
await user.type(command, 'cat /etc/shadow')
|
|
expect(command).toHaveValue('cat /etc/shadow')
|
|
|
|
rerender(<BatchCommandDialog isOpen={false} sessions={sessions} onClose={vi.fn()} />)
|
|
rerender(<BatchCommandDialog isOpen sessions={sessions} onClose={vi.fn()} />)
|
|
|
|
expect(screen.getByLabelText('远程命令')).toHaveValue('')
|
|
})
|
|
})
|