const React = require("react"); const { render } = require("ink-testing-library"); const { useEventListener, useInterval, useTimeout, useAsyncOperation, useMemoryMonitor, useWeakRef, useCleanup, useResourcePool, } = require("../../../src/tui/hooks/useMemoryManagement.js"); const { withMemoryManagement, MemoryOptimizedContainer, MemoryEfficientList, AutoCleanupComponent, } = require("../../../src/tui/components/common/MemoryOptimizedComponent.jsx"); /** * Memory management tests for TUI components * Requirements: 4.2, 4.5 */ describe("Memory Management Hooks", () => { describe("useCleanup", () => { test("should execute cleanup functions on unmount", () => { const cleanupFn1 = jest.fn(); const cleanupFn2 = jest.fn(); const TestComponent = () => { const { addCleanup } = useCleanup(); React.useEffect(() => { addCleanup(cleanupFn1); addCleanup(cleanupFn2); }, [addCleanup]); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); expect(cleanupFn1).not.toHaveBeenCalled(); expect(cleanupFn2).not.toHaveBeenCalled(); unmount(); expect(cleanupFn1).toHaveBeenCalledTimes(1); expect(cleanupFn2).toHaveBeenCalledTimes(1); }); test("should handle cleanup function errors gracefully", () => { const consoleSpy = jest .spyOn(console, "error") .mockImplementation(() => {}); const goodCleanup = jest.fn(); const badCleanup = jest.fn(() => { throw new Error("Cleanup error"); }); const TestComponent = () => { const { addCleanup } = useCleanup(); React.useEffect(() => { addCleanup(badCleanup); addCleanup(goodCleanup); }, [addCleanup]); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); unmount(); expect(badCleanup).toHaveBeenCalled(); expect(goodCleanup).toHaveBeenCalled(); expect(consoleSpy).toHaveBeenCalledWith( "Error during cleanup:", expect.any(Error) ); consoleSpy.mockRestore(); }); }); describe("useAsyncOperation", () => { test("should cancel operations on unmount", async () => { let operationCancelled = false; const asyncOperation = () => new Promise((resolve, reject) => { setTimeout(() => { if (operationCancelled) { reject(new Error("Operation cancelled")); } else { resolve("success"); } }, 100); }); const TestComponent = () => { const { executeAsync, cancelAllOperations } = useAsyncOperation(); React.useEffect(() => { executeAsync(asyncOperation).catch((error) => { if (error.message === "Operation cancelled") { operationCancelled = true; } }); }, [executeAsync]); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); // Unmount before operation completes setTimeout(() => unmount(), 50); // Wait for operation to complete or be cancelled await new Promise((resolve) => setTimeout(resolve, 150)); expect(operationCancelled).toBe(true); }); test("should not execute callbacks after unmount", async () => { const onSuccess = jest.fn(); const onError = jest.fn(); const TestComponent = () => { const { executeAsync } = useAsyncOperation(); React.useEffect(() => { const asyncOp = () => Promise.resolve("success"); executeAsync(asyncOp, onSuccess, onError); }, [executeAsync]); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); // Unmount immediately unmount(); // Wait for async operation await new Promise((resolve) => setTimeout(resolve, 50)); expect(onSuccess).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); }); describe("useInterval", () => { test("should clear interval on unmount", () => { jest.useFakeTimers(); const callback = jest.fn(); const TestComponent = () => { useInterval(callback, 1000); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); // Fast-forward time jest.advanceTimersByTime(2000); expect(callback).toHaveBeenCalledTimes(2); unmount(); // Fast-forward more time after unmount jest.advanceTimersByTime(2000); expect(callback).toHaveBeenCalledTimes(2); // Should not increase jest.useRealTimers(); }); test("should provide manual control over interval", () => { jest.useFakeTimers(); const callback = jest.fn(); const TestComponent = () => { const { start, stop, restart } = useInterval(callback, 1000); React.useEffect(() => { // Test manual control setTimeout(() => stop(), 1500); setTimeout(() => start(), 2500); setTimeout(() => restart(), 3500); }, [start, stop, restart]); return React.createElement("div", null, "Test"); }; render(React.createElement(TestComponent)); jest.advanceTimersByTime(1000); expect(callback).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(1000); // 2000ms total, stopped at 1500ms expect(callback).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(1000); // 3000ms total, restarted at 2500ms expect(callback).toHaveBeenCalledTimes(2); jest.useRealTimers(); }); }); describe("useTimeout", () => { test("should clear timeout on unmount", () => { jest.useFakeTimers(); const callback = jest.fn(); const TestComponent = () => { useTimeout(callback, 1000); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); // Unmount before timeout unmount(); // Fast-forward past timeout jest.advanceTimersByTime(1500); expect(callback).not.toHaveBeenCalled(); jest.useRealTimers(); }); }); describe("useMemoryMonitor", () => { test("should track render count", () => { let renderCount = 0; const TestComponent = ({ value }) => { const { renderCount: currentRenderCount } = useMemoryMonitor("TestComponent"); renderCount = currentRenderCount; return React.createElement("div", null, value); }; const { rerender } = render( React.createElement(TestComponent, { value: 1 }) ); expect(renderCount).toBe(1); rerender(React.createElement(TestComponent, { value: 2 })); expect(renderCount).toBe(2); rerender(React.createElement(TestComponent, { value: 3 })); expect(renderCount).toBe(3); }); test("should provide memory statistics", () => { let memoryStats = null; const TestComponent = () => { const { getMemoryStats } = useMemoryMonitor("TestComponent"); React.useEffect(() => { // Simulate some memory usage setTimeout(() => { memoryStats = getMemoryStats(); }, 100); }, [getMemoryStats]); return React.createElement("div", null, "Test"); }; render(React.createElement(TestComponent)); return new Promise((resolve) => { setTimeout(() => { // Memory stats might be null in test environment // but the function should exist expect(typeof memoryStats).toBeDefined(); resolve(); }, 150); }); }); }); describe("useWeakRef", () => { test("should store and retrieve values using weak references", () => { let getValue, setValue; const TestComponent = () => { [getValue, setValue] = useWeakRef(); return React.createElement("div", null, "Test"); }; render(React.createElement(TestComponent)); const testObject = { data: "test" }; setValue(testObject); expect(getValue()).toBe(testObject); setValue(null); expect(getValue()).toBe(null); }); }); describe("useResourcePool", () => { test("should manage resource pool efficiently", () => { let resourcePool; const createResource = jest.fn(() => ({ id: Math.random() })); const resetResource = jest.fn(); const TestComponent = () => { resourcePool = useResourcePool(createResource, resetResource, 3); return React.createElement("div", null, "Test"); }; render(React.createElement(TestComponent)); // Acquire resources const resource1 = resourcePool.acquire(); const resource2 = resourcePool.acquire(); expect(createResource).toHaveBeenCalledTimes(2); expect(resourcePool.activeCount).toBe(2); expect(resourcePool.poolSize).toBe(0); // Release resources resourcePool.release(resource1); expect(resourcePool.activeCount).toBe(1); expect(resourcePool.poolSize).toBe(1); // Acquire again (should reuse) const resource3 = resourcePool.acquire(); expect(createResource).toHaveBeenCalledTimes(2); // No new creation expect(resetResource).toHaveBeenCalledTimes(1); }); }); }); describe("Memory Optimized Components", () => { describe("withMemoryManagement HOC", () => { test("should provide memory management props to wrapped component", () => { let receivedProps = {}; const TestComponent = (props) => { receivedProps = props; return React.createElement("div", null, "Test"); }; const MemoryManagedComponent = withMemoryManagement(TestComponent, { componentName: "TestComponent", }); render( React.createElement(MemoryManagedComponent, { testProp: "value" }) ); expect(receivedProps.testProp).toBe("value"); expect(typeof receivedProps.addCleanup).toBe("function"); expect(typeof receivedProps.executeAsync).toBe("function"); expect(typeof receivedProps.getMemoryStats).toBe("function"); expect(typeof receivedProps.renderCount).toBe("number"); }); }); describe("MemoryOptimizedContainer", () => { test("should display memory warnings when threshold is exceeded", () => { // Mock process.memoryUsage to return high memory usage const originalMemoryUsage = process.memoryUsage; process.memoryUsage = jest.fn(() => ({ heapUsed: 200 * 1024 * 1024, // 200MB heapTotal: 250 * 1024 * 1024, external: 10 * 1024 * 1024, rss: 300 * 1024 * 1024, })); const onMemoryWarning = jest.fn(); const { lastFrame } = render( React.createElement( MemoryOptimizedContainer, { memoryThreshold: 100 * 1024 * 1024, // 100MB threshold memoryCheckInterval: 100, // Fast check for testing onMemoryWarning, }, "Test content" ) ); // Wait for memory check return new Promise((resolve) => { setTimeout(() => { const output = lastFrame(); expect(output).toContain("Memory Warning"); process.memoryUsage = originalMemoryUsage; resolve(); }, 150); }); }); }); describe("MemoryEfficientList", () => { test("should limit cached items to prevent memory bloat", () => { const items = Array.from({ length: 200 }, (_, i) => `Item ${i}`); const renderItem = (item, index) => React.createElement("div", { key: index }, item); const { lastFrame } = render( React.createElement(MemoryEfficientList, { items, renderItem, maxCachedItems: 50, }) ); const output = lastFrame(); expect(output).toContain("Cached:"); expect(output).toContain("50"); // Should show cache limit }); }); describe("AutoCleanupComponent", () => { test("should cleanup old resources automatically", () => { jest.useFakeTimers(); let resourceUtils = {}; const TestComponent = (utils) => { resourceUtils = utils; return React.createElement("div", null, "Test"); }; render( React.createElement( AutoCleanupComponent, { cleanupInterval: 1000, maxAge: 2000, }, TestComponent ) ); // Add a resource const mockResource = { cleanup: jest.fn(), }; resourceUtils.addResource("test", mockResource); expect(resourceUtils.resourceCount).toBe(1); // Fast-forward past maxAge jest.advanceTimersByTime(3000); expect(mockResource.cleanup).toHaveBeenCalled(); expect(resourceUtils.resourceCount).toBe(0); jest.useRealTimers(); }); }); }); describe("Memory Leak Detection", () => { test("should detect potential memory leaks in component lifecycle", async () => { const components = []; // Create multiple components with potential leaks for (let i = 0; i < 10; i++) { const TestComponent = () => { const [data, setData] = React.useState([]); React.useEffect(() => { // Simulate memory leak by accumulating data const interval = setInterval(() => { setData((prev) => [...prev, new Array(1000).fill(i)]); }, 10); return () => clearInterval(interval); }, []); return React.createElement("div", null, `Component ${i}`); }; const { unmount } = render(React.createElement(TestComponent)); components.push(unmount); } // Let components run for a bit await new Promise((resolve) => setTimeout(resolve, 100)); // Unmount all components components.forEach((unmount) => unmount()); // Wait for cleanup await new Promise((resolve) => setTimeout(resolve, 100)); // In a real scenario, we would check memory usage here // For testing, we just verify that components were created and destroyed expect(components).toHaveLength(10); }); test("should properly cleanup event listeners", () => { const mockAddEventListener = jest.fn(); const mockRemoveEventListener = jest.fn(); // Mock DOM element const mockElement = { addEventListener: mockAddEventListener, removeEventListener: mockRemoveEventListener, }; const TestComponent = () => { useEventListener("click", () => {}, mockElement); return React.createElement("div", null, "Test"); }; const { unmount } = render(React.createElement(TestComponent)); expect(mockAddEventListener).toHaveBeenCalledTimes(1); expect(mockRemoveEventListener).not.toHaveBeenCalled(); unmount(); expect(mockRemoveEventListener).toHaveBeenCalledTimes(1); }); });