Skip to main content
Glama
interactive-terminal-browser-real.test.ts10.9 kB
import { JSDOM } from "jsdom"; const { TerminalInputHandler } = require("../static/terminal-input-handler.js"); describe("Story 1: Browser Terminal Input Handling - Real Production Code Tests", () => { let dom: JSDOM; let window: Window & typeof globalThis; let document: Document; let terminalHandler: any; let mockTerm: any; let mockWebSocket: any; beforeEach(() => { // Set up DOM environment dom = new JSDOM( `<!DOCTYPE html> <html> <head><title>Terminal Test</title></head> <body> <div id="terminal-container"> <div id="terminal"></div> </div> <div id="connection-status">🟢 Connected</div> </body> </html>`, { pretendToBeVisual: true, resources: "usable" } ); window = dom.window as any; document = window.document; global.window = window; global.document = document; // Mock xterm.js Terminal mockTerm = { write: jest.fn(), onData: jest.fn(), element: document.getElementById('terminal') }; // Mock WebSocket mockWebSocket = { readyState: 1, // OPEN send: jest.fn(), sentMessages: [] as string[] }; mockWebSocket.send.mockImplementation((data: string) => { mockWebSocket.sentMessages.push(data); }); // Initialize the real terminal handler terminalHandler = new TerminalInputHandler(mockTerm, mockWebSocket, 'test-session'); }); afterEach(() => { dom.window.close(); }); describe("AC1.1: Local Command Echo - Real Code Tests", () => { it("should echo characters immediately when typed", () => { // Type a character - this should call the real handleLocalInput method const testChar = 'a'; terminalHandler.handleInput(testChar); // Should echo locally immediately expect(mockTerm.write).toHaveBeenCalledWith(testChar); expect(terminalHandler.getCurrentLine()).toBe(testChar); expect(terminalHandler.getCursorPosition()).toBe(1); }); it("should handle cursor movement after typing", () => { // Type multiple characters 'hello'.split('').forEach(char => terminalHandler.handleInput(char)); // Cursor should advance expect(terminalHandler.getCursorPosition()).toBe(5); expect(terminalHandler.getCurrentLine()).toBe('hello'); }); it("should handle backspace correctly", () => { // Type some characters first 'hello'.split('').forEach(char => terminalHandler.handleInput(char)); // Clear previous write calls mockTerm.write.mockClear(); // Simulate backspace terminalHandler.handleInput('\x08'); // Should remove character locally expect(terminalHandler.getCurrentLine()).toBe('hell'); expect(terminalHandler.getCursorPosition()).toBe(4); expect(mockTerm.write).toHaveBeenCalledWith('\x08 \x08'); }); }); describe("AC1.2: Command Line Navigation - Real Code Tests", () => { it("should handle left arrow key movement", () => { // Type some characters first 'hello world'.split('').forEach(char => terminalHandler.handleInput(char)); // Clear previous calls mockTerm.write.mockClear(); // Simulate left arrow key terminalHandler.handleInput('\x1b[D'); // Cursor should move left expect(terminalHandler.getCursorPosition()).toBe(10); expect(mockTerm.write).toHaveBeenCalledWith('\x1b[D'); }); it("should handle Home key correctly", () => { // Type some characters first 'hello world'.split('').forEach(char => terminalHandler.handleInput(char)); // Clear previous calls mockTerm.write.mockClear(); // Simulate Home key terminalHandler.handleInput('\x1b[H'); // Cursor should move to beginning expect(terminalHandler.getCursorPosition()).toBe(0); }); it("should handle End key correctly", () => { // Type some characters first 'hello world'.split('').forEach(char => terminalHandler.handleInput(char)); // Move cursor to home first terminalHandler.handleInput('\x1b[H'); // Clear previous calls mockTerm.write.mockClear(); // Simulate End key terminalHandler.handleInput('\x1b[F'); // Cursor should move to end expect(terminalHandler.getCursorPosition()).toBe(11); }); }); describe("AC1.3: Command Submission - Real Code Tests", () => { it("should handle Enter key submission correctly", () => { // Type a command first 'ls -la'.split('').forEach(char => terminalHandler.handleInput(char)); // Clear previous calls mockTerm.write.mockClear(); mockWebSocket.send.mockClear(); // Simulate Enter key terminalHandler.handleInput('\r'); // Should move to new line, reset state, and send command expect(mockTerm.write).toHaveBeenCalledWith('\r\n'); expect(terminalHandler.getCurrentLine()).toBe(''); expect(terminalHandler.getCursorPosition()).toBe(0); expect(terminalHandler.isLocked()).toBe(true); }); it("should send command via WebSocket on Enter", () => { const command = 'echo test'; // Type command first command.split('').forEach(char => terminalHandler.handleInput(char)); // Clear previous calls mockWebSocket.send.mockClear(); // Simulate Enter key terminalHandler.handleInput('\r'); // Should send terminal_input message expect(mockWebSocket.send).toHaveBeenCalled(); const sentData = JSON.parse(mockWebSocket.sentMessages[mockWebSocket.sentMessages.length - 1]); expect(sentData.type).toBe('terminal_input'); expect(sentData.sessionName).toBe('test-session'); expect(sentData.data).toBe(command + '\r'); }); }); describe("AC1.4: Terminal Input Locking - Real Code Tests", () => { it("should block input when terminal is locked", () => { // First submit a command to lock the terminal 'test'.split('').forEach(char => terminalHandler.handleInput(char)); terminalHandler.handleInput('\r'); // This should lock terminal // Clear previous calls mockTerm.write.mockClear(); // Try to input characters while locked terminalHandler.handleInput('b'); terminalHandler.handleInput('l'); terminalHandler.handleInput('o'); // Input should be blocked expect(mockTerm.write).not.toHaveBeenCalledWith('b'); expect(mockTerm.write).not.toHaveBeenCalledWith('l'); expect(mockTerm.write).not.toHaveBeenCalledWith('o'); expect(terminalHandler.getCurrentLine()).toBe(''); }); it("should show visual indication when locked", () => { // First submit a command to lock the terminal 'test'.split('').forEach(char => terminalHandler.handleInput(char)); terminalHandler.handleInput('\r'); // This should lock terminal // Terminal element should have locked class const terminalElement = document.getElementById('terminal'); expect(terminalElement!.classList.contains('terminal-locked')).toBe(true); }); }); describe("AC1.5: Terminal Input Unlocking - Real Code Tests", () => { it("should unlock when user command completes", () => { // First submit a command to lock the terminal 'test'.split('').forEach(char => terminalHandler.handleInput(char)); terminalHandler.handleInput('\r'); // This should lock terminal expect(terminalHandler.isLocked()).toBe(true); // Simulate receiving command completion (not from Claude Code) const messageData = { type: 'terminal_output' as const, data: 'user@host:~$ ' // Note: no source specified, which means it's NOT from claude_code }; terminalHandler.handleTerminalOutput(messageData); // Terminal should unlock expect(terminalHandler.isLocked()).toBe(false); }); it("should remain locked for Claude Code commands", () => { // First submit a command to lock the terminal 'test'.split('').forEach(char => terminalHandler.handleInput(char)); terminalHandler.handleInput('\r'); // This should lock terminal expect(terminalHandler.isLocked()).toBe(true); // Simulate receiving Claude Code command output (should not unlock) const messageData = { type: 'terminal_output' as const, data: 'some output from Claude Code command', source: 'claude_code' // This indicates it's from Claude Code }; terminalHandler.handleTerminalOutput(messageData); // Terminal should remain locked expect(terminalHandler.isLocked()).toBe(true); }); }); describe("Security and Validation - Real Code Tests", () => { it("should validate input and prevent XSS", () => { // Try to input malicious characters/sequences const maliciousInput = '<script>alert("xss")</script>'; maliciousInput.split('').forEach(char => { terminalHandler.handleInput(char); }); // Should sanitize and validate the input const currentLine = terminalHandler.getCurrentLine(); expect(currentLine).not.toContain('<script>'); // Should either escape or reject the malicious content }); it("should validate cursor boundaries", () => { // Type some text 'hello'.split('').forEach(char => terminalHandler.handleInput(char)); // Try to move cursor beyond boundaries // Multiple left arrows to go beyond start for (let i = 0; i < 10; i++) { terminalHandler.handleInput('\x1b[D'); } // Cursor should not go below 0 expect(terminalHandler.getCursorPosition()).toBe(0); // Move to end and try to go beyond terminalHandler.handleInput('\x1b[F'); // End key for (let i = 0; i < 10; i++) { terminalHandler.handleInput('\x1b[C'); } // Cursor should not go beyond line length expect(terminalHandler.getCursorPosition()).toBe(terminalHandler.getCurrentLine().length); }); }); describe("Error Handling - Real Code Tests", () => { it("should handle WebSocket errors gracefully", () => { // Set WebSocket to error state mockWebSocket.readyState = 3; // CLOSED // Try to submit command 'test'.split('').forEach(char => terminalHandler.handleInput(char)); // Should not throw error when trying to send expect(() => terminalHandler.handleInput('\r')).not.toThrow(); // Should show error indication in UI const statusElement = document.getElementById('connection-status'); expect(statusElement!.innerHTML).toContain('Error'); }); }); });

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