mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-13 06:40:33 -07:00
Fix tests to wrap all calls changing the UI with act. (#12268)
This commit is contained in:
@@ -6,7 +6,8 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { render } from 'ink-testing-library';
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import {
|
||||
useSelectionList,
|
||||
type SelectionListItem,
|
||||
@@ -67,7 +68,7 @@ describe('useSelectionList', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const renderSelectionListHook = (initialProps: {
|
||||
const renderSelectionListHook = async (initialProps: {
|
||||
items: Array<SelectionListItem<string>>;
|
||||
onSelect: (item: string) => void;
|
||||
onHighlight?: (item: string) => void;
|
||||
@@ -87,23 +88,26 @@ describe('useSelectionList', () => {
|
||||
return hookResult;
|
||||
},
|
||||
},
|
||||
rerender: (newProps: Partial<typeof initialProps>) =>
|
||||
rerender(<TestComponent {...initialProps} {...newProps} />),
|
||||
unmount,
|
||||
rerender: async (newProps: Partial<typeof initialProps>) => {
|
||||
rerender(<TestComponent {...initialProps} {...newProps} />);
|
||||
},
|
||||
unmount: async () => {
|
||||
unmount();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('Initialization', () => {
|
||||
it('should initialize with the default index (0) if enabled', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should initialize with the default index (0) if enabled', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
|
||||
it('should initialize with the provided initialIndex if enabled', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should initialize with the provided initialIndex if enabled', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: 2,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -111,16 +115,16 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle an empty list gracefully', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should handle an empty list gracefully', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: [],
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
|
||||
it('should find the next enabled item (downwards) if initialIndex is disabled', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should find the next enabled item (downwards) if initialIndex is disabled', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: 1,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -128,13 +132,13 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
|
||||
it('should wrap around to find the next enabled item if initialIndex is disabled', () => {
|
||||
it('should wrap around to find the next enabled item if initialIndex is disabled', async () => {
|
||||
const wrappingItems = [
|
||||
{ value: 'A', key: 'A' },
|
||||
{ value: 'B', disabled: true, key: 'B' },
|
||||
{ value: 'C', disabled: true, key: 'C' },
|
||||
];
|
||||
const { result } = renderSelectionListHook({
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: wrappingItems,
|
||||
initialIndex: 2,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -142,15 +146,15 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
|
||||
it('should default to 0 if initialIndex is out of bounds', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should default to 0 if initialIndex is out of bounds', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: 10,
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
|
||||
const { result: resultNeg } = renderSelectionListHook({
|
||||
const { result: resultNeg } = await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: -1,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -158,12 +162,12 @@ describe('useSelectionList', () => {
|
||||
expect(resultNeg.current.activeIndex).toBe(0);
|
||||
});
|
||||
|
||||
it('should stick to the initial index if all items are disabled', () => {
|
||||
it('should stick to the initial index if all items are disabled', async () => {
|
||||
const allDisabled = [
|
||||
{ value: 'A', disabled: true, key: 'A' },
|
||||
{ value: 'B', disabled: true, key: 'B' },
|
||||
];
|
||||
const { result } = renderSelectionListHook({
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: allDisabled,
|
||||
initialIndex: 1,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -173,8 +177,8 @@ describe('useSelectionList', () => {
|
||||
});
|
||||
|
||||
describe('Keyboard Navigation (Up/Down/J/K)', () => {
|
||||
it('should move down with "j" and "down" keys, skipping disabled items', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should move down with "j" and "down" keys, skipping disabled items', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
@@ -185,8 +189,8 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(3);
|
||||
});
|
||||
|
||||
it('should move up with "k" and "up" keys, skipping disabled items', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should move up with "k" and "up" keys, skipping disabled items', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: 3,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -198,8 +202,8 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
|
||||
it('should wrap navigation correctly', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should wrap navigation correctly', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: items.length - 1,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -212,8 +216,8 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(3);
|
||||
});
|
||||
|
||||
it('should call onHighlight when index changes', () => {
|
||||
renderSelectionListHook({
|
||||
it('should call onHighlight when index changes', async () => {
|
||||
await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -223,9 +227,9 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnHighlight).toHaveBeenCalledWith('C');
|
||||
});
|
||||
|
||||
it('should not move or call onHighlight if navigation results in the same index (e.g., single item)', () => {
|
||||
it('should not move or call onHighlight if navigation results in the same index (e.g., single item)', async () => {
|
||||
const singleItem = [{ value: 'A', key: 'A' }];
|
||||
const { result } = renderSelectionListHook({
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: singleItem,
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -235,12 +239,12 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnHighlight).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not move or call onHighlight if all items are disabled', () => {
|
||||
it('should not move or call onHighlight if all items are disabled', async () => {
|
||||
const allDisabled = [
|
||||
{ value: 'A', disabled: true, key: 'A' },
|
||||
{ value: 'B', disabled: true, key: 'B' },
|
||||
];
|
||||
const { result } = renderSelectionListHook({
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: allDisabled,
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -253,8 +257,8 @@ describe('useSelectionList', () => {
|
||||
});
|
||||
|
||||
describe('Selection (Enter)', () => {
|
||||
it('should call onSelect when "return" is pressed on enabled item', () => {
|
||||
renderSelectionListHook({
|
||||
it('should call onSelect when "return" is pressed on enabled item', async () => {
|
||||
await renderSelectionListHook({
|
||||
items,
|
||||
initialIndex: 2,
|
||||
onSelect: mockOnSelect,
|
||||
@@ -264,8 +268,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('C');
|
||||
});
|
||||
|
||||
it('should not call onSelect if the active item is disabled', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should not call onSelect if the active item is disabled', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
@@ -278,8 +282,8 @@ describe('useSelectionList', () => {
|
||||
});
|
||||
|
||||
describe('Keyboard Navigation Robustness (Rapid Input)', () => {
|
||||
it('should handle rapid navigation and selection robustly (avoiding stale state)', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should handle rapid navigation and selection robustly (avoiding stale state)', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items, // A, B(disabled), C, D. Initial index 0 (A).
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -326,8 +330,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).not.toHaveBeenCalledWith('A');
|
||||
});
|
||||
|
||||
it('should handle ultra-rapid input (multiple presses in single act) without stale state', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should handle ultra-rapid input (multiple presses in single act) without stale state', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items, // A, B(disabled), C, D. Initial index 0 (A).
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -366,8 +370,8 @@ describe('useSelectionList', () => {
|
||||
});
|
||||
|
||||
describe('Focus Management (isFocused)', () => {
|
||||
it('should activate the keypress handler when focused (default) and items exist', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should activate the keypress handler when focused (default) and items exist', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
@@ -376,8 +380,8 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
|
||||
it('should not activate the keypress handler when isFocused is false', () => {
|
||||
renderSelectionListHook({
|
||||
it('should not activate the keypress handler when isFocused is false', async () => {
|
||||
await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
isFocused: false,
|
||||
@@ -386,8 +390,8 @@ describe('useSelectionList', () => {
|
||||
expect(() => pressKey('down')).toThrow(/keypress handler is not active/);
|
||||
});
|
||||
|
||||
it('should not activate the keypress handler when items list is empty', () => {
|
||||
renderSelectionListHook({
|
||||
it('should not activate the keypress handler when items list is empty', async () => {
|
||||
await renderSelectionListHook({
|
||||
items: [],
|
||||
onSelect: mockOnSelect,
|
||||
isFocused: true,
|
||||
@@ -396,8 +400,8 @@ describe('useSelectionList', () => {
|
||||
expect(() => pressKey('down')).toThrow(/keypress handler is not active/);
|
||||
});
|
||||
|
||||
it('should activate/deactivate when isFocused prop changes', () => {
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
it('should activate/deactivate when isFocused prop changes', async () => {
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
isFocused: false,
|
||||
@@ -405,12 +409,12 @@ describe('useSelectionList', () => {
|
||||
|
||||
expect(activeKeypressHandler).toBeNull();
|
||||
|
||||
rerender({ isFocused: true });
|
||||
await rerender({ isFocused: true });
|
||||
expect(activeKeypressHandler).not.toBeNull();
|
||||
pressKey('down');
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
|
||||
rerender({ isFocused: false });
|
||||
await rerender({ isFocused: false });
|
||||
expect(activeKeypressHandler).toBeNull();
|
||||
expect(() => pressKey('down')).toThrow(/keypress handler is not active/);
|
||||
});
|
||||
@@ -433,8 +437,8 @@ describe('useSelectionList', () => {
|
||||
|
||||
const pressNumber = (num: string) => pressKey(num, num);
|
||||
|
||||
it('should not respond to numbers if showNumbers is false (default)', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should not respond to numbers if showNumbers is false (default)', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: shortList,
|
||||
onSelect: mockOnSelect,
|
||||
});
|
||||
@@ -443,8 +447,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should select item immediately if the number cannot be extended (unambiguous)', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should select item immediately if the number cannot be extended (unambiguous)', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: shortList,
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -459,8 +463,8 @@ describe('useSelectionList', () => {
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('should highlight and wait for timeout if the number can be extended (ambiguous)', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should highlight and wait for timeout if the number can be extended (ambiguous)', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: longList,
|
||||
initialIndex: 1, // Start at index 1 so pressing "1" (index 0) causes a change
|
||||
onSelect: mockOnSelect,
|
||||
@@ -484,8 +488,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('Item 1');
|
||||
});
|
||||
|
||||
it('should handle multi-digit input correctly', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should handle multi-digit input correctly', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: longList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -502,8 +506,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('Item 12');
|
||||
});
|
||||
|
||||
it('should reset buffer if input becomes invalid (out of bounds)', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should reset buffer if input becomes invalid (out of bounds)', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: shortList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -519,8 +523,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('C');
|
||||
});
|
||||
|
||||
it('should allow "0" as subsequent digit, but ignore as first digit', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should allow "0" as subsequent digit, but ignore as first digit', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: longList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -540,8 +544,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('Item 10');
|
||||
});
|
||||
|
||||
it('should clear the initial "0" input after timeout', () => {
|
||||
renderSelectionListHook({
|
||||
it('should clear the initial "0" input after timeout', async () => {
|
||||
await renderSelectionListHook({
|
||||
items: longList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -557,8 +561,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('Item 1');
|
||||
});
|
||||
|
||||
it('should highlight but not select a disabled item (immediate selection case)', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should highlight but not select a disabled item (immediate selection case)', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: shortList, // B (index 1, number 2) is disabled
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
@@ -574,14 +578,14 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should highlight but not select a disabled item (timeout case)', () => {
|
||||
it('should highlight but not select a disabled item (timeout case)', async () => {
|
||||
// Create a list where the ambiguous prefix points to a disabled item
|
||||
const disabledAmbiguousList = [
|
||||
{ value: 'Item 1 Disabled', disabled: true, key: 'Item 1 Disabled' },
|
||||
...longList.slice(1),
|
||||
];
|
||||
|
||||
const { result } = renderSelectionListHook({
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: disabledAmbiguousList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -599,8 +603,8 @@ describe('useSelectionList', () => {
|
||||
expect(mockOnSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should clear the number buffer if a non-numeric key (e.g., navigation) is pressed', () => {
|
||||
const { result } = renderSelectionListHook({
|
||||
it('should clear the number buffer if a non-numeric key (e.g., navigation) is pressed', async () => {
|
||||
const { result } = await renderSelectionListHook({
|
||||
items: longList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -619,8 +623,8 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
|
||||
it('should clear the number buffer if "return" is pressed', () => {
|
||||
renderSelectionListHook({
|
||||
it('should clear the number buffer if "return" is pressed', async () => {
|
||||
await renderSelectionListHook({
|
||||
items: longList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -642,20 +646,20 @@ describe('useSelectionList', () => {
|
||||
|
||||
describe('Reactivity (Dynamic Updates)', () => {
|
||||
it('should update activeIndex when initialIndex prop changes', async () => {
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 0,
|
||||
});
|
||||
|
||||
rerender({ initialIndex: 2 });
|
||||
await vi.waitFor(() => {
|
||||
await rerender({ initialIndex: 2 });
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect a new initialIndex even after user interaction', async () => {
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 0,
|
||||
@@ -666,30 +670,30 @@ describe('useSelectionList', () => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
|
||||
// The component re-renders with a new initial index
|
||||
rerender({ initialIndex: 3 });
|
||||
await rerender({ initialIndex: 3 });
|
||||
|
||||
// The hook should now respect the new initial index
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate index when initialIndex prop changes to a disabled item', async () => {
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
items,
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 0,
|
||||
});
|
||||
|
||||
rerender({ initialIndex: 1 });
|
||||
await rerender({ initialIndex: 1 });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should adjust activeIndex if items change and the initialIndex is now out of bounds', async () => {
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 3,
|
||||
items,
|
||||
@@ -701,10 +705,10 @@ describe('useSelectionList', () => {
|
||||
{ value: 'X', key: 'X' },
|
||||
{ value: 'Y', key: 'Y' },
|
||||
];
|
||||
rerender({ items: shorterItems }); // Length 2
|
||||
await rerender({ items: shorterItems }); // Length 2
|
||||
|
||||
// The useEffect syncs based on the initialIndex (3) which is now out of bounds. It defaults to 0.
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -715,7 +719,7 @@ describe('useSelectionList', () => {
|
||||
{ value: 'B', key: 'B' },
|
||||
{ value: 'C', key: 'C' },
|
||||
];
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 1,
|
||||
items: initialItems,
|
||||
@@ -728,22 +732,22 @@ describe('useSelectionList', () => {
|
||||
{ value: 'B', disabled: true, key: 'B' },
|
||||
{ value: 'C', key: 'C' },
|
||||
];
|
||||
rerender({ items: newItems });
|
||||
await rerender({ items: newItems });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset to 0 if items change to an empty list', async () => {
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 2,
|
||||
items,
|
||||
});
|
||||
|
||||
rerender({ items: [] });
|
||||
await vi.waitFor(() => {
|
||||
await rerender({ items: [] });
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -756,7 +760,7 @@ describe('useSelectionList', () => {
|
||||
{ value: 'D', key: 'D' },
|
||||
];
|
||||
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
initialIndex: 2,
|
||||
@@ -780,10 +784,10 @@ describe('useSelectionList', () => {
|
||||
{ value: 'D', key: 'D' },
|
||||
];
|
||||
|
||||
rerender({ items: newItems });
|
||||
await rerender({ items: newItems });
|
||||
|
||||
// Active index should remain the same since items are deeply equal
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(3);
|
||||
});
|
||||
// onHighlight should NOT be called since the index didn't change
|
||||
@@ -798,7 +802,7 @@ describe('useSelectionList', () => {
|
||||
{ value: 'D', key: 'D' },
|
||||
];
|
||||
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
onHighlight: mockOnHighlight,
|
||||
initialIndex: 3,
|
||||
@@ -815,10 +819,10 @@ describe('useSelectionList', () => {
|
||||
{ value: 'Z', key: 'Z' },
|
||||
];
|
||||
|
||||
rerender({ items: newItems });
|
||||
await rerender({ items: newItems });
|
||||
|
||||
// Active index should update based on initialIndex and new items
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -830,7 +834,7 @@ describe('useSelectionList', () => {
|
||||
{ value: 'C', key: 'C' },
|
||||
];
|
||||
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
initialIndex: 1,
|
||||
items: initialItems,
|
||||
@@ -845,10 +849,10 @@ describe('useSelectionList', () => {
|
||||
{ value: 'C', key: 'C' },
|
||||
];
|
||||
|
||||
rerender({ items: newItems });
|
||||
await rerender({ items: newItems });
|
||||
|
||||
// Should find next valid index since current became disabled
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -860,7 +864,7 @@ describe('useSelectionList', () => {
|
||||
{ value: 'C', key: 'C' },
|
||||
];
|
||||
|
||||
const { result, rerender } = renderSelectionListHook({
|
||||
const { result, rerender } = await renderSelectionListHook({
|
||||
onSelect: mockOnSelect,
|
||||
items: initialItems,
|
||||
});
|
||||
@@ -875,14 +879,14 @@ describe('useSelectionList', () => {
|
||||
{ value: 'C', key: 'C' },
|
||||
];
|
||||
|
||||
rerender({ items: newItems });
|
||||
await rerender({ items: newItems });
|
||||
|
||||
await vi.waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(result.current.activeIndex).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not re-initialize when items have identical keys but are different objects', () => {
|
||||
it('should not re-initialize when items have identical keys but are different objects', async () => {
|
||||
const initialItems = [
|
||||
{ value: 'A', key: 'A' },
|
||||
{ value: 'B', key: 'B' },
|
||||
@@ -890,7 +894,7 @@ describe('useSelectionList', () => {
|
||||
|
||||
let renderCount = 0;
|
||||
|
||||
const renderHookWithCount = (initialProps: {
|
||||
const renderHookWithCount = async (initialProps: {
|
||||
items: Array<SelectionListItem<string>>;
|
||||
}) => {
|
||||
function TestComponent(props: typeof initialProps) {
|
||||
@@ -904,12 +908,13 @@ describe('useSelectionList', () => {
|
||||
}
|
||||
const { rerender } = render(<TestComponent {...initialProps} />);
|
||||
return {
|
||||
rerender: (newProps: Partial<typeof initialProps>) =>
|
||||
rerender(<TestComponent {...initialProps} {...newProps} />),
|
||||
rerender: async (newProps: Partial<typeof initialProps>) => {
|
||||
rerender(<TestComponent {...initialProps} {...newProps} />);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const { rerender } = renderHookWithCount({ items: initialItems });
|
||||
const { rerender } = await renderHookWithCount({ items: initialItems });
|
||||
|
||||
// Initial render
|
||||
expect(renderCount).toBe(1);
|
||||
@@ -920,7 +925,7 @@ describe('useSelectionList', () => {
|
||||
{ value: 'B', key: 'B' },
|
||||
];
|
||||
|
||||
rerender({ items: newItems });
|
||||
await rerender({ items: newItems });
|
||||
expect(renderCount).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -934,13 +939,13 @@ describe('useSelectionList', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should clear timeout on unmount when timer is active', () => {
|
||||
it('should clear timeout on unmount when timer is active', async () => {
|
||||
const longList: Array<SelectionListItem<string>> = Array.from(
|
||||
{ length: 15 },
|
||||
(_, i) => ({ value: `Item ${i + 1}`, key: `Item ${i + 1}` }),
|
||||
);
|
||||
|
||||
const { unmount } = renderSelectionListHook({
|
||||
const { unmount } = await renderSelectionListHook({
|
||||
items: longList,
|
||||
onSelect: mockOnSelect,
|
||||
showNumbers: true,
|
||||
@@ -955,7 +960,7 @@ describe('useSelectionList', () => {
|
||||
});
|
||||
expect(mockOnSelect).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
await unmount();
|
||||
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user