const React = require("react"); const { render } = require("ink-testing-library"); const { PerformanceBenchmark, PerformanceProfiler, MemoryMonitor, } = require("../../../src/tui/utils/performanceUtils.js"); // Import optimized components const OptimizedMenuList = require("../../../src/tui/components/common/OptimizedMenuList.jsx"); const VirtualScrollableContainer = require("../../../src/tui/components/common/VirtualScrollableContainer.jsx"); const OptimizedProgressBar = require("../../../src/tui/components/common/OptimizedProgressBar.jsx"); // Import original components for comparison const MenuList = require("../../../src/tui/components/common/MenuList.jsx"); const ScrollableContainer = require("../../../src/tui/components/common/ScrollableContainer.jsx"); const ProgressBar = require("../../../src/tui/components/common/ProgressBar.jsx"); /** * Performance tests for TUI component rendering * Requirements: 4.1, 4.3, 4.4 */ describe("TUI Component Rendering Performance", () => { let profiler; let memoryMonitor; beforeEach(() => { profiler = new PerformanceProfiler(); memoryMonitor = new MemoryMonitor(); }); afterEach(() => { profiler.clear(); memoryMonitor.stopMonitoring(); }); describe("MenuList Performance", () => { const generateMenuItems = (count) => { return Array.from({ length: count }, (_, i) => ({ label: `Menu Item ${i + 1}`, shortcut: String.fromCharCode(97 + (i % 26)), // a-z description: `Description for menu item ${i + 1}`, })); }; test("should render small menu lists efficiently", async () => { const items = generateMenuItems(10); const benchmark = new PerformanceBenchmark("Small MenuList Rendering"); const testFunction = () => { const { unmount } = render( React.createElement(OptimizedMenuList, { items, selectedIndex: 0, onSelect: () => {}, showShortcuts: true, }) ); unmount(); }; const results = await benchmark.run(testFunction, 100); expect(results.average).toBeLessThan(10); // Should render in less than 10ms on average expect(results.p95).toBeLessThan(20); // 95% of renders should be under 20ms benchmark.logResults(); }); test("should handle large menu lists with virtual scrolling", async () => { const items = generateMenuItems(1000); const benchmark = new PerformanceBenchmark("Large MenuList Rendering"); const testFunction = () => { const { unmount } = render( React.createElement(OptimizedMenuList, { items, selectedIndex: 0, onSelect: () => {}, showShortcuts: true, }) ); unmount(); }; const results = await benchmark.run(testFunction, 50); expect(results.average).toBeLessThan(50); // Should render in less than 50ms on average expect(results.p95).toBeLessThan(100); // 95% of renders should be under 100ms benchmark.logResults(); }); test("should show performance improvement over original MenuList", async () => { const items = generateMenuItems(500); // Benchmark original MenuList const originalBenchmark = new PerformanceBenchmark("Original MenuList"); const originalTestFunction = () => { const { unmount } = render( React.createElement(MenuList, { items, selectedIndex: 0, onSelect: () => {}, showShortcuts: true, }) ); unmount(); }; const originalResults = await originalBenchmark.run( originalTestFunction, 30 ); // Benchmark optimized MenuList const optimizedBenchmark = new PerformanceBenchmark("Optimized MenuList"); const optimizedTestFunction = () => { const { unmount } = render( React.createElement(OptimizedMenuList, { items, selectedIndex: 0, onSelect: () => {}, showShortcuts: true, }) ); unmount(); }; const optimizedResults = await optimizedBenchmark.run( optimizedTestFunction, 30 ); // Optimized version should be at least 20% faster const improvement = (originalResults.average - optimizedResults.average) / originalResults.average; expect(improvement).toBeGreaterThan(0.2); console.log( `Performance improvement: ${(improvement * 100).toFixed(1)}%` ); originalBenchmark.logResults(); optimizedBenchmark.logResults(); }); }); describe("VirtualScrollableContainer Performance", () => { const generateScrollItems = (count) => { return Array.from({ length: count }, (_, i) => ({ id: i, content: `Item ${ i + 1 } - Lorem ipsum dolor sit amet, consectetur adipiscing elit.`, })); }; const renderItem = (item, index) => { return React.createElement("div", { key: index }, item.content); }; test("should handle large datasets efficiently with virtual scrolling", async () => { const items = generateScrollItems(10000); const benchmark = new PerformanceBenchmark( "Virtual Scrolling Large Dataset" ); memoryMonitor.startMonitoring(1000); const testFunction = () => { const { unmount } = render( React.createElement(VirtualScrollableContainer, { items, renderItem, itemHeight: 1, showScrollIndicators: true, }) ); unmount(); }; const results = await benchmark.run(testFunction, 20); // Should handle large datasets efficiently expect(results.average).toBeLessThan(100); // Should render in less than 100ms expect(results.p95).toBeLessThan(200); // 95% of renders should be under 200ms // Check memory usage const memoryStats = memoryMonitor.getStatistics(); const memoryLeak = memoryMonitor.checkForLeaks(); expect(memoryLeak.isLikely).toBe(false); // Should not have memory leaks benchmark.logResults(); memoryMonitor.logSummary(); }); test("should maintain consistent performance with different scroll positions", async () => { const items = generateScrollItems(5000); const scrollPositions = [0, 100, 500, 1000, 2500, 4999]; const results = []; for (const scrollPosition of scrollPositions) { const benchmark = new PerformanceBenchmark( `Virtual Scroll Position ${scrollPosition}` ); const testFunction = () => { const { unmount } = render( React.createElement(VirtualScrollableContainer, { items, renderItem, itemHeight: 1, initialScrollPosition: scrollPosition, }) ); unmount(); }; const result = await benchmark.run(testFunction, 20); results.push(result.average); } // Performance should be consistent across different scroll positions const maxVariation = Math.max(...results) - Math.min(...results); const averageTime = results.reduce((sum, time) => sum + time, 0) / results.length; const variationPercentage = (maxVariation / averageTime) * 100; expect(variationPercentage).toBeLessThan(50); // Variation should be less than 50% console.log( `Scroll position performance variation: ${variationPercentage.toFixed( 1 )}%` ); }); }); describe("ProgressBar Performance", () => { test("should handle rapid progress updates efficiently", async () => { const benchmark = new PerformanceBenchmark("Rapid Progress Updates"); memoryMonitor.startMonitoring(500); const testFunction = () => { let progress = 0; const { rerender, unmount } = render( React.createElement(OptimizedProgressBar, { progress, label: "Test Progress", animate: true, debounceDelay: 50, }) ); // Simulate rapid updates for (let i = 0; i < 100; i++) { progress = i; rerender( React.createElement(OptimizedProgressBar, { progress, label: "Test Progress", animate: true, debounceDelay: 50, }) ); } unmount(); }; const results = await benchmark.run(testFunction, 10); expect(results.average).toBeLessThan(200); // Should handle rapid updates efficiently // Check for memory leaks during rapid updates const memoryLeak = memoryMonitor.checkForLeaks(); expect(memoryLeak.isLikely).toBe(false); benchmark.logResults(); memoryMonitor.logSummary(); }); test("should optimize multi-progress bar rendering", async () => { const progressItems = Array.from({ length: 20 }, (_, i) => ({ key: `progress-${i}`, label: `Operation ${i + 1}`, progress: Math.random() * 100, color: ["blue", "green", "yellow", "cyan", "magenta"][i % 5], })); const benchmark = new PerformanceBenchmark( "Multi-Progress Bar Rendering" ); const testFunction = () => { const { unmount } = render( React.createElement(OptimizedProgressBar.Multi, { progressItems, width: 40, showLabels: true, showPercentages: true, animate: false, }) ); unmount(); }; const results = await benchmark.run(testFunction, 50); expect(results.average).toBeLessThan(30); // Should render multiple progress bars efficiently expect(results.p95).toBeLessThan(60); benchmark.logResults(); }); }); describe("Memory Management", () => { test("should not leak memory during component lifecycle", async () => { const items = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`); memoryMonitor.startMonitoring(500); // Create and destroy components multiple times for (let i = 0; i < 50; i++) { const { unmount } = render( React.createElement(OptimizedMenuList, { items, selectedIndex: i % items.length, onSelect: () => {}, showShortcuts: true, }) ); // Simulate some async operations await new Promise((resolve) => setTimeout(resolve, 10)); unmount(); } // Wait for garbage collection await new Promise((resolve) => setTimeout(resolve, 1000)); const memoryLeak = memoryMonitor.checkForLeaks(); const memoryStats = memoryMonitor.getStatistics(); expect(memoryLeak.isLikely).toBe(false); expect(memoryStats.growth.heapUsed).toBeLessThan(50 * 1024 * 1024); // Less than 50MB growth memoryMonitor.logSummary(); }); test("should clean up event listeners and timers", async () => { const initialHandlers = process.listenerCount("uncaughtException"); // Create components with timers and event listeners const components = []; for (let i = 0; i < 10; i++) { const { unmount } = render( React.createElement(OptimizedProgressBar, { progress: 50, animate: true, animationSpeed: 100, }) ); components.push(unmount); } // Unmount all components components.forEach((unmount) => unmount()); // Wait for cleanup await new Promise((resolve) => setTimeout(resolve, 500)); const finalHandlers = process.listenerCount("uncaughtException"); // Should not have increased the number of event listeners expect(finalHandlers).toBeLessThanOrEqual(initialHandlers + 1); }); }); describe("Debouncing and Throttling", () => { test("should reduce render frequency with debouncing", async () => { let renderCount = 0; const TestComponent = () => { renderCount++; return React.createElement("div", null, "Test"); }; const { rerender } = render(React.createElement(TestComponent)); // Trigger multiple rapid re-renders for (let i = 0; i < 100; i++) { rerender(React.createElement(TestComponent)); } // With proper debouncing, render count should be significantly less than 100 expect(renderCount).toBeLessThan(50); }); test("should maintain responsiveness with throttling", async () => { const updates = []; let lastUpdate = Date.now(); const TestComponent = ({ value }) => { const currentTime = Date.now(); updates.push(currentTime - lastUpdate); lastUpdate = currentTime; return React.createElement("div", null, value); }; const { rerender } = render( React.createElement(TestComponent, { value: 0 }) ); // Simulate rapid updates with throttling for (let i = 1; i <= 50; i++) { await new Promise((resolve) => setTimeout(resolve, 10)); rerender(React.createElement(TestComponent, { value: i })); } // Updates should be throttled but still responsive const averageInterval = updates.reduce((sum, interval) => sum + interval, 0) / updates.length; expect(averageInterval).toBeGreaterThan(5); // Should be throttled expect(averageInterval).toBeLessThan(100); // But still responsive }); }); }); describe("Performance Regression Tests", () => { test("should maintain performance benchmarks", async () => { const benchmarks = { smallMenuList: { maxAverage: 10, maxP95: 20 }, largeMenuList: { maxAverage: 50, maxP95: 100 }, virtualScrolling: { maxAverage: 100, maxP95: 200 }, progressBar: { maxAverage: 30, maxP95: 60 }, }; // This test would be run in CI to ensure performance doesn't regress // For now, we'll just verify the benchmarks structure expect(benchmarks).toBeDefined(); expect(Object.keys(benchmarks)).toHaveLength(4); }); });