import { render, screen, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { vi } from 'vitest'; import { terminalClose } from '@/portainer/services/terminal-window'; import { Terminal } from '@@/Terminal/Terminal'; import type { ShellState } from '@@/Terminal/Terminal'; import { KubectlShellView } from './KubectlShellView'; vi.mock('@@/Terminal/Terminal', () => ({ Terminal: vi.fn(() => null), LINUX_SHELL_INIT_COMMANDS: [], })); vi.mock('@/react/hooks/useEnvironmentId', () => ({ useEnvironmentId: () => 1, })); vi.mock('@/portainer/helpers/pathHelper', () => ({ baseHref: vi.fn().mockReturnValue('/portainer/'), })); vi.mock('@/portainer/services/terminal-window', () => ({ terminalClose: vi.fn(), })); function getTerminalProps() { return vi.mocked(Terminal).mock.calls[0][0] as { url: string; connect: boolean; onStateChange?: (state: ShellState) => void; }; } function triggerStateChange(state: ShellState) { act(() => { getTerminalProps().onStateChange?.(state); }); } beforeEach(() => { vi.clearAllMocks(); Object.defineProperty(window, 'location', { value: { protocol: 'https:', host: 'localhost:3000' }, writable: true, }); }); describe('KubectlShellView', () => { describe('URL construction', () => { it('builds wss:// URL when location is https', () => { render(); expect(getTerminalProps().url).toBe( 'wss://localhost:3000/portainer/api/websocket/kubernetes-shell?endpointId=1' ); }); it('builds ws:// URL when location is http', () => { Object.defineProperty(window, 'location', { value: { protocol: 'http:', host: 'localhost:3000' }, writable: true, }); render(); expect(getTerminalProps().url).toBe( 'ws://localhost:3000/portainer/api/websocket/kubernetes-shell?endpointId=1' ); }); it('passes connect=true to Terminal', () => { render(); expect(getTerminalProps().connect).toBe(true); }); }); describe('shell state', () => { it('shows loading indicator when connecting', () => { render(); triggerStateChange('connecting'); expect(screen.getByText('Loading Terminal...')).toBeInTheDocument(); }); it('shows disconnected panel when disconnected', () => { render(); triggerStateChange('disconnected'); expect(screen.getByText('Console disconnected')).toBeInTheDocument(); }); it('calls terminalClose when state becomes disconnected', () => { render(); triggerStateChange('disconnected'); expect(vi.mocked(terminalClose)).toHaveBeenCalled(); }); it('shows nothing initially (idle state)', () => { render(); expect(screen.queryByText('Loading Terminal...')).not.toBeInTheDocument(); expect( screen.queryByText('Console disconnected') ).not.toBeInTheDocument(); }); }); describe('disconnected buttons', () => { beforeEach(() => { render(); triggerStateChange('disconnected'); }); it('renders Reload button', () => { expect(screen.getByTestId('k8sShell-reloadButton')).toHaveTextContent( 'Reload' ); }); it('renders Close button', () => { expect(screen.getByTestId('k8sShell-closeButton')).toHaveTextContent( 'Close' ); }); it('reloads page when Reload is clicked', async () => { const user = userEvent.setup(); const mockReload = vi.fn(); Object.defineProperty(window, 'location', { value: { ...window.location, reload: mockReload }, writable: true, }); await user.click(screen.getByTestId('k8sShell-reloadButton')); expect(mockReload).toHaveBeenCalled(); }); it('closes window when Close is clicked', async () => { const user = userEvent.setup(); const mockClose = vi.fn(); Object.defineProperty(window, 'close', { value: mockClose, writable: true, }); await user.click(screen.getByTestId('k8sShell-closeButton')); expect(mockClose).toHaveBeenCalled(); }); }); });