Just a whole lot of crap
This commit is contained in:
446
tests/tui/performance/renderingPerformance.test.js
Normal file
446
tests/tui/performance/renderingPerformance.test.js
Normal file
@@ -0,0 +1,446 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user