266 lines
6.6 KiB
JavaScript
266 lines
6.6 KiB
JavaScript
const React = require("react");
|
|
const { renderHook, act } = require("@testing-library/react");
|
|
const {
|
|
usePerformanceOptimization,
|
|
useVirtualScrolling,
|
|
useLazyLoading,
|
|
useDebouncedSearch,
|
|
} = require("../../../src/tui/hooks/usePerformanceOptimization.js");
|
|
|
|
// Mock timers for testing
|
|
jest.useFakeTimers();
|
|
|
|
describe("usePerformanceOptimization", () => {
|
|
afterEach(() => {
|
|
jest.clearAllTimers();
|
|
});
|
|
|
|
it("should provide performance optimization functions", () => {
|
|
const { result } = renderHook(() =>
|
|
usePerformanceOptimization("test-component")
|
|
);
|
|
|
|
expect(result.current).toHaveProperty("createDebouncedFunction");
|
|
expect(result.current).toHaveProperty("createThrottledFunction");
|
|
expect(result.current).toHaveProperty("createMemoizedFunction");
|
|
expect(result.current).toHaveProperty("createVirtualScrolling");
|
|
expect(result.current).toHaveProperty("createLazyLoading");
|
|
expect(result.current).toHaveProperty("registerEventListener");
|
|
expect(result.current).toHaveProperty("optimizeRender");
|
|
expect(result.current).toHaveProperty("createBatchedUpdate");
|
|
expect(result.current).toHaveProperty("forceCleanup");
|
|
});
|
|
|
|
it("should create debounced functions", () => {
|
|
const { result } = renderHook(() =>
|
|
usePerformanceOptimization("test-component")
|
|
);
|
|
|
|
let callCount = 0;
|
|
const testFunction = () => {
|
|
callCount++;
|
|
};
|
|
|
|
act(() => {
|
|
const debouncedFn = result.current.createDebouncedFunction(
|
|
testFunction,
|
|
100
|
|
);
|
|
debouncedFn();
|
|
debouncedFn();
|
|
debouncedFn();
|
|
});
|
|
|
|
expect(callCount).toBe(0);
|
|
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
|
|
expect(callCount).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("useVirtualScrolling", () => {
|
|
it("should provide virtual scrolling data", () => {
|
|
const items = Array.from({ length: 100 }, (_, i) => ({
|
|
id: i,
|
|
name: `Item ${i}`,
|
|
}));
|
|
const options = { itemHeight: 30, containerHeight: 300 };
|
|
|
|
const { result } = renderHook(() => useVirtualScrolling(items, options));
|
|
|
|
expect(result.current).toHaveProperty("visibleItems");
|
|
expect(result.current).toHaveProperty("totalHeight");
|
|
expect(result.current).toHaveProperty("startIndex");
|
|
expect(result.current).toHaveProperty("endIndex");
|
|
expect(result.current).toHaveProperty("handleScroll");
|
|
expect(result.current.totalHeight).toBe(3000); // 100 * 30
|
|
});
|
|
|
|
it("should handle scroll updates", () => {
|
|
const items = Array.from({ length: 100 }, (_, i) => ({
|
|
id: i,
|
|
name: `Item ${i}`,
|
|
}));
|
|
const options = { itemHeight: 30, containerHeight: 300 };
|
|
|
|
const { result } = renderHook(() => useVirtualScrolling(items, options));
|
|
|
|
act(() => {
|
|
result.current.handleScroll(150);
|
|
});
|
|
|
|
expect(result.current.scrollTop).toBe(150);
|
|
expect(result.current.startIndex).toBe(5); // 150 / 30
|
|
});
|
|
});
|
|
|
|
describe("useLazyLoading", () => {
|
|
it("should load data lazily", async () => {
|
|
const mockLoadFunction = jest.fn().mockResolvedValue({
|
|
items: [
|
|
{ id: 1, name: "Item 1" },
|
|
{ id: 2, name: "Item 2" },
|
|
],
|
|
hasMore: true,
|
|
});
|
|
|
|
const { result, waitForNextUpdate } = renderHook(() =>
|
|
useLazyLoading(mockLoadFunction, { pageSize: 2 })
|
|
);
|
|
|
|
// Initial state
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.items).toEqual([]);
|
|
|
|
// Wait for initial load
|
|
await waitForNextUpdate();
|
|
|
|
expect(result.current.loading).toBe(false);
|
|
expect(result.current.items).toHaveLength(2);
|
|
expect(result.current.hasMore).toBe(true);
|
|
expect(mockLoadFunction).toHaveBeenCalledWith({
|
|
page: 0,
|
|
pageSize: 2,
|
|
offset: 0,
|
|
});
|
|
});
|
|
|
|
it("should load more data", async () => {
|
|
const mockLoadFunction = jest
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
items: [{ id: 1, name: "Item 1" }],
|
|
hasMore: true,
|
|
})
|
|
.mockResolvedValueOnce({
|
|
items: [{ id: 2, name: "Item 2" }],
|
|
hasMore: false,
|
|
});
|
|
|
|
const { result, waitForNextUpdate } = renderHook(() =>
|
|
useLazyLoading(mockLoadFunction, { pageSize: 1, enablePreloading: false })
|
|
);
|
|
|
|
// Wait for initial load
|
|
await waitForNextUpdate();
|
|
|
|
expect(result.current.items).toHaveLength(1);
|
|
|
|
// Load more
|
|
act(() => {
|
|
result.current.loadMore();
|
|
});
|
|
|
|
await waitForNextUpdate();
|
|
|
|
expect(result.current.items).toHaveLength(2);
|
|
expect(result.current.hasMore).toBe(false);
|
|
});
|
|
|
|
it("should handle reload", async () => {
|
|
const mockLoadFunction = jest.fn().mockResolvedValue({
|
|
items: [{ id: 1, name: "Item 1" }],
|
|
hasMore: false,
|
|
});
|
|
|
|
const { result, waitForNextUpdate } = renderHook(() =>
|
|
useLazyLoading(mockLoadFunction)
|
|
);
|
|
|
|
// Wait for initial load
|
|
await waitForNextUpdate();
|
|
|
|
expect(mockLoadFunction).toHaveBeenCalledTimes(1);
|
|
|
|
// Reload
|
|
act(() => {
|
|
result.current.reload();
|
|
});
|
|
|
|
await waitForNextUpdate();
|
|
|
|
expect(mockLoadFunction).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe("useDebouncedSearch", () => {
|
|
it("should debounce search queries", async () => {
|
|
const mockSearchFunction = jest
|
|
.fn()
|
|
.mockResolvedValue([{ id: 1, name: "Test Result" }]);
|
|
|
|
const { result, waitForNextUpdate } = renderHook(() =>
|
|
useDebouncedSearch(mockSearchFunction, 100)
|
|
);
|
|
|
|
// Update query multiple times rapidly
|
|
act(() => {
|
|
result.current.updateQuery("t");
|
|
result.current.updateQuery("te");
|
|
result.current.updateQuery("tes");
|
|
result.current.updateQuery("test");
|
|
});
|
|
|
|
expect(result.current.query).toBe("test");
|
|
expect(result.current.loading).toBe(false); // Should not be loading yet due to debounce
|
|
|
|
// Advance timers to trigger debounced search
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
|
|
await waitForNextUpdate();
|
|
|
|
expect(mockSearchFunction).toHaveBeenCalledTimes(1);
|
|
expect(mockSearchFunction).toHaveBeenCalledWith("test");
|
|
expect(result.current.results).toHaveLength(1);
|
|
});
|
|
|
|
it("should clear search", () => {
|
|
const mockSearchFunction = jest.fn().mockResolvedValue([]);
|
|
|
|
const { result } = renderHook(() => useDebouncedSearch(mockSearchFunction));
|
|
|
|
act(() => {
|
|
result.current.updateQuery("test");
|
|
});
|
|
|
|
expect(result.current.query).toBe("test");
|
|
|
|
act(() => {
|
|
result.current.clearSearch();
|
|
});
|
|
|
|
expect(result.current.query).toBe("");
|
|
expect(result.current.results).toEqual([]);
|
|
});
|
|
|
|
it("should handle empty queries", async () => {
|
|
const mockSearchFunction = jest.fn().mockResolvedValue([]);
|
|
|
|
const { result } = renderHook(() =>
|
|
useLazyLoading(() =>
|
|
Promise.resolve({ items: [{ id: 1 }], hasMore: false })
|
|
)
|
|
);
|
|
|
|
const { result: searchResult } = renderHook(() =>
|
|
useDebouncedSearch(mockSearchFunction)
|
|
);
|
|
|
|
act(() => {
|
|
searchResult.current.updateQuery(" "); // Whitespace only
|
|
});
|
|
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
|
|
expect(mockSearchFunction).not.toHaveBeenCalled();
|
|
expect(searchResult.current.results).toEqual([]);
|
|
});
|
|
});
|