import { act, render, screen, waitFor } from '@testing-library/react' import '@testing-library/jest-dom' import { beforeEach, describe, expect, it, vi } from 'vitest' const terminalMocks = vi.hoisted(() => { const terminalInstance = { cols: 80, rows: 24, loadAddon: vi.fn(), open: vi.fn(), dispose: vi.fn(), onData: vi.fn(), write: vi.fn(), writeln: vi.fn(), clear: vi.fn(), } const fitInstance = { fit: vi.fn(), } return { available: false, terminalInstance, fitInstance, spawn: vi.fn(), write: vi.fn(), resize: vi.fn(), kill: vi.fn(), onOutput: vi.fn(), onExit: vi.fn(), } }) vi.mock('@xterm/xterm', () => ({ Terminal: vi.fn(() => terminalMocks.terminalInstance), })) vi.mock('@xterm/addon-fit', () => ({ FitAddon: vi.fn(() => terminalMocks.fitInstance), })) vi.mock('../api/terminal', () => ({ terminalApi: { isAvailable: () => terminalMocks.available, spawn: terminalMocks.spawn, write: terminalMocks.write, resize: terminalMocks.resize, kill: terminalMocks.kill, onOutput: terminalMocks.onOutput, onExit: terminalMocks.onExit, }, })) import { TerminalSettings } from './TerminalSettings' describe('TerminalSettings', () => { beforeEach(() => { terminalMocks.available = false terminalMocks.spawn.mockReset() terminalMocks.write.mockReset() terminalMocks.resize.mockReset() terminalMocks.kill.mockReset() terminalMocks.onOutput.mockReset() terminalMocks.onExit.mockReset() terminalMocks.terminalInstance.loadAddon.mockClear() terminalMocks.terminalInstance.open.mockClear() terminalMocks.terminalInstance.dispose.mockClear() terminalMocks.terminalInstance.onData.mockClear() terminalMocks.terminalInstance.write.mockClear() terminalMocks.terminalInstance.writeln.mockClear() terminalMocks.terminalInstance.clear.mockClear() terminalMocks.fitInstance.fit.mockClear() terminalMocks.onOutput.mockResolvedValue(vi.fn()) terminalMocks.onExit.mockResolvedValue(vi.fn()) terminalMocks.write.mockResolvedValue(undefined) terminalMocks.resize.mockResolvedValue(undefined) terminalMocks.kill.mockResolvedValue(undefined) terminalMocks.spawn.mockResolvedValue({ session_id: 7, shell: '/bin/zsh', cwd: '/Users/test', }) vi.stubGlobal('ResizeObserver', class { observe = vi.fn() disconnect = vi.fn() }) }) it('shows a desktop-runtime empty state outside Tauri', () => { render() expect(screen.getByText('Desktop runtime required')).toBeInTheDocument() expect(terminalMocks.spawn).not.toHaveBeenCalled() }) it('starts a host terminal session when Tauri is available', async () => { terminalMocks.available = true render() await waitFor(() => { expect(terminalMocks.spawn).toHaveBeenCalledWith({ cols: 80, rows: 24 }) }) expect(screen.getByText('/bin/zsh')).toBeInTheDocument() expect(screen.getByText('/Users/test')).toBeInTheDocument() expect(terminalMocks.terminalInstance.open).toHaveBeenCalled() expect(terminalMocks.fitInstance.fit).toHaveBeenCalled() }) it('writes matching terminal output events into xterm', async () => { terminalMocks.available = true let outputHandler: ((payload: { session_id: number; data: string }) => void) | undefined terminalMocks.onOutput.mockImplementation(async (handler) => { outputHandler = handler return vi.fn() }) render() await waitFor(() => expect(terminalMocks.spawn).toHaveBeenCalled()) act(() => { outputHandler?.({ session_id: 7, data: 'hello\r\n' }) outputHandler?.({ session_id: 8, data: 'ignored\r\n' }) }) expect(terminalMocks.terminalInstance.write).toHaveBeenCalledWith('hello\r\n') expect(terminalMocks.terminalInstance.write).not.toHaveBeenCalledWith('ignored\r\n') }) })