Skip to main content
Glama
terminal-typing-feedback-fix.test.ts7.92 kB
/** * CRITICAL FIX TESTS: Terminal Typing Feedback * * These tests verify that users can see their typing immediately * and that the terminal displays an initial prompt on connection. * * CURRENT STATE: FAILING - Users cannot see what they type * TARGET STATE: PASSING - Immediate visual feedback for all keystrokes */ import { WebSocket } from 'ws'; // Mock xterm.js for testing class MockTerminal { private writtenContent = ''; private dataCallback: ((data: string) => void) | null = null; write(data: string): void { this.writtenContent += data; } onData(callback: (data: string) => void): void { this.dataCallback = callback; } simulateUserInput(input: string): void { if (this.dataCallback) { this.dataCallback(input); } } getWrittenContent(): string { return this.writtenContent; } clear(): void { this.writtenContent = ''; } } // Mock WebSocket for testing class MockWebSocket { readyState = WebSocket.OPEN; private sentMessages: string[] = []; send(message: string): void { this.sentMessages.push(message); } getSentMessages(): string[] { return [...this.sentMessages]; } clear(): void { this.sentMessages.length = 0; } } // Import the terminal handler (will be loaded dynamically for testing) let TerminalInputHandler: any; beforeAll(async () => { // Load the terminal input handler in a way that simulates browser environment const fs = await import('fs/promises'); const handlerCode = await fs.readFile('/home/jsbattig/Dev/ls-ssh-mcp/static/terminal-input-handler.js', 'utf8'); // Create a function that executes the handler code and returns the class const createHandler = new Function('WebSocket', handlerCode + '; return TerminalInputHandler;'); TerminalInputHandler = createHandler(MockWebSocket); }); describe('Terminal Typing Feedback - Critical UX Fix', () => { let mockTerminal: MockTerminal; let mockWebSocket: MockWebSocket; let handler: any; beforeEach(() => { mockTerminal = new MockTerminal(); mockWebSocket = new MockWebSocket(); handler = new TerminalInputHandler(mockTerminal, mockWebSocket, 'test-session'); }); describe('CRITICAL ISSUE: Users Must See Their Typing', () => { test('FAILING - Single character typing should show immediate feedback', () => { mockTerminal.clear(); // Simulate user typing 'p' mockTerminal.simulateUserInput('p'); // ASSERTION: User should see the character immediately const writtenContent = mockTerminal.getWrittenContent(); expect(writtenContent).toContain('p'); // Also verify internal state is correct expect(handler.getCurrentLine()).toBe('p'); expect(handler.getCursorPosition()).toBe(1); }); test('FAILING - Multiple character typing should show progressive feedback', () => { mockTerminal.clear(); // Simulate user typing 'pwd' mockTerminal.simulateUserInput('p'); mockTerminal.simulateUserInput('w'); mockTerminal.simulateUserInput('d'); // ASSERTION: User should see each character as it's typed const writtenContent = mockTerminal.getWrittenContent(); expect(writtenContent).toContain('p'); expect(writtenContent).toContain('w'); expect(writtenContent).toContain('d'); // Verify progressive state updates expect(handler.getCurrentLine()).toBe('pwd'); expect(handler.getCursorPosition()).toBe(3); }); test('FAILING - Backspace should show visual feedback', () => { mockTerminal.clear(); // Type 'pwd' then backspace mockTerminal.simulateUserInput('p'); mockTerminal.simulateUserInput('w'); mockTerminal.simulateUserInput('d'); mockTerminal.simulateUserInput('\x08'); // Backspace // ASSERTION: User should see backspace effect (character removal) const writtenContent = mockTerminal.getWrittenContent(); expect(writtenContent).toContain('\x08 \x08'); // Backspace sequence // Verify internal state expect(handler.getCurrentLine()).toBe('pw'); expect(handler.getCursorPosition()).toBe(2); }); test('FAILING - Arrow key navigation should show cursor movement', () => { mockTerminal.clear(); // Type 'pwd' then move cursor left mockTerminal.simulateUserInput('p'); mockTerminal.simulateUserInput('w'); mockTerminal.simulateUserInput('d'); mockTerminal.simulateUserInput('\x1b[D'); // Left arrow // ASSERTION: User should see cursor move left const writtenContent = mockTerminal.getWrittenContent(); expect(writtenContent).toContain('\x1b[D'); // Left arrow escape sequence // Verify internal state expect(handler.getCursorPosition()).toBe(2); }); }); describe('Command Submission Flow', () => { test('Enter key should submit command and clear line', () => { mockTerminal.clear(); mockWebSocket.clear(); // Type command and press Enter mockTerminal.simulateUserInput('p'); mockTerminal.simulateUserInput('w'); mockTerminal.simulateUserInput('d'); mockTerminal.simulateUserInput('\r'); // Enter // ASSERTION: Command should be sent via WebSocket const sentMessages = mockWebSocket.getSentMessages(); expect(sentMessages).toHaveLength(1); const message = JSON.parse(sentMessages[0]); expect(message.type).toBe('terminal_input'); expect(message.command).toBe('pwd'); expect(message.sessionName).toBe('test-session'); // ASSERTION: Current line should be cleared after submission expect(handler.getCurrentLine()).toBe(''); expect(handler.getCursorPosition()).toBe(0); // ASSERTION: Enter should write newline to terminal const writtenContent = mockTerminal.getWrittenContent(); expect(writtenContent).toContain('\r\n'); }); }); describe('Security Validation', () => { test('XSS prevention should not block legitimate commands', () => { mockTerminal.clear(); // Type legitimate command that might look suspicious mockTerminal.simulateUserInput('e'); mockTerminal.simulateUserInput('c'); mockTerminal.simulateUserInput('h'); mockTerminal.simulateUserInput('o'); // Should still work normally expect(handler.getCurrentLine()).toBe('echo'); expect(handler.getCursorPosition()).toBe(4); }); test('Should block dangerous input patterns', () => { mockTerminal.clear(); // Try to input dangerous characters mockTerminal.simulateUserInput('<'); // Should not appear in current line due to validation expect(handler.getCurrentLine()).toBe(''); expect(handler.getCursorPosition()).toBe(0); }); }); }); describe('Initial Prompt Display Tests', () => { test('SSH connection should provide initial prompt in history', async () => { // This test will verify that SSH connection manager broadcasts initial prompt // We'll import the actual SSH connection manager for this test const { SSHConnectionManager } = await import('../src/ssh-connection-manager.js'); const manager = new SSHConnectionManager(8080); const promptHistory: string[] = []; // Mock a session with initial prompt const mockSessionName = 'test-prompt-session'; // Add listener to capture prompt broadcasts manager.addTerminalOutputListener(mockSessionName, (entry) => { promptHistory.push(entry.content || entry.output || ''); }); // The SSH connection manager should broadcast initial prompt // This test documents the expected behavior expect(typeof manager.getTerminalHistory).toBe('function'); expect(typeof manager.addTerminalOutputListener).toBe('function'); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LightspeedDMS/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server