Files
PriceUpdaterAppv2/tests/tui/performance/renderingPerformance.test.js

447 lines
13 KiB
JavaScript

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);
});
});