portainer/app/react/kubernetes/cluster/KubectlShell/KubectlShellView.test.tsx

146 lines
4.2 KiB
TypeScript

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(<KubectlShellView />);
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(<KubectlShellView />);
expect(getTerminalProps().url).toBe(
'ws://localhost:3000/portainer/api/websocket/kubernetes-shell?endpointId=1'
);
});
it('passes connect=true to Terminal', () => {
render(<KubectlShellView />);
expect(getTerminalProps().connect).toBe(true);
});
});
describe('shell state', () => {
it('shows loading indicator when connecting', () => {
render(<KubectlShellView />);
triggerStateChange('connecting');
expect(screen.getByText('Loading Terminal...')).toBeInTheDocument();
});
it('shows disconnected panel when disconnected', () => {
render(<KubectlShellView />);
triggerStateChange('disconnected');
expect(screen.getByText('Console disconnected')).toBeInTheDocument();
});
it('calls terminalClose when state becomes disconnected', () => {
render(<KubectlShellView />);
triggerStateChange('disconnected');
expect(vi.mocked(terminalClose)).toHaveBeenCalled();
});
it('shows nothing initially (idle state)', () => {
render(<KubectlShellView />);
expect(screen.queryByText('Loading Terminal...')).not.toBeInTheDocument();
expect(
screen.queryByText('Console disconnected')
).not.toBeInTheDocument();
});
});
describe('disconnected buttons', () => {
beforeEach(() => {
render(<KubectlShellView />);
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();
});
});
});