const PerformanceOptimizer = require("../../../src/tui/utils/PerformanceOptimizer.js"); describe("PerformanceOptimizer", () => { let optimizer; beforeEach(() => { optimizer = new PerformanceOptimizer(); }); afterEach(() => { optimizer.destroy(); }); describe("debounce", () => { it("should debounce function calls", (done) => { let callCount = 0; const testFunction = () => { callCount++; }; const debouncedFunction = optimizer.debounce(testFunction, 100, "test"); // Call multiple times rapidly debouncedFunction(); debouncedFunction(); debouncedFunction(); // Should not have been called yet expect(callCount).toBe(0); // Wait for debounce delay setTimeout(() => { expect(callCount).toBe(1); done(); }, 150); }); }); describe("throttle", () => { it("should throttle function calls", (done) => { let callCount = 0; const testFunction = () => { callCount++; }; const throttledFunction = optimizer.throttle(testFunction, 100, "test"); // Call multiple times rapidly throttledFunction(); throttledFunction(); throttledFunction(); // Should have been called once immediately expect(callCount).toBe(1); // Wait and call again setTimeout(() => { throttledFunction(); expect(callCount).toBe(2); done(); }, 150); }); }); describe("memoize", () => { it("should memoize function results", () => { let callCount = 0; const expensiveFunction = (x, y) => { callCount++; return x + y; }; const memoizedFunction = optimizer.memoize(expensiveFunction); // First call const result1 = memoizedFunction(1, 2); expect(result1).toBe(3); expect(callCount).toBe(1); // Second call with same arguments const result2 = memoizedFunction(1, 2); expect(result2).toBe(3); expect(callCount).toBe(1); // Should not have called function again // Third call with different arguments const result3 = memoizedFunction(2, 3); expect(result3).toBe(5); expect(callCount).toBe(2); }); it("should limit cache size", () => { const testFunction = (x) => x * 2; const memoizedFunction = optimizer.memoize(testFunction, undefined, 2); // Fill cache beyond limit memoizedFunction(1); memoizedFunction(2); memoizedFunction(3); // Should evict first entry // Verify first entry was evicted let callCount = 0; const countingFunction = (x) => { callCount++; return x * 2; }; const countingMemoized = optimizer.memoize( countingFunction, undefined, 2 ); countingMemoized(1); countingMemoized(2); expect(callCount).toBe(2); countingMemoized(3); // Should evict entry for 1 expect(callCount).toBe(3); countingMemoized(1); // Should call function again since it was evicted expect(callCount).toBe(4); }); }); describe("createVirtualScrolling", () => { it("should calculate virtual scrolling data correctly", () => { const items = Array.from({ length: 100 }, (_, i) => ({ id: i, name: `Item ${i}`, })); const result = optimizer.createVirtualScrolling(items, 300, 30, 150); expect(result.totalHeight).toBe(3000); // 100 items * 30px each expect(result.startIndex).toBe(5); // 150px / 30px per item expect(result.visibleItems.length).toBeGreaterThan(0); expect(result.visibleItems.length).toBeLessThanOrEqual(12); // Visible count + buffer }); }); describe("createLazyLoading", () => { it("should create lazy loading data correctly", () => { const items = Array.from({ length: 100 }, (_, i) => ({ id: i, name: `Item ${i}`, })); const result = optimizer.createLazyLoading(items, 10, 5); expect(result.startIndex).toBe(5); // 10 - 5 expect(result.endIndex).toBe(20); // 10 + 5*2 expect(result.loadedItems.length).toBe(15); // 20 - 5 expect(result.hasMore).toBe(true); expect(result.hasPrevious).toBe(true); }); }); describe("memory management", () => { it("should track memory usage", () => { const stats = optimizer.getMemoryUsage(); expect(stats).toHaveProperty("estimatedSizeBytes"); expect(stats).toHaveProperty("estimatedSizeMB"); expect(stats).toHaveProperty("cacheEntries"); expect(stats).toHaveProperty("eventListeners"); expect(stats).toHaveProperty("activeTimers"); expect(stats).toHaveProperty("memoryPressure"); }); it("should clean up expired cache entries", () => { // Add some cache entries optimizer.componentCache.set("test1", { data: "test", timestamp: Date.now() - 10000, }); optimizer.componentCache.set("test2", { data: "test", timestamp: Date.now(), }); optimizer.cleanupExpiredCache(5000); // 5 second max age expect(optimizer.componentCache.has("test1")).toBe(false); expect(optimizer.componentCache.has("test2")).toBe(true); }); }); describe("event listener management", () => { it("should register and cleanup event listeners", () => { const mockTarget = { addEventListener: jest.fn(), removeEventListener: jest.fn(), }; const handler = () => {}; optimizer.registerEventListener( "component1", "click", handler, mockTarget ); expect(mockTarget.addEventListener).toHaveBeenCalledWith( "click", handler ); optimizer.cleanupEventListeners("component1"); expect(mockTarget.removeEventListener).toHaveBeenCalledWith( "click", handler ); }); }); describe("batched updates", () => { it("should batch updates correctly", (done) => { let batchedUpdates = []; const updateFunction = async (batch) => { batchedUpdates.push(batch); }; const batchedUpdate = optimizer.createBatchedUpdate( updateFunction, 3, 50 ); // Add updates batchedUpdate("update1"); batchedUpdate("update2"); batchedUpdate("update3"); batchedUpdate("update4"); // Wait for batching setTimeout(() => { expect(batchedUpdates.length).toBeGreaterThan(0); expect(batchedUpdates[0]).toEqual(["update1", "update2", "update3"]); done(); }, 100); }); }); });