import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import App from '../../src/app/App' import QuickLauncher from '../../src/components/sessions/QuickLauncher' import SerialConnectDialog from '../../src/components/serial/SerialConnectDialog' import { useSessionStore } from '../../src/stores/sessionStore' import { useTabStore } from '../../src/stores/tabStore' import { mockDesktopApi } from '../setup' const serialMocks = vi.hoisted(() => ({ listSerialPorts: vi.fn(), })) vi.mock('../../src/platform/serial', () => ({ listSerialPorts: serialMocks.listSerialPorts, })) describe('Quick launcher and serial acceptance', () => { beforeEach(() => { vi.clearAllMocks() window.opsxApi = mockDesktopApi serialMocks.listSerialPorts.mockResolvedValue([ { path: '/dev/tty.debug-console', portType: 'Serial' }, { path: '/dev/tty.Bluetooth-Incoming-Port', portType: 'Bluetooth' }, ]) useSessionStore.setState({ sessions: [] }) useTabStore.setState({ tabs: [{ id: 'welcome', title: '欢迎', type: 'welcome', isActive: true }], activeTabId: 'welcome', }) }) it('opens the unified launcher from the tab-bar plus button', async () => { const user = userEvent.setup() render() await user.click(screen.getByRole('button', { name: '快速启动' })) expect(await screen.findByRole('heading', { name: '快速启动' })).toBeVisible() const launcher = within(screen.getByRole('dialog')) expect(launcher.getByRole('button', { name: /串口终端/ })).toBeVisible() expect(launcher.getByRole('button', { name: /快速连接/ })).toBeVisible() }) it('starts the system-default zsh directly from the launcher', async () => { const user = userEvent.setup() render( ) const zshButton = await screen.findByRole('button', { name: /Zsh.*\/bin\/zsh.*系统默认/, }) await user.click(zshButton) expect(useTabStore.getState().tabs).toContainEqual( expect.objectContaining({ title: 'Zsh', type: 'terminal', isLocal: true, localShellPath: '/bin/zsh', localShellKind: 'zsh', }) ) }) it('maps the selected serial device and line settings into a serial config', async () => { const user = userEvent.setup() const onConnect = vi.fn() render() await user.click( await screen.findByRole('button', { name: /\/dev\/tty\.Bluetooth-Incoming-Port/ }) ) await user.selectOptions(screen.getByLabelText('波特率'), '921600') await user.selectOptions(screen.getByLabelText('数据位'), '7') await user.selectOptions(screen.getByLabelText('停止位'), '2') await user.selectOptions(screen.getByLabelText('校验'), 'even') await user.selectOptions(screen.getByLabelText('流控'), 'hardware') await user.click(screen.getByRole('button', { name: '打开串口' })) expect(onConnect).toHaveBeenCalledWith({ path: '/dev/tty.Bluetooth-Incoming-Port', baudRate: 921600, dataBits: 7, stopBits: 2, parity: 'even', flowControl: 'hardware', }) }) it('keeps the documented manual serial path when enumeration returns no devices', async () => { serialMocks.listSerialPorts.mockResolvedValue([]) render() await waitFor(() => expect(serialMocks.listSerialPorts).toHaveBeenCalled()) expect(screen.getByLabelText('设备')).toHaveValue('/dev/tty.debug-console') expect(screen.getByText(/\/dev\/tty\.Bluetooth-Incoming-Port/)).toBeVisible() }) })