@google/gemini-cli 0.12.0-nightly.20251023.c4c0c0d1 → 0.12.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/package.json +3 -3
- package/dist/src/commands/extensions/disable.d.ts +1 -1
- package/dist/src/commands/extensions/disable.js +5 -4
- package/dist/src/commands/extensions/disable.js.map +1 -1
- package/dist/src/commands/extensions/enable.d.ts +1 -1
- package/dist/src/commands/extensions/enable.js +3 -2
- package/dist/src/commands/extensions/enable.js.map +1 -1
- package/dist/src/commands/extensions/install.js +2 -1
- package/dist/src/commands/extensions/install.js.map +1 -1
- package/dist/src/commands/extensions/install.test.js +1 -0
- package/dist/src/commands/extensions/install.test.js.map +1 -1
- package/dist/src/commands/extensions/link.js +2 -1
- package/dist/src/commands/extensions/link.js.map +1 -1
- package/dist/src/commands/extensions/list.js +2 -2
- package/dist/src/commands/extensions/list.js.map +1 -1
- package/dist/src/commands/extensions/uninstall.js +2 -1
- package/dist/src/commands/extensions/uninstall.js.map +1 -1
- package/dist/src/commands/extensions/update.js +2 -2
- package/dist/src/commands/extensions/update.js.map +1 -1
- package/dist/src/commands/mcp/list.js +2 -2
- package/dist/src/commands/mcp/list.js.map +1 -1
- package/dist/src/config/config.d.ts +6 -3
- package/dist/src/config/config.js +56 -11
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +208 -175
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.d.ts +23 -10
- package/dist/src/config/extension-manager.js +90 -64
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +183 -76
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
- package/dist/src/config/extensions/extensionEnablement.js +3 -2
- package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
- package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.d.ts +3 -3
- package/dist/src/config/extensions/extensionSettings.js +74 -24
- package/dist/src/config/extensions/extensionSettings.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.test.js +145 -24
- package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
- package/dist/src/config/extensions/github.js +3 -3
- package/dist/src/config/extensions/github.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +1 -1
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.d.ts +1 -1
- package/dist/src/config/extensions/github_fetch.js +13 -1
- package/dist/src/config/extensions/github_fetch.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
- package/dist/src/config/extensions/github_fetch.test.js +169 -0
- package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
- package/dist/src/config/extensions/update.js +7 -6
- package/dist/src/config/extensions/update.js.map +1 -1
- package/dist/src/config/extensions/update.test.js +54 -30
- package/dist/src/config/extensions/update.test.js.map +1 -1
- package/dist/src/config/keyBindings.js +1 -1
- package/dist/src/config/keyBindings.js.map +1 -1
- package/dist/src/config/policies/read-only.toml +56 -0
- package/dist/src/config/policies/write.toml +63 -0
- package/dist/src/config/policies/yolo.toml +31 -0
- package/dist/src/config/policy-engine.integration.test.js +41 -38
- package/dist/src/config/policy-engine.integration.test.js.map +1 -1
- package/dist/src/config/policy-toml-loader.d.ts +46 -0
- package/dist/src/config/policy-toml-loader.js +314 -0
- package/dist/src/config/policy-toml-loader.js.map +1 -0
- package/dist/src/config/policy-toml-loader.test.d.ts +6 -0
- package/dist/src/config/policy-toml-loader.test.js +626 -0
- package/dist/src/config/policy-toml-loader.test.js.map +1 -0
- package/dist/src/config/policy.d.ts +9 -2
- package/dist/src/config/policy.js +139 -110
- package/dist/src/config/policy.js.map +1 -1
- package/dist/src/config/policy.test.js +780 -82
- package/dist/src/config/policy.test.js.map +1 -1
- package/dist/src/config/settings.test.js +6 -6
- package/dist/src/config/settings.test.js.map +1 -1
- package/dist/src/core/initializer.js +2 -1
- package/dist/src/core/initializer.js.map +1 -1
- package/dist/src/gemini.js +6 -17
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +27 -2
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +16 -4
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/nonInteractiveCli.test.js +67 -12
- package/dist/src/nonInteractiveCli.test.js.map +1 -1
- package/dist/src/test-utils/render.d.ts +12 -0
- package/dist/src/test-utils/render.js +28 -1
- package/dist/src/test-utils/render.js.map +1 -1
- package/dist/src/test-utils/render.test.d.ts +6 -0
- package/dist/src/test-utils/render.test.js +54 -0
- package/dist/src/test-utils/render.test.js.map +1 -0
- package/dist/src/ui/AppContainer.js +29 -23
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +8 -0
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/commands/directoryCommand.js +1 -1
- package/dist/src/ui/commands/directoryCommand.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.js +45 -1
- package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.test.js +64 -1
- package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.js +1 -1
- package/dist/src/ui/commands/memoryCommand.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.test.js +3 -1
- package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
- package/dist/src/ui/components/ConsoleSummaryDisplay.js +1 -1
- package/dist/src/ui/components/ConsoleSummaryDisplay.js.map +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
- package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
- package/dist/src/ui/components/FolderTrustDialog.test.js +4 -4
- package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/Footer.js +1 -1
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/Footer.test.js +24 -0
- package/dist/src/ui/components/Footer.test.js.map +1 -1
- package/dist/src/ui/components/Help.test.js +0 -1
- package/dist/src/ui/components/Help.test.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +442 -342
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +5 -5
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +11 -12
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.test.js +13 -14
- package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
- package/dist/src/ui/components/ThemeDialog.test.js +1 -2
- package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
- package/dist/src/ui/components/shared/BaseSelectionList.test.js +11 -12
- package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.test.js +2 -1
- package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionsList.d.ts +1 -1
- package/dist/src/ui/components/views/ExtensionsList.js +4 -1
- package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
- package/dist/src/ui/contexts/KeypressContext.js +114 -64
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +166 -482
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/SessionContext.test.js +27 -13
- package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
- package/dist/src/ui/hooks/atCommandProcessor.js +2 -2
- package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
- package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
- package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/useAtCompletion.test.js +32 -23
- package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +2 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useCommandCompletion.test.js +79 -78
- package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
- package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
- package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
- package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.d.ts +1 -2
- package/dist/src/ui/hooks/useExtensionUpdates.js +4 -2
- package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.test.js +37 -26
- package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
- package/dist/src/ui/hooks/useFlickerDetector.test.js +9 -5
- package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
- package/dist/src/ui/hooks/useFocus.test.js +25 -9
- package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.test.js +45 -22
- package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +56 -19
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +155 -74
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.test.js +29 -16
- package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
- package/dist/src/ui/hooks/useHistoryManager.test.js +2 -1
- package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
- package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
- package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistory.test.js +2 -1
- package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js +2 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +94 -113
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
- package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
- package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
- package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
- package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
- package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +2 -2
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.js +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.test.js +83 -110
- package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
- package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
- package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
- package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js +33 -40
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +37 -26
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.js +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +2 -2
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/hooks/useShellHistory.test.js +40 -16
- package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useSlashCompletion.test.js +54 -49
- package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useTimer.test.js +43 -14
- package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +163 -74
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/vim.test.js +251 -356
- package/dist/src/ui/hooks/vim.test.js.map +1 -1
- package/dist/src/ui/keyMatchers.test.js +3 -3
- package/dist/src/ui/keyMatchers.test.js.map +1 -1
- package/dist/src/ui/utils/textOutput.d.ts +25 -0
- package/dist/src/ui/utils/textOutput.js +49 -0
- package/dist/src/ui/utils/textOutput.js.map +1 -0
- package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
- package/dist/src/ui/utils/textOutput.test.js +79 -0
- package/dist/src/ui/utils/textOutput.test.js.map +1 -0
- package/dist/src/ui/utils/updateCheck.d.ts +7 -1
- package/dist/src/ui/utils/updateCheck.js +27 -26
- package/dist/src/ui/utils/updateCheck.js.map +1 -1
- package/dist/src/ui/utils/updateCheck.test.js +19 -49
- package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.js +9 -3
- package/dist/src/utils/handleAutoUpdate.js.map +1 -1
- package/dist/src/zed-integration/zedIntegration.d.ts +2 -2
- package/dist/src/zed-integration/zedIntegration.js +9 -16
- package/dist/src/zed-integration/zedIntegration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
/**
|
|
2
3
|
* @license
|
|
3
4
|
* Copyright 2025 Google LLC
|
|
4
5
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
6
|
*/
|
|
6
7
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import {
|
|
8
|
+
import { act } from 'react';
|
|
9
|
+
import { render } from 'ink-testing-library';
|
|
8
10
|
import { useSelectionList, } from './useSelectionList.js';
|
|
9
11
|
import { useKeypress } from './useKeypress.js';
|
|
10
12
|
vi.mock('./useKeypress.js');
|
|
@@ -49,29 +51,52 @@ describe('useSelectionList', () => {
|
|
|
49
51
|
}
|
|
50
52
|
});
|
|
51
53
|
};
|
|
54
|
+
const renderSelectionListHook = (initialProps) => {
|
|
55
|
+
let hookResult;
|
|
56
|
+
function TestComponent(props) {
|
|
57
|
+
hookResult = useSelectionList(props);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const { rerender, unmount } = render(_jsx(TestComponent, { ...initialProps }));
|
|
61
|
+
return {
|
|
62
|
+
result: {
|
|
63
|
+
get current() {
|
|
64
|
+
return hookResult;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
rerender: (newProps) => rerender(_jsx(TestComponent, { ...initialProps, ...newProps })),
|
|
68
|
+
unmount,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
52
71
|
describe('Initialization', () => {
|
|
53
72
|
it('should initialize with the default index (0) if enabled', () => {
|
|
54
|
-
const { result } =
|
|
73
|
+
const { result } = renderSelectionListHook({
|
|
74
|
+
items,
|
|
75
|
+
onSelect: mockOnSelect,
|
|
76
|
+
});
|
|
55
77
|
expect(result.current.activeIndex).toBe(0);
|
|
56
78
|
});
|
|
57
79
|
it('should initialize with the provided initialIndex if enabled', () => {
|
|
58
|
-
const { result } =
|
|
80
|
+
const { result } = renderSelectionListHook({
|
|
59
81
|
items,
|
|
60
82
|
initialIndex: 2,
|
|
61
83
|
onSelect: mockOnSelect,
|
|
62
|
-
})
|
|
84
|
+
});
|
|
63
85
|
expect(result.current.activeIndex).toBe(2);
|
|
64
86
|
});
|
|
65
87
|
it('should handle an empty list gracefully', () => {
|
|
66
|
-
const { result } =
|
|
88
|
+
const { result } = renderSelectionListHook({
|
|
89
|
+
items: [],
|
|
90
|
+
onSelect: mockOnSelect,
|
|
91
|
+
});
|
|
67
92
|
expect(result.current.activeIndex).toBe(0);
|
|
68
93
|
});
|
|
69
94
|
it('should find the next enabled item (downwards) if initialIndex is disabled', () => {
|
|
70
|
-
const { result } =
|
|
95
|
+
const { result } = renderSelectionListHook({
|
|
71
96
|
items,
|
|
72
97
|
initialIndex: 1,
|
|
73
98
|
onSelect: mockOnSelect,
|
|
74
|
-
})
|
|
99
|
+
});
|
|
75
100
|
expect(result.current.activeIndex).toBe(2);
|
|
76
101
|
});
|
|
77
102
|
it('should wrap around to find the next enabled item if initialIndex is disabled', () => {
|
|
@@ -80,25 +105,25 @@ describe('useSelectionList', () => {
|
|
|
80
105
|
{ value: 'B', disabled: true, key: 'B' },
|
|
81
106
|
{ value: 'C', disabled: true, key: 'C' },
|
|
82
107
|
];
|
|
83
|
-
const { result } =
|
|
108
|
+
const { result } = renderSelectionListHook({
|
|
84
109
|
items: wrappingItems,
|
|
85
110
|
initialIndex: 2,
|
|
86
111
|
onSelect: mockOnSelect,
|
|
87
|
-
})
|
|
112
|
+
});
|
|
88
113
|
expect(result.current.activeIndex).toBe(0);
|
|
89
114
|
});
|
|
90
115
|
it('should default to 0 if initialIndex is out of bounds', () => {
|
|
91
|
-
const { result } =
|
|
116
|
+
const { result } = renderSelectionListHook({
|
|
92
117
|
items,
|
|
93
118
|
initialIndex: 10,
|
|
94
119
|
onSelect: mockOnSelect,
|
|
95
|
-
})
|
|
120
|
+
});
|
|
96
121
|
expect(result.current.activeIndex).toBe(0);
|
|
97
|
-
const { result: resultNeg } =
|
|
122
|
+
const { result: resultNeg } = renderSelectionListHook({
|
|
98
123
|
items,
|
|
99
124
|
initialIndex: -1,
|
|
100
125
|
onSelect: mockOnSelect,
|
|
101
|
-
})
|
|
126
|
+
});
|
|
102
127
|
expect(resultNeg.current.activeIndex).toBe(0);
|
|
103
128
|
});
|
|
104
129
|
it('should stick to the initial index if all items are disabled', () => {
|
|
@@ -106,17 +131,20 @@ describe('useSelectionList', () => {
|
|
|
106
131
|
{ value: 'A', disabled: true, key: 'A' },
|
|
107
132
|
{ value: 'B', disabled: true, key: 'B' },
|
|
108
133
|
];
|
|
109
|
-
const { result } =
|
|
134
|
+
const { result } = renderSelectionListHook({
|
|
110
135
|
items: allDisabled,
|
|
111
136
|
initialIndex: 1,
|
|
112
137
|
onSelect: mockOnSelect,
|
|
113
|
-
})
|
|
138
|
+
});
|
|
114
139
|
expect(result.current.activeIndex).toBe(1);
|
|
115
140
|
});
|
|
116
141
|
});
|
|
117
142
|
describe('Keyboard Navigation (Up/Down/J/K)', () => {
|
|
118
143
|
it('should move down with "j" and "down" keys, skipping disabled items', () => {
|
|
119
|
-
const { result } =
|
|
144
|
+
const { result } = renderSelectionListHook({
|
|
145
|
+
items,
|
|
146
|
+
onSelect: mockOnSelect,
|
|
147
|
+
});
|
|
120
148
|
expect(result.current.activeIndex).toBe(0);
|
|
121
149
|
pressKey('j');
|
|
122
150
|
expect(result.current.activeIndex).toBe(2);
|
|
@@ -124,7 +152,11 @@ describe('useSelectionList', () => {
|
|
|
124
152
|
expect(result.current.activeIndex).toBe(3);
|
|
125
153
|
});
|
|
126
154
|
it('should move up with "k" and "up" keys, skipping disabled items', () => {
|
|
127
|
-
const { result } =
|
|
155
|
+
const { result } = renderSelectionListHook({
|
|
156
|
+
items,
|
|
157
|
+
initialIndex: 3,
|
|
158
|
+
onSelect: mockOnSelect,
|
|
159
|
+
});
|
|
128
160
|
expect(result.current.activeIndex).toBe(3);
|
|
129
161
|
pressKey('k');
|
|
130
162
|
expect(result.current.activeIndex).toBe(2);
|
|
@@ -132,11 +164,11 @@ describe('useSelectionList', () => {
|
|
|
132
164
|
expect(result.current.activeIndex).toBe(0);
|
|
133
165
|
});
|
|
134
166
|
it('should wrap navigation correctly', () => {
|
|
135
|
-
const { result } =
|
|
167
|
+
const { result } = renderSelectionListHook({
|
|
136
168
|
items,
|
|
137
169
|
initialIndex: items.length - 1,
|
|
138
170
|
onSelect: mockOnSelect,
|
|
139
|
-
})
|
|
171
|
+
});
|
|
140
172
|
expect(result.current.activeIndex).toBe(3);
|
|
141
173
|
pressKey('down');
|
|
142
174
|
expect(result.current.activeIndex).toBe(0);
|
|
@@ -144,22 +176,22 @@ describe('useSelectionList', () => {
|
|
|
144
176
|
expect(result.current.activeIndex).toBe(3);
|
|
145
177
|
});
|
|
146
178
|
it('should call onHighlight when index changes', () => {
|
|
147
|
-
|
|
179
|
+
renderSelectionListHook({
|
|
148
180
|
items,
|
|
149
181
|
onSelect: mockOnSelect,
|
|
150
182
|
onHighlight: mockOnHighlight,
|
|
151
|
-
})
|
|
183
|
+
});
|
|
152
184
|
pressKey('down');
|
|
153
185
|
expect(mockOnHighlight).toHaveBeenCalledTimes(1);
|
|
154
186
|
expect(mockOnHighlight).toHaveBeenCalledWith('C');
|
|
155
187
|
});
|
|
156
188
|
it('should not move or call onHighlight if navigation results in the same index (e.g., single item)', () => {
|
|
157
189
|
const singleItem = [{ value: 'A', key: 'A' }];
|
|
158
|
-
const { result } =
|
|
190
|
+
const { result } = renderSelectionListHook({
|
|
159
191
|
items: singleItem,
|
|
160
192
|
onSelect: mockOnSelect,
|
|
161
193
|
onHighlight: mockOnHighlight,
|
|
162
|
-
})
|
|
194
|
+
});
|
|
163
195
|
pressKey('down');
|
|
164
196
|
expect(result.current.activeIndex).toBe(0);
|
|
165
197
|
expect(mockOnHighlight).not.toHaveBeenCalled();
|
|
@@ -169,11 +201,11 @@ describe('useSelectionList', () => {
|
|
|
169
201
|
{ value: 'A', disabled: true, key: 'A' },
|
|
170
202
|
{ value: 'B', disabled: true, key: 'B' },
|
|
171
203
|
];
|
|
172
|
-
const { result } =
|
|
204
|
+
const { result } = renderSelectionListHook({
|
|
173
205
|
items: allDisabled,
|
|
174
206
|
onSelect: mockOnSelect,
|
|
175
207
|
onHighlight: mockOnHighlight,
|
|
176
|
-
})
|
|
208
|
+
});
|
|
177
209
|
const initialIndex = result.current.activeIndex;
|
|
178
210
|
pressKey('down');
|
|
179
211
|
expect(result.current.activeIndex).toBe(initialIndex);
|
|
@@ -182,20 +214,20 @@ describe('useSelectionList', () => {
|
|
|
182
214
|
});
|
|
183
215
|
describe('Selection (Enter)', () => {
|
|
184
216
|
it('should call onSelect when "return" is pressed on enabled item', () => {
|
|
185
|
-
|
|
217
|
+
renderSelectionListHook({
|
|
186
218
|
items,
|
|
187
219
|
initialIndex: 2,
|
|
188
220
|
onSelect: mockOnSelect,
|
|
189
|
-
})
|
|
221
|
+
});
|
|
190
222
|
pressKey('return');
|
|
191
223
|
expect(mockOnSelect).toHaveBeenCalledTimes(1);
|
|
192
224
|
expect(mockOnSelect).toHaveBeenCalledWith('C');
|
|
193
225
|
});
|
|
194
226
|
it('should not call onSelect if the active item is disabled', () => {
|
|
195
|
-
const { result } =
|
|
227
|
+
const { result } = renderSelectionListHook({
|
|
196
228
|
items,
|
|
197
229
|
onSelect: mockOnSelect,
|
|
198
|
-
})
|
|
230
|
+
});
|
|
199
231
|
act(() => result.current.setActiveIndex(1));
|
|
200
232
|
pressKey('return');
|
|
201
233
|
expect(mockOnSelect).not.toHaveBeenCalled();
|
|
@@ -203,11 +235,11 @@ describe('useSelectionList', () => {
|
|
|
203
235
|
});
|
|
204
236
|
describe('Keyboard Navigation Robustness (Rapid Input)', () => {
|
|
205
237
|
it('should handle rapid navigation and selection robustly (avoiding stale state)', () => {
|
|
206
|
-
const { result } =
|
|
238
|
+
const { result } = renderSelectionListHook({
|
|
207
239
|
items, // A, B(disabled), C, D. Initial index 0 (A).
|
|
208
240
|
onSelect: mockOnSelect,
|
|
209
241
|
onHighlight: mockOnHighlight,
|
|
210
|
-
})
|
|
242
|
+
});
|
|
211
243
|
// Simulate rapid inputs with separate act blocks to allow effects to run
|
|
212
244
|
if (!activeKeypressHandler)
|
|
213
245
|
throw new Error('Handler not active');
|
|
@@ -244,11 +276,11 @@ describe('useSelectionList', () => {
|
|
|
244
276
|
expect(mockOnSelect).not.toHaveBeenCalledWith('A');
|
|
245
277
|
});
|
|
246
278
|
it('should handle ultra-rapid input (multiple presses in single act) without stale state', () => {
|
|
247
|
-
const { result } =
|
|
279
|
+
const { result } = renderSelectionListHook({
|
|
248
280
|
items, // A, B(disabled), C, D. Initial index 0 (A).
|
|
249
281
|
onSelect: mockOnSelect,
|
|
250
282
|
onHighlight: mockOnHighlight,
|
|
251
|
-
})
|
|
283
|
+
});
|
|
252
284
|
// Simulate ultra-rapid inputs where all keypresses happen faster than React can re-render
|
|
253
285
|
act(() => {
|
|
254
286
|
if (!activeKeypressHandler)
|
|
@@ -278,27 +310,38 @@ describe('useSelectionList', () => {
|
|
|
278
310
|
});
|
|
279
311
|
describe('Focus Management (isFocused)', () => {
|
|
280
312
|
it('should activate the keypress handler when focused (default) and items exist', () => {
|
|
281
|
-
const { result } =
|
|
313
|
+
const { result } = renderSelectionListHook({
|
|
314
|
+
items,
|
|
315
|
+
onSelect: mockOnSelect,
|
|
316
|
+
});
|
|
282
317
|
expect(activeKeypressHandler).not.toBeNull();
|
|
283
318
|
pressKey('down');
|
|
284
319
|
expect(result.current.activeIndex).toBe(2);
|
|
285
320
|
});
|
|
286
321
|
it('should not activate the keypress handler when isFocused is false', () => {
|
|
287
|
-
|
|
322
|
+
renderSelectionListHook({
|
|
323
|
+
items,
|
|
324
|
+
onSelect: mockOnSelect,
|
|
325
|
+
isFocused: false,
|
|
326
|
+
});
|
|
288
327
|
expect(activeKeypressHandler).toBeNull();
|
|
289
328
|
expect(() => pressKey('down')).toThrow(/keypress handler is not active/);
|
|
290
329
|
});
|
|
291
330
|
it('should not activate the keypress handler when items list is empty', () => {
|
|
292
|
-
|
|
331
|
+
renderSelectionListHook({
|
|
293
332
|
items: [],
|
|
294
333
|
onSelect: mockOnSelect,
|
|
295
334
|
isFocused: true,
|
|
296
|
-
})
|
|
335
|
+
});
|
|
297
336
|
expect(activeKeypressHandler).toBeNull();
|
|
298
337
|
expect(() => pressKey('down')).toThrow(/keypress handler is not active/);
|
|
299
338
|
});
|
|
300
339
|
it('should activate/deactivate when isFocused prop changes', () => {
|
|
301
|
-
const { result, rerender } =
|
|
340
|
+
const { result, rerender } = renderSelectionListHook({
|
|
341
|
+
items,
|
|
342
|
+
onSelect: mockOnSelect,
|
|
343
|
+
isFocused: false,
|
|
344
|
+
});
|
|
302
345
|
expect(activeKeypressHandler).toBeNull();
|
|
303
346
|
rerender({ isFocused: true });
|
|
304
347
|
expect(activeKeypressHandler).not.toBeNull();
|
|
@@ -320,18 +363,21 @@ describe('useSelectionList', () => {
|
|
|
320
363
|
const longList = Array.from({ length: 15 }, (_, i) => ({ value: `Item ${i + 1}`, key: `Item ${i + 1}` }));
|
|
321
364
|
const pressNumber = (num) => pressKey(num, num);
|
|
322
365
|
it('should not respond to numbers if showNumbers is false (default)', () => {
|
|
323
|
-
const { result } =
|
|
366
|
+
const { result } = renderSelectionListHook({
|
|
367
|
+
items: shortList,
|
|
368
|
+
onSelect: mockOnSelect,
|
|
369
|
+
});
|
|
324
370
|
pressNumber('1');
|
|
325
371
|
expect(result.current.activeIndex).toBe(0);
|
|
326
372
|
expect(mockOnSelect).not.toHaveBeenCalled();
|
|
327
373
|
});
|
|
328
374
|
it('should select item immediately if the number cannot be extended (unambiguous)', () => {
|
|
329
|
-
const { result } =
|
|
375
|
+
const { result } = renderSelectionListHook({
|
|
330
376
|
items: shortList,
|
|
331
377
|
onSelect: mockOnSelect,
|
|
332
378
|
onHighlight: mockOnHighlight,
|
|
333
379
|
showNumbers: true,
|
|
334
|
-
})
|
|
380
|
+
});
|
|
335
381
|
pressNumber('3');
|
|
336
382
|
expect(result.current.activeIndex).toBe(2);
|
|
337
383
|
expect(mockOnHighlight).toHaveBeenCalledWith('C');
|
|
@@ -340,13 +386,13 @@ describe('useSelectionList', () => {
|
|
|
340
386
|
expect(vi.getTimerCount()).toBe(0);
|
|
341
387
|
});
|
|
342
388
|
it('should highlight and wait for timeout if the number can be extended (ambiguous)', () => {
|
|
343
|
-
const { result } =
|
|
389
|
+
const { result } = renderSelectionListHook({
|
|
344
390
|
items: longList,
|
|
345
391
|
initialIndex: 1, // Start at index 1 so pressing "1" (index 0) causes a change
|
|
346
392
|
onSelect: mockOnSelect,
|
|
347
393
|
onHighlight: mockOnHighlight,
|
|
348
394
|
showNumbers: true,
|
|
349
|
-
})
|
|
395
|
+
});
|
|
350
396
|
pressNumber('1');
|
|
351
397
|
expect(result.current.activeIndex).toBe(0);
|
|
352
398
|
expect(mockOnHighlight).toHaveBeenCalledWith('Item 1');
|
|
@@ -359,11 +405,11 @@ describe('useSelectionList', () => {
|
|
|
359
405
|
expect(mockOnSelect).toHaveBeenCalledWith('Item 1');
|
|
360
406
|
});
|
|
361
407
|
it('should handle multi-digit input correctly', () => {
|
|
362
|
-
const { result } =
|
|
408
|
+
const { result } = renderSelectionListHook({
|
|
363
409
|
items: longList,
|
|
364
410
|
onSelect: mockOnSelect,
|
|
365
411
|
showNumbers: true,
|
|
366
|
-
})
|
|
412
|
+
});
|
|
367
413
|
pressNumber('1');
|
|
368
414
|
expect(mockOnSelect).not.toHaveBeenCalled();
|
|
369
415
|
pressNumber('2');
|
|
@@ -372,11 +418,11 @@ describe('useSelectionList', () => {
|
|
|
372
418
|
expect(mockOnSelect).toHaveBeenCalledWith('Item 12');
|
|
373
419
|
});
|
|
374
420
|
it('should reset buffer if input becomes invalid (out of bounds)', () => {
|
|
375
|
-
const { result } =
|
|
421
|
+
const { result } = renderSelectionListHook({
|
|
376
422
|
items: shortList,
|
|
377
423
|
onSelect: mockOnSelect,
|
|
378
424
|
showNumbers: true,
|
|
379
|
-
})
|
|
425
|
+
});
|
|
380
426
|
pressNumber('5');
|
|
381
427
|
expect(result.current.activeIndex).toBe(0);
|
|
382
428
|
expect(mockOnSelect).not.toHaveBeenCalled();
|
|
@@ -385,11 +431,11 @@ describe('useSelectionList', () => {
|
|
|
385
431
|
expect(mockOnSelect).toHaveBeenCalledWith('C');
|
|
386
432
|
});
|
|
387
433
|
it('should allow "0" as subsequent digit, but ignore as first digit', () => {
|
|
388
|
-
const { result } =
|
|
434
|
+
const { result } = renderSelectionListHook({
|
|
389
435
|
items: longList,
|
|
390
436
|
onSelect: mockOnSelect,
|
|
391
437
|
showNumbers: true,
|
|
392
|
-
})
|
|
438
|
+
});
|
|
393
439
|
pressNumber('0');
|
|
394
440
|
expect(result.current.activeIndex).toBe(0);
|
|
395
441
|
expect(mockOnSelect).not.toHaveBeenCalled();
|
|
@@ -402,11 +448,11 @@ describe('useSelectionList', () => {
|
|
|
402
448
|
expect(mockOnSelect).toHaveBeenCalledWith('Item 10');
|
|
403
449
|
});
|
|
404
450
|
it('should clear the initial "0" input after timeout', () => {
|
|
405
|
-
|
|
451
|
+
renderSelectionListHook({
|
|
406
452
|
items: longList,
|
|
407
453
|
onSelect: mockOnSelect,
|
|
408
454
|
showNumbers: true,
|
|
409
|
-
})
|
|
455
|
+
});
|
|
410
456
|
pressNumber('0');
|
|
411
457
|
act(() => vi.advanceTimersByTime(1000)); // Timeout the '0' input
|
|
412
458
|
pressNumber('1');
|
|
@@ -415,12 +461,12 @@ describe('useSelectionList', () => {
|
|
|
415
461
|
expect(mockOnSelect).toHaveBeenCalledWith('Item 1');
|
|
416
462
|
});
|
|
417
463
|
it('should highlight but not select a disabled item (immediate selection case)', () => {
|
|
418
|
-
const { result } =
|
|
464
|
+
const { result } = renderSelectionListHook({
|
|
419
465
|
items: shortList, // B (index 1, number 2) is disabled
|
|
420
466
|
onSelect: mockOnSelect,
|
|
421
467
|
onHighlight: mockOnHighlight,
|
|
422
468
|
showNumbers: true,
|
|
423
|
-
})
|
|
469
|
+
});
|
|
424
470
|
pressNumber('2');
|
|
425
471
|
expect(result.current.activeIndex).toBe(1);
|
|
426
472
|
expect(mockOnHighlight).toHaveBeenCalledWith('B');
|
|
@@ -433,11 +479,11 @@ describe('useSelectionList', () => {
|
|
|
433
479
|
{ value: 'Item 1 Disabled', disabled: true, key: 'Item 1 Disabled' },
|
|
434
480
|
...longList.slice(1),
|
|
435
481
|
];
|
|
436
|
-
const { result } =
|
|
482
|
+
const { result } = renderSelectionListHook({
|
|
437
483
|
items: disabledAmbiguousList,
|
|
438
484
|
onSelect: mockOnSelect,
|
|
439
485
|
showNumbers: true,
|
|
440
|
-
})
|
|
486
|
+
});
|
|
441
487
|
pressNumber('1');
|
|
442
488
|
expect(result.current.activeIndex).toBe(0);
|
|
443
489
|
expect(vi.getTimerCount()).toBe(1);
|
|
@@ -448,11 +494,11 @@ describe('useSelectionList', () => {
|
|
|
448
494
|
expect(mockOnSelect).not.toHaveBeenCalled();
|
|
449
495
|
});
|
|
450
496
|
it('should clear the number buffer if a non-numeric key (e.g., navigation) is pressed', () => {
|
|
451
|
-
const { result } =
|
|
497
|
+
const { result } = renderSelectionListHook({
|
|
452
498
|
items: longList,
|
|
453
499
|
onSelect: mockOnSelect,
|
|
454
500
|
showNumbers: true,
|
|
455
|
-
})
|
|
501
|
+
});
|
|
456
502
|
pressNumber('1');
|
|
457
503
|
expect(vi.getTimerCount()).toBe(1);
|
|
458
504
|
pressKey('down');
|
|
@@ -463,11 +509,11 @@ describe('useSelectionList', () => {
|
|
|
463
509
|
expect(result.current.activeIndex).toBe(2);
|
|
464
510
|
});
|
|
465
511
|
it('should clear the number buffer if "return" is pressed', () => {
|
|
466
|
-
|
|
512
|
+
renderSelectionListHook({
|
|
467
513
|
items: longList,
|
|
468
514
|
onSelect: mockOnSelect,
|
|
469
515
|
showNumbers: true,
|
|
470
|
-
})
|
|
516
|
+
});
|
|
471
517
|
pressNumber('1');
|
|
472
518
|
pressKey('return');
|
|
473
519
|
expect(mockOnSelect).toHaveBeenCalledTimes(1);
|
|
@@ -479,44 +525,50 @@ describe('useSelectionList', () => {
|
|
|
479
525
|
});
|
|
480
526
|
});
|
|
481
527
|
describe('Reactivity (Dynamic Updates)', () => {
|
|
482
|
-
it('should update activeIndex when initialIndex prop changes', () => {
|
|
483
|
-
const { result, rerender } =
|
|
528
|
+
it('should update activeIndex when initialIndex prop changes', async () => {
|
|
529
|
+
const { result, rerender } = renderSelectionListHook({
|
|
484
530
|
items,
|
|
485
531
|
onSelect: mockOnSelect,
|
|
486
|
-
initialIndex,
|
|
487
|
-
})
|
|
532
|
+
initialIndex: 0,
|
|
533
|
+
});
|
|
488
534
|
rerender({ initialIndex: 2 });
|
|
489
|
-
|
|
535
|
+
await vi.waitFor(() => {
|
|
536
|
+
expect(result.current.activeIndex).toBe(2);
|
|
537
|
+
});
|
|
490
538
|
});
|
|
491
|
-
it('should respect a new initialIndex even after user interaction', () => {
|
|
492
|
-
const { result, rerender } =
|
|
539
|
+
it('should respect a new initialIndex even after user interaction', async () => {
|
|
540
|
+
const { result, rerender } = renderSelectionListHook({
|
|
493
541
|
items,
|
|
494
542
|
onSelect: mockOnSelect,
|
|
495
|
-
initialIndex,
|
|
496
|
-
})
|
|
543
|
+
initialIndex: 0,
|
|
544
|
+
});
|
|
497
545
|
// User navigates, changing the active index
|
|
498
546
|
pressKey('down');
|
|
499
547
|
expect(result.current.activeIndex).toBe(2);
|
|
500
548
|
// The component re-renders with a new initial index
|
|
501
549
|
rerender({ initialIndex: 3 });
|
|
502
550
|
// The hook should now respect the new initial index
|
|
503
|
-
|
|
551
|
+
await vi.waitFor(() => {
|
|
552
|
+
expect(result.current.activeIndex).toBe(3);
|
|
553
|
+
});
|
|
504
554
|
});
|
|
505
|
-
it('should validate index when initialIndex prop changes to a disabled item', () => {
|
|
506
|
-
const { result, rerender } =
|
|
555
|
+
it('should validate index when initialIndex prop changes to a disabled item', async () => {
|
|
556
|
+
const { result, rerender } = renderSelectionListHook({
|
|
507
557
|
items,
|
|
508
558
|
onSelect: mockOnSelect,
|
|
509
|
-
initialIndex,
|
|
510
|
-
})
|
|
559
|
+
initialIndex: 0,
|
|
560
|
+
});
|
|
511
561
|
rerender({ initialIndex: 1 });
|
|
512
|
-
|
|
562
|
+
await vi.waitFor(() => {
|
|
563
|
+
expect(result.current.activeIndex).toBe(2);
|
|
564
|
+
});
|
|
513
565
|
});
|
|
514
|
-
it('should adjust activeIndex if items change and the initialIndex is now out of bounds', () => {
|
|
515
|
-
const { result, rerender } =
|
|
566
|
+
it('should adjust activeIndex if items change and the initialIndex is now out of bounds', async () => {
|
|
567
|
+
const { result, rerender } = renderSelectionListHook({
|
|
516
568
|
onSelect: mockOnSelect,
|
|
517
569
|
initialIndex: 3,
|
|
518
|
-
items
|
|
519
|
-
})
|
|
570
|
+
items,
|
|
571
|
+
});
|
|
520
572
|
expect(result.current.activeIndex).toBe(3);
|
|
521
573
|
const shorterItems = [
|
|
522
574
|
{ value: 'X', key: 'X' },
|
|
@@ -524,19 +576,21 @@ describe('useSelectionList', () => {
|
|
|
524
576
|
];
|
|
525
577
|
rerender({ items: shorterItems }); // Length 2
|
|
526
578
|
// The useEffect syncs based on the initialIndex (3) which is now out of bounds. It defaults to 0.
|
|
527
|
-
|
|
579
|
+
await vi.waitFor(() => {
|
|
580
|
+
expect(result.current.activeIndex).toBe(0);
|
|
581
|
+
});
|
|
528
582
|
});
|
|
529
|
-
it('should adjust activeIndex if items change and the initialIndex becomes disabled', () => {
|
|
583
|
+
it('should adjust activeIndex if items change and the initialIndex becomes disabled', async () => {
|
|
530
584
|
const initialItems = [
|
|
531
585
|
{ value: 'A', key: 'A' },
|
|
532
586
|
{ value: 'B', key: 'B' },
|
|
533
587
|
{ value: 'C', key: 'C' },
|
|
534
588
|
];
|
|
535
|
-
const { result, rerender } =
|
|
589
|
+
const { result, rerender } = renderSelectionListHook({
|
|
536
590
|
onSelect: mockOnSelect,
|
|
537
591
|
initialIndex: 1,
|
|
538
|
-
items:
|
|
539
|
-
})
|
|
592
|
+
items: initialItems,
|
|
593
|
+
});
|
|
540
594
|
expect(result.current.activeIndex).toBe(1);
|
|
541
595
|
const newItems = [
|
|
542
596
|
{ value: 'A', key: 'A' },
|
|
@@ -544,30 +598,34 @@ describe('useSelectionList', () => {
|
|
|
544
598
|
{ value: 'C', key: 'C' },
|
|
545
599
|
];
|
|
546
600
|
rerender({ items: newItems });
|
|
547
|
-
|
|
601
|
+
await vi.waitFor(() => {
|
|
602
|
+
expect(result.current.activeIndex).toBe(2);
|
|
603
|
+
});
|
|
548
604
|
});
|
|
549
|
-
it('should reset to 0 if items change to an empty list', () => {
|
|
550
|
-
const { result, rerender } =
|
|
605
|
+
it('should reset to 0 if items change to an empty list', async () => {
|
|
606
|
+
const { result, rerender } = renderSelectionListHook({
|
|
551
607
|
onSelect: mockOnSelect,
|
|
552
608
|
initialIndex: 2,
|
|
553
|
-
items
|
|
554
|
-
})
|
|
609
|
+
items,
|
|
610
|
+
});
|
|
555
611
|
rerender({ items: [] });
|
|
556
|
-
|
|
612
|
+
await vi.waitFor(() => {
|
|
613
|
+
expect(result.current.activeIndex).toBe(0);
|
|
614
|
+
});
|
|
557
615
|
});
|
|
558
|
-
it('should not reset activeIndex when items are deeply equal', () => {
|
|
616
|
+
it('should not reset activeIndex when items are deeply equal', async () => {
|
|
559
617
|
const initialItems = [
|
|
560
618
|
{ value: 'A', key: 'A' },
|
|
561
619
|
{ value: 'B', disabled: true, key: 'B' },
|
|
562
620
|
{ value: 'C', key: 'C' },
|
|
563
621
|
{ value: 'D', key: 'D' },
|
|
564
622
|
];
|
|
565
|
-
const { result, rerender } =
|
|
623
|
+
const { result, rerender } = renderSelectionListHook({
|
|
566
624
|
onSelect: mockOnSelect,
|
|
567
625
|
onHighlight: mockOnHighlight,
|
|
568
626
|
initialIndex: 2,
|
|
569
|
-
items:
|
|
570
|
-
})
|
|
627
|
+
items: initialItems,
|
|
628
|
+
});
|
|
571
629
|
expect(result.current.activeIndex).toBe(2);
|
|
572
630
|
act(() => {
|
|
573
631
|
result.current.setActiveIndex(3);
|
|
@@ -583,23 +641,25 @@ describe('useSelectionList', () => {
|
|
|
583
641
|
];
|
|
584
642
|
rerender({ items: newItems });
|
|
585
643
|
// Active index should remain the same since items are deeply equal
|
|
586
|
-
|
|
644
|
+
await vi.waitFor(() => {
|
|
645
|
+
expect(result.current.activeIndex).toBe(3);
|
|
646
|
+
});
|
|
587
647
|
// onHighlight should NOT be called since the index didn't change
|
|
588
648
|
expect(mockOnHighlight).not.toHaveBeenCalled();
|
|
589
649
|
});
|
|
590
|
-
it('should update activeIndex when items change structurally', () => {
|
|
650
|
+
it('should update activeIndex when items change structurally', async () => {
|
|
591
651
|
const initialItems = [
|
|
592
652
|
{ value: 'A', key: 'A' },
|
|
593
653
|
{ value: 'B', disabled: true, key: 'B' },
|
|
594
654
|
{ value: 'C', key: 'C' },
|
|
595
655
|
{ value: 'D', key: 'D' },
|
|
596
656
|
];
|
|
597
|
-
const { result, rerender } =
|
|
657
|
+
const { result, rerender } = renderSelectionListHook({
|
|
598
658
|
onSelect: mockOnSelect,
|
|
599
659
|
onHighlight: mockOnHighlight,
|
|
600
660
|
initialIndex: 3,
|
|
601
|
-
items:
|
|
602
|
-
})
|
|
661
|
+
items: initialItems,
|
|
662
|
+
});
|
|
603
663
|
expect(result.current.activeIndex).toBe(3);
|
|
604
664
|
mockOnHighlight.mockClear();
|
|
605
665
|
// Change item values (not deeply equal)
|
|
@@ -610,19 +670,21 @@ describe('useSelectionList', () => {
|
|
|
610
670
|
];
|
|
611
671
|
rerender({ items: newItems });
|
|
612
672
|
// Active index should update based on initialIndex and new items
|
|
613
|
-
|
|
673
|
+
await vi.waitFor(() => {
|
|
674
|
+
expect(result.current.activeIndex).toBe(0);
|
|
675
|
+
});
|
|
614
676
|
});
|
|
615
|
-
it('should handle partial changes in items array', () => {
|
|
677
|
+
it('should handle partial changes in items array', async () => {
|
|
616
678
|
const initialItems = [
|
|
617
679
|
{ value: 'A', key: 'A' },
|
|
618
680
|
{ value: 'B', key: 'B' },
|
|
619
681
|
{ value: 'C', key: 'C' },
|
|
620
682
|
];
|
|
621
|
-
const { result, rerender } =
|
|
683
|
+
const { result, rerender } = renderSelectionListHook({
|
|
622
684
|
onSelect: mockOnSelect,
|
|
623
685
|
initialIndex: 1,
|
|
624
|
-
items:
|
|
625
|
-
})
|
|
686
|
+
items: initialItems,
|
|
687
|
+
});
|
|
626
688
|
expect(result.current.activeIndex).toBe(1);
|
|
627
689
|
// Change only one item's disabled status
|
|
628
690
|
const newItems = [
|
|
@@ -632,18 +694,20 @@ describe('useSelectionList', () => {
|
|
|
632
694
|
];
|
|
633
695
|
rerender({ items: newItems });
|
|
634
696
|
// Should find next valid index since current became disabled
|
|
635
|
-
|
|
697
|
+
await vi.waitFor(() => {
|
|
698
|
+
expect(result.current.activeIndex).toBe(2);
|
|
699
|
+
});
|
|
636
700
|
});
|
|
637
|
-
it('should update selection when a new item is added to the start of the list', () => {
|
|
701
|
+
it('should update selection when a new item is added to the start of the list', async () => {
|
|
638
702
|
const initialItems = [
|
|
639
703
|
{ value: 'A', key: 'A' },
|
|
640
704
|
{ value: 'B', key: 'B' },
|
|
641
705
|
{ value: 'C', key: 'C' },
|
|
642
706
|
];
|
|
643
|
-
const { result, rerender } =
|
|
707
|
+
const { result, rerender } = renderSelectionListHook({
|
|
644
708
|
onSelect: mockOnSelect,
|
|
645
|
-
items:
|
|
646
|
-
})
|
|
709
|
+
items: initialItems,
|
|
710
|
+
});
|
|
647
711
|
pressKey('down');
|
|
648
712
|
expect(result.current.activeIndex).toBe(1);
|
|
649
713
|
const newItems = [
|
|
@@ -653,7 +717,9 @@ describe('useSelectionList', () => {
|
|
|
653
717
|
{ value: 'C', key: 'C' },
|
|
654
718
|
];
|
|
655
719
|
rerender({ items: newItems });
|
|
656
|
-
|
|
720
|
+
await vi.waitFor(() => {
|
|
721
|
+
expect(result.current.activeIndex).toBe(2);
|
|
722
|
+
});
|
|
657
723
|
});
|
|
658
724
|
it('should not re-initialize when items have identical keys but are different objects', () => {
|
|
659
725
|
const initialItems = [
|
|
@@ -661,14 +727,22 @@ describe('useSelectionList', () => {
|
|
|
661
727
|
{ value: 'B', key: 'B' },
|
|
662
728
|
];
|
|
663
729
|
let renderCount = 0;
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
730
|
+
const renderHookWithCount = (initialProps) => {
|
|
731
|
+
function TestComponent(props) {
|
|
732
|
+
renderCount++;
|
|
733
|
+
useSelectionList({
|
|
734
|
+
onSelect: mockOnSelect,
|
|
735
|
+
onHighlight: mockOnHighlight,
|
|
736
|
+
items: props.items,
|
|
737
|
+
});
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
const { rerender } = render(_jsx(TestComponent, { ...initialProps }));
|
|
741
|
+
return {
|
|
742
|
+
rerender: (newProps) => rerender(_jsx(TestComponent, { ...initialProps, ...newProps })),
|
|
743
|
+
};
|
|
744
|
+
};
|
|
745
|
+
const { rerender } = renderHookWithCount({ items: initialItems });
|
|
672
746
|
// Initial render
|
|
673
747
|
expect(renderCount).toBe(1);
|
|
674
748
|
// Create new items with the same keys but different object references
|
|
@@ -680,19 +754,6 @@ describe('useSelectionList', () => {
|
|
|
680
754
|
expect(renderCount).toBe(2);
|
|
681
755
|
});
|
|
682
756
|
});
|
|
683
|
-
describe('Manual Control', () => {
|
|
684
|
-
it('should allow manual setting of active index via setActiveIndex', () => {
|
|
685
|
-
const { result } = renderHook(() => useSelectionList({ items, onSelect: mockOnSelect }));
|
|
686
|
-
act(() => {
|
|
687
|
-
result.current.setActiveIndex(3);
|
|
688
|
-
});
|
|
689
|
-
expect(result.current.activeIndex).toBe(3);
|
|
690
|
-
act(() => {
|
|
691
|
-
result.current.setActiveIndex(1);
|
|
692
|
-
});
|
|
693
|
-
expect(result.current.activeIndex).toBe(1);
|
|
694
|
-
});
|
|
695
|
-
});
|
|
696
757
|
describe('Cleanup', () => {
|
|
697
758
|
beforeEach(() => {
|
|
698
759
|
vi.useFakeTimers();
|
|
@@ -702,11 +763,11 @@ describe('useSelectionList', () => {
|
|
|
702
763
|
});
|
|
703
764
|
it('should clear timeout on unmount when timer is active', () => {
|
|
704
765
|
const longList = Array.from({ length: 15 }, (_, i) => ({ value: `Item ${i + 1}`, key: `Item ${i + 1}` }));
|
|
705
|
-
const { unmount } =
|
|
766
|
+
const { unmount } = renderSelectionListHook({
|
|
706
767
|
items: longList,
|
|
707
768
|
onSelect: mockOnSelect,
|
|
708
769
|
showNumbers: true,
|
|
709
|
-
})
|
|
770
|
+
});
|
|
710
771
|
pressKey('1', '1');
|
|
711
772
|
expect(vi.getTimerCount()).toBe(1);
|
|
712
773
|
act(() => {
|