/** * Modern Terminal Features Tests * Tests for true color, enhanced Unicode, and mouse interaction support * Requirements: 12.1, 12.2, 12.3 */ const { TerminalCapabilities, TrueColorUtils, UnicodeChars, MouseUtils, FeatureDetection, } = require("../../src/tui/utils/modernTerminal.js"); // Mock environment variables for testing const mockEnv = (envVars) => { const originalEnv = { ...process.env }; Object.assign(process.env, envVars); return () => { process.env = originalEnv; }; }; describe("Terminal Capabilities", () => { describe("True Color Support Detection", () => { test("should detect true color via COLORTERM=truecolor", () => { const restore = mockEnv({ COLORTERM: "truecolor" }); expect(TerminalCapabilities.supportsTrueColor()).toBe(true); restore(); }); test("should detect true color via COLORTERM=24bit", () => { const restore = mockEnv({ COLORTERM: "24bit" }); expect(TerminalCapabilities.supportsTrueColor()).toBe(true); restore(); }); test("should detect true color via modern terminal programs", () => { const restore = mockEnv({ TERM_PROGRAM: "iTerm.app" }); expect(TerminalCapabilities.supportsTrueColor()).toBe(true); restore(); }); test("should detect true color via Windows Terminal", () => { const originalPlatform = process.platform; Object.defineProperty(process, "platform", { value: "win32" }); const restore = mockEnv({ WT_SESSION: "12345" }); expect(TerminalCapabilities.supportsTrueColor()).toBe(true); restore(); Object.defineProperty(process, "platform", { value: originalPlatform }); }); test("should detect true color via TERM variable", () => { const restore = mockEnv({ TERM: "xterm-256color" }); expect(TerminalCapabilities.supportsTrueColor()).toBe(true); restore(); }); test("should not detect true color when no indicators present", () => { const restore = mockEnv({ COLORTERM: undefined, TERM_PROGRAM: undefined, TERMINAL_EMULATOR: undefined, TERM: "xterm", WT_SESSION: undefined, }); expect(TerminalCapabilities.supportsTrueColor()).toBe(false); restore(); }); }); describe("Enhanced Unicode Support Detection", () => { test("should detect Unicode via UTF-8 locale", () => { const restore = mockEnv({ LC_ALL: "en_US.UTF-8" }); expect(TerminalCapabilities.supportsEnhancedUnicode()).toBe(true); restore(); }); test("should detect Unicode via LANG variable", () => { const restore = mockEnv({ LANG: "en_US.utf8" }); expect(TerminalCapabilities.supportsEnhancedUnicode()).toBe(true); restore(); }); test("should fallback to true color detection for Unicode", () => { const restore = mockEnv({ LC_ALL: undefined, LC_CTYPE: undefined, LANG: undefined, COLORTERM: "truecolor", }); expect(TerminalCapabilities.supportsEnhancedUnicode()).toBe(true); restore(); }); test("should not detect Unicode when no indicators present", () => { const restore = mockEnv({ LC_ALL: undefined, LC_CTYPE: undefined, LANG: undefined, COLORTERM: undefined, TERM_PROGRAM: undefined, }); expect(TerminalCapabilities.supportsEnhancedUnicode()).toBe(false); restore(); }); }); describe("Mouse Interaction Support Detection", () => { test("should detect mouse via TERM_FEATURES", () => { const restore = mockEnv({ TERM_FEATURES: "mouse,color" }); expect(TerminalCapabilities.supportsMouseInteraction()).toBe(true); restore(); }); test("should detect mouse via modern terminal programs", () => { const restore = mockEnv({ TERM_PROGRAM: "Windows Terminal" }); expect(TerminalCapabilities.supportsMouseInteraction()).toBe(true); restore(); }); test("should detect mouse via Windows Terminal", () => { const originalPlatform = process.platform; Object.defineProperty(process, "platform", { value: "win32" }); const restore = mockEnv({ WT_SESSION: "12345" }); expect(TerminalCapabilities.supportsMouseInteraction()).toBe(true); restore(); Object.defineProperty(process, "platform", { value: originalPlatform }); }); test("should not detect mouse when no indicators present", () => { const restore = mockEnv({ TERM_FEATURES: undefined, TERM_PROGRAM: undefined, TERMINAL_EMULATOR: undefined, WT_SESSION: undefined, }); expect(TerminalCapabilities.supportsMouseInteraction()).toBe(false); restore(); }); }); describe("Terminal Information", () => { test("should return terminal information", () => { const restore = mockEnv({ TERM_PROGRAM: "iTerm.app", TERM: "xterm-256color", }); const info = TerminalCapabilities.getTerminalInfo(); expect(info).toHaveProperty("width"); expect(info).toHaveProperty("height"); expect(info).toHaveProperty("colorDepth"); expect(info).toHaveProperty("supportsUnicode"); expect(info).toHaveProperty("supportsMouse"); expect(info).toHaveProperty("platform"); expect(info).toHaveProperty("termProgram"); expect(info).toHaveProperty("termType"); expect(info.termProgram).toBe("iTerm.app"); expect(info.termType).toBe("xterm-256color"); restore(); }); }); }); describe("True Color Utils", () => { describe("RGB Color Generation", () => { test("should generate RGB true color escape sequence", () => { // Mock true color support jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); const result = TrueColorUtils.rgb(255, 128, 64); expect(result).toBe("\x1b[38;2;255;128;64m"); }); test("should generate RGB background true color escape sequence", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); const result = TrueColorUtils.rgbBg(255, 128, 64); expect(result).toBe("\x1b[48;2;255;128;64m"); }); test("should fallback to 8-bit color when true color not supported", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(false); const result = TrueColorUtils.rgb(255, 0, 0); expect(result).toMatch(/\x1b\[38;5;\d+m/); }); }); describe("Hex Color Conversion", () => { test("should convert hex to RGB true color", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); const result = TrueColorUtils.hex("#FF8040"); expect(result).toBe("\x1b[38;2;255;128;64m"); }); test("should convert hex to RGB background true color", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); const result = TrueColorUtils.hexBg("#FF8040"); expect(result).toBe("\x1b[48;2;255;128;64m"); }); test("should handle hex colors without # prefix", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); const result = TrueColorUtils.hex("FF8040"); expect(result).toBe("\x1b[38;2;255;128;64m"); }); }); describe("Ink Color Compatibility", () => { test("should return hex color for true color terminals", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); const result = TrueColorUtils.getInkColor("#FF0000"); expect(result).toBe("#FF0000"); }); test("should return standard color names for fallback", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(false); const result = TrueColorUtils.getInkColor("#FF0000"); expect(result).toBe("red"); }); test("should fallback to white for unknown colors", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(false); const result = TrueColorUtils.getInkColor("#123456"); expect(result).toBe("white"); }); }); describe("Color Reset", () => { test("should provide reset escape sequence", () => { const result = TrueColorUtils.reset(); expect(result).toBe("\x1b[0m"); }); }); }); describe("Unicode Characters", () => { describe("Character Categories", () => { test("should provide box drawing characters", () => { expect(UnicodeChars.box.horizontal).toBe("─"); expect(UnicodeChars.box.vertical).toBe("│"); expect(UnicodeChars.box.topLeft).toBe("┌"); expect(UnicodeChars.box.roundedTopLeft).toBe("╭"); }); test("should provide progress characters", () => { expect(UnicodeChars.progress.full).toBe("█"); expect(UnicodeChars.progress.empty).toBe("░"); expect(UnicodeChars.progress.spinner).toBeInstanceOf(Array); expect(UnicodeChars.progress.spinner.length).toBeGreaterThan(0); }); test("should provide symbol characters", () => { expect(UnicodeChars.symbols.checkMark).toBe("✓"); expect(UnicodeChars.symbols.crossMark).toBe("✗"); expect(UnicodeChars.symbols.rightArrow).toBe("→"); }); test("should provide emoji characters", () => { expect(UnicodeChars.emoji.gear).toBe("⚙"); expect(UnicodeChars.emoji.rocket).toBe("🚀"); }); }); describe("Character Selection with Fallbacks", () => { test("should return Unicode character when supported", () => { jest .spyOn(TerminalCapabilities, "supportsEnhancedUnicode") .mockReturnValue(true); const result = UnicodeChars.getChar("box", "horizontal"); expect(result).toBe("─"); }); test("should return ASCII fallback when Unicode not supported", () => { jest .spyOn(TerminalCapabilities, "supportsEnhancedUnicode") .mockReturnValue(false); const result = UnicodeChars.getChar("box", "horizontal"); expect(result).toBe("-"); }); test("should return custom fallback when provided", () => { jest .spyOn(TerminalCapabilities, "supportsEnhancedUnicode") .mockReturnValue(false); const result = UnicodeChars.getChar("box", "nonexistent", "X"); expect(result).toBe("X"); }); test("should return default fallback for unknown characters", () => { jest .spyOn(TerminalCapabilities, "supportsEnhancedUnicode") .mockReturnValue(true); const result = UnicodeChars.getChar("unknown", "unknown"); expect(result).toBe("?"); }); }); }); describe("Mouse Utils", () => { let originalStdout; beforeEach(() => { originalStdout = process.stdout.write; process.stdout.write = jest.fn(); }); afterEach(() => { process.stdout.write = originalStdout; }); describe("Mouse Tracking Control", () => { test("should enable mouse tracking when supported", () => { jest .spyOn(TerminalCapabilities, "supportsMouseInteraction") .mockReturnValue(true); const result = MouseUtils.enableMouse(); expect(result).toBe(true); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1000h"); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1002h"); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1015h"); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1006h"); }); test("should not enable mouse tracking when not supported", () => { jest .spyOn(TerminalCapabilities, "supportsMouseInteraction") .mockReturnValue(false); const result = MouseUtils.enableMouse(); expect(result).toBe(false); expect(process.stdout.write).not.toHaveBeenCalled(); }); test("should disable mouse tracking when supported", () => { jest .spyOn(TerminalCapabilities, "supportsMouseInteraction") .mockReturnValue(true); const result = MouseUtils.disableMouse(); expect(result).toBe(true); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1006l"); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1015l"); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1002l"); expect(process.stdout.write).toHaveBeenCalledWith("\x1b[?1000l"); }); }); describe("Mouse Event Parsing", () => { test("should parse SGR mouse format press event", () => { const data = "\x1b[<0;10;5M"; const result = MouseUtils.parseMouseEvent(data); expect(result).toEqual({ button: 0, x: 10, y: 5, action: "press", type: "mouse", }); }); test("should parse SGR mouse format release event", () => { const data = "\x1b[<0;10;5m"; const result = MouseUtils.parseMouseEvent(data); expect(result).toEqual({ button: 0, x: 10, y: 5, action: "release", type: "mouse", }); }); test("should parse basic mouse format", () => { const data = "\x1b[M" + String.fromCharCode(32, 42, 37); // button=0, x=10, y=5 const result = MouseUtils.parseMouseEvent(data); expect(result).toEqual({ button: 0, x: 10, y: 5, action: "press", type: "mouse", }); }); test("should return null for invalid mouse data", () => { const data = "invalid mouse data"; const result = MouseUtils.parseMouseEvent(data); expect(result).toBeNull(); }); }); describe("Bounds Checking", () => { test("should detect coordinates within bounds", () => { const bounds = { x: 5, y: 5, width: 10, height: 5 }; expect(MouseUtils.isWithinBounds(10, 7, bounds)).toBe(true); expect(MouseUtils.isWithinBounds(5, 5, bounds)).toBe(true); expect(MouseUtils.isWithinBounds(14, 9, bounds)).toBe(true); }); test("should detect coordinates outside bounds", () => { const bounds = { x: 5, y: 5, width: 10, height: 5 }; expect(MouseUtils.isWithinBounds(4, 7, bounds)).toBe(false); expect(MouseUtils.isWithinBounds(15, 7, bounds)).toBe(false); expect(MouseUtils.isWithinBounds(10, 4, bounds)).toBe(false); expect(MouseUtils.isWithinBounds(10, 10, bounds)).toBe(false); }); }); }); describe("Feature Detection", () => { describe("Available Features", () => { test("should return all available features", () => { const features = FeatureDetection.getAvailableFeatures(); expect(features).toHaveProperty("trueColor"); expect(features).toHaveProperty("enhancedUnicode"); expect(features).toHaveProperty("mouseInteraction"); expect(features).toHaveProperty("terminalInfo"); expect(typeof features.trueColor).toBe("boolean"); expect(typeof features.enhancedUnicode).toBe("boolean"); expect(typeof features.mouseInteraction).toBe("boolean"); expect(typeof features.terminalInfo).toBe("object"); }); }); describe("Optimal Configuration", () => { test("should return optimal configuration based on capabilities", () => { const config = FeatureDetection.getOptimalConfig(); expect(config).toHaveProperty("colors"); expect(config).toHaveProperty("characters"); expect(config).toHaveProperty("interaction"); expect(config).toHaveProperty("performance"); expect(config.colors).toHaveProperty("useTrue"); expect(config.colors).toHaveProperty("palette"); expect(config.characters).toHaveProperty("useUnicode"); expect(config.interaction).toHaveProperty("enableMouse"); }); test("should configure for enhanced features when available", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(true); jest .spyOn(TerminalCapabilities, "supportsEnhancedUnicode") .mockReturnValue(true); jest .spyOn(TerminalCapabilities, "supportsMouseInteraction") .mockReturnValue(true); const config = FeatureDetection.getOptimalConfig(); expect(config.colors.useTrue).toBe(true); expect(config.colors.palette).toBe("extended"); expect(config.characters.useUnicode).toBe(true); expect(config.characters.boxStyle).toBe("rounded"); expect(config.interaction.enableMouse).toBe(true); expect(config.performance.animationLevel).toBe("full"); }); test("should configure for basic features when not available", () => { jest .spyOn(TerminalCapabilities, "supportsTrueColor") .mockReturnValue(false); jest .spyOn(TerminalCapabilities, "supportsEnhancedUnicode") .mockReturnValue(false); jest .spyOn(TerminalCapabilities, "supportsMouseInteraction") .mockReturnValue(false); const config = FeatureDetection.getOptimalConfig(); expect(config.colors.useTrue).toBe(false); expect(config.colors.palette).toBe("basic"); expect(config.characters.useUnicode).toBe(false); expect(config.characters.boxStyle).toBe("basic"); expect(config.interaction.enableMouse).toBe(false); expect(config.performance.animationLevel).toBe("reduced"); }); }); describe("Capability Testing", () => { test("should test terminal capabilities", () => { const results = FeatureDetection.testCapabilities(); expect(results).toHaveProperty("trueColor"); expect(results).toHaveProperty("unicode"); expect(results).toHaveProperty("mouse"); expect(results).toHaveProperty("errors"); expect(typeof results.trueColor).toBe("boolean"); expect(typeof results.unicode).toBe("boolean"); expect(typeof results.mouse).toBe("boolean"); expect(Array.isArray(results.errors)).toBe(true); }); }); }); // Cleanup mocks afterEach(() => { jest.restoreAllMocks(); });