cdd-cli 4.0.0 → 4.2.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.es.md +60 -26
- package/README.md +60 -26
- package/dist/App.js +1 -0
- package/dist/components/ContainerCreationPrompt.js +14 -0
- package/dist/components/PromptField.js +13 -6
- package/dist/components/TextField.js +54 -0
- package/dist/components/UsageMenu.js +1 -1
- package/dist/helpers/appState.js +26 -0
- package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
- package/dist/helpers/textEditing.js +293 -0
- package/dist/hooks/creation/useContainerCreation.js +132 -15
- package/dist/hooks/useContainerCommandRouter.js +12 -2
- package/dist/hooks/useControls.js +21 -26
- package/dist/hooks/useShellMode.js +101 -0
- package/dist/index.js +3 -1
- package/package.json +4 -1
- package/.atl/skill-registry.md +0 -30
- package/.editorconfig +0 -12
- package/.eslintrc.json +0 -30
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/docs.yml +0 -42
- package/.prettierignore +0 -4
- package/.prettierrc +0 -8
- package/.~lock.ROADMAP.md# +0 -1
- package/CHANGELOG.md +0 -216
- package/CODE_OF_CONDUCT.md +0 -12
- package/CONTRIBUTING.md +0 -35
- package/ROADMAP.md +0 -647
- package/SECURITY.md +0 -5
- package/__mocks__/ink.cjs +0 -17
- package/_config.yml +0 -13
- package/babel.config.cjs +0 -23
- package/coverage/clover.xml +0 -575
- package/coverage/coverage-final.json +0 -17
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +0 -400
- package/coverage/lcov-report/ControlsHUD.jsx.html +0 -256
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -394
- package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -268
- package/coverage/lcov-report/components/index.html +0 -146
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/helpers/constants.js.html +0 -352
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
- package/coverage/lcov-report/helpers/dockerHubService.js.html +0 -310
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
- package/coverage/lcov-report/helpers/index.html +0 -206
- package/coverage/lcov-report/helpers/logger.js.html +0 -424
- package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
- package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
- package/coverage/lcov-report/hooks/creation/index.html +0 -146
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -982
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
- package/coverage/lcov-report/hooks/debug/index.html +0 -116
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
- package/coverage/lcov-report/hooks/index.html +0 -161
- package/coverage/lcov-report/hooks/navigation/index.html +0 -116
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
- package/coverage/lcov-report/hooks/useControls.js.html +0 -868
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
- package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
- package/coverage/lcov-report/index.html +0 -176
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -1163
- package/fix-imports.cjs +0 -54
- package/jest.config.cjs +0 -14
- package/src/App.jsx +0 -102
- package/src/components/ContainerCreationPrompt.jsx +0 -107
- package/src/components/ContainerList.jsx +0 -32
- package/src/components/ContainerRow.jsx +0 -84
- package/src/components/ContainerSection.jsx +0 -24
- package/src/components/ControlsHUD.jsx +0 -66
- package/src/components/Footer.jsx +0 -15
- package/src/components/Header.jsx +0 -41
- package/src/components/LogViewer.jsx +0 -36
- package/src/components/MessageFeedback.jsx +0 -25
- package/src/components/PromptField.jsx +0 -48
- package/src/components/StatsBar.jsx +0 -70
- package/src/components/SuggestionPanel.jsx +0 -61
- package/src/components/UsageMenu.jsx +0 -52
- package/src/helpers/appInfo.js +0 -23
- package/src/helpers/constants.js +0 -89
- package/src/helpers/containerOptionsBuilder.js +0 -53
- package/src/helpers/dockerHubService.js +0 -75
- package/src/helpers/dockerService/dockerService.js +0 -28
- package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
- package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
- package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
- package/src/helpers/exitWithMessage.js +0 -32
- package/src/helpers/imageNameUtils.js +0 -32
- package/src/helpers/logger.js +0 -112
- package/src/helpers/safeCall.js +0 -18
- package/src/helpers/validationHelpers.js +0 -87
- package/src/hooks/creation/useContainerActions.js +0 -89
- package/src/hooks/creation/useContainerCreation.js +0 -377
- package/src/hooks/creation/useLogsViewer.js +0 -51
- package/src/hooks/debug/useDebugLogs.js +0 -34
- package/src/hooks/navigation/useContainerSelection.js +0 -28
- package/src/hooks/useContainerCommandRouter.js +0 -123
- package/src/hooks/useContainerStats.js +0 -44
- package/src/hooks/useContainers.js +0 -26
- package/src/hooks/useControls.js +0 -263
- package/src/hooks/useEraseConfirmation.js +0 -39
- package/src/hooks/useExitHandler.js +0 -69
- package/src/index.js +0 -20
- package/test/ContainerCreationPrompt.dom.test.js +0 -313
- package/test/ControlsHUD.dom.test.js +0 -125
- package/test/SuggestionPanel.dom.test.js +0 -83
- package/test/constants.test.js +0 -41
- package/test/containerActions.test.js +0 -229
- package/test/containerList.test.js +0 -79
- package/test/containerOptionsBuilder.test.js +0 -36
- package/test/dockerHubService.test.js +0 -213
- package/test/jest.setup.js +0 -7
- package/test/logger.test.js +0 -58
- package/test/resolveImageTag.test.js +0 -26
- package/test/safeCall.test.js +0 -25
- package/test/useContainerCreation.dom.test.js +0 -567
- package/test/useContainerCreationHub.dom.test.js +0 -184
- package/test/useControls.dom.test.js +0 -519
- package/test/validationHelpers.test.js +0 -111
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import { render } from '@testing-library/react';
|
|
6
|
-
import { ContainerCreationPrompt } from '../src/components/ContainerCreationPrompt.jsx';
|
|
7
|
-
|
|
8
|
-
describe('ContainerCreationPrompt — SuggestionPanel wiring', () => {
|
|
9
|
-
test('does NOT render SuggestionPanel when step > 0', () => {
|
|
10
|
-
const { queryByText } = render(
|
|
11
|
-
<ContainerCreationPrompt
|
|
12
|
-
step={1}
|
|
13
|
-
imageName="nginx"
|
|
14
|
-
containerName=""
|
|
15
|
-
portInput=""
|
|
16
|
-
envInput=""
|
|
17
|
-
message=""
|
|
18
|
-
messageColor="yellow"
|
|
19
|
-
suggestions={['nginx', 'node']}
|
|
20
|
-
selectedSuggestionIndex={-1}
|
|
21
|
-
visibleOffset={0}
|
|
22
|
-
/>
|
|
23
|
-
);
|
|
24
|
-
// On step 1, the imageName field is not active — no suggestion panel
|
|
25
|
-
expect(queryByText(/^nginx$/)).toBeNull();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('does NOT render SuggestionPanel on step 0 when suggestions is empty', () => {
|
|
29
|
-
const { queryByTestId } = render(
|
|
30
|
-
<ContainerCreationPrompt
|
|
31
|
-
step={0}
|
|
32
|
-
imageName="ng"
|
|
33
|
-
containerName=""
|
|
34
|
-
portInput=""
|
|
35
|
-
envInput=""
|
|
36
|
-
message=""
|
|
37
|
-
messageColor="yellow"
|
|
38
|
-
suggestions={[]}
|
|
39
|
-
selectedSuggestionIndex={-1}
|
|
40
|
-
visibleOffset={0}
|
|
41
|
-
/>
|
|
42
|
-
);
|
|
43
|
-
// No suggestion panel should render when suggestions array is empty
|
|
44
|
-
expect(queryByTestId('suggestion-panel')).toBeNull();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('renders SuggestionPanel items on step 0 when suggestions exist', () => {
|
|
48
|
-
const { getAllByText, getByText } = render(
|
|
49
|
-
<ContainerCreationPrompt
|
|
50
|
-
step={0}
|
|
51
|
-
imageName="ng"
|
|
52
|
-
containerName=""
|
|
53
|
-
portInput=""
|
|
54
|
-
envInput=""
|
|
55
|
-
message=""
|
|
56
|
-
messageColor="yellow"
|
|
57
|
-
suggestions={['nginx', 'golang']}
|
|
58
|
-
selectedSuggestionIndex={-1}
|
|
59
|
-
visibleOffset={0}
|
|
60
|
-
/>
|
|
61
|
-
);
|
|
62
|
-
// golang is not in any label — appears only in suggestion panel
|
|
63
|
-
expect(getByText(/golang/)).toBeTruthy();
|
|
64
|
-
// nginx appears in suggestion panel (may also appear in label text, but it does appear)
|
|
65
|
-
const nginxEls = getAllByText(/nginx/);
|
|
66
|
-
expect(nginxEls.length).toBeGreaterThan(0);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// FR8 — hubResults wiring
|
|
70
|
-
test('when hubResults is provided, activeItems = hubResults (replaces suggestions)', () => {
|
|
71
|
-
const { getByText, queryByText } = render(
|
|
72
|
-
<ContainerCreationPrompt
|
|
73
|
-
step={0}
|
|
74
|
-
imageName="ng"
|
|
75
|
-
containerName=""
|
|
76
|
-
portInput=""
|
|
77
|
-
envInput=""
|
|
78
|
-
message=""
|
|
79
|
-
messageColor="yellow"
|
|
80
|
-
suggestions={['nginx', 'golang']}
|
|
81
|
-
hubResults={['hub-nginx', 'hub-node']}
|
|
82
|
-
selectedSuggestionIndex={-1}
|
|
83
|
-
visibleOffset={0}
|
|
84
|
-
/>
|
|
85
|
-
);
|
|
86
|
-
expect(getByText(/hub-nginx/)).toBeTruthy();
|
|
87
|
-
expect(queryByText(/golang/)).toBeNull();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('when hubResults is null, falls back to suggestions', () => {
|
|
91
|
-
const { getByText } = render(
|
|
92
|
-
<ContainerCreationPrompt
|
|
93
|
-
step={0}
|
|
94
|
-
imageName="ng"
|
|
95
|
-
containerName=""
|
|
96
|
-
portInput=""
|
|
97
|
-
envInput=""
|
|
98
|
-
message=""
|
|
99
|
-
messageColor="yellow"
|
|
100
|
-
suggestions={['nginx', 'golang']}
|
|
101
|
-
hubResults={null}
|
|
102
|
-
selectedSuggestionIndex={-1}
|
|
103
|
-
visibleOffset={0}
|
|
104
|
-
/>
|
|
105
|
-
);
|
|
106
|
-
expect(getByText(/golang/)).toBeTruthy();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('when step===0 and isSearchingHub=true, SuggestionPanel is shown even if activeItems is empty', () => {
|
|
110
|
-
const { getByText } = render(
|
|
111
|
-
<ContainerCreationPrompt
|
|
112
|
-
step={0}
|
|
113
|
-
imageName="ng"
|
|
114
|
-
containerName=""
|
|
115
|
-
portInput=""
|
|
116
|
-
envInput=""
|
|
117
|
-
message=""
|
|
118
|
-
messageColor="yellow"
|
|
119
|
-
suggestions={[]}
|
|
120
|
-
hubResults={null}
|
|
121
|
-
isSearchingHub={true}
|
|
122
|
-
selectedSuggestionIndex={-1}
|
|
123
|
-
visibleOffset={0}
|
|
124
|
-
/>
|
|
125
|
-
);
|
|
126
|
-
expect(getByText(/searching Docker Hub/)).toBeTruthy();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('when step===0 and isSearchingHub=false and activeItems is empty, SuggestionPanel is NOT shown', () => {
|
|
130
|
-
const { queryByTestId } = render(
|
|
131
|
-
<ContainerCreationPrompt
|
|
132
|
-
step={0}
|
|
133
|
-
imageName="ng"
|
|
134
|
-
containerName=""
|
|
135
|
-
portInput=""
|
|
136
|
-
envInput=""
|
|
137
|
-
message=""
|
|
138
|
-
messageColor="yellow"
|
|
139
|
-
suggestions={[]}
|
|
140
|
-
hubResults={null}
|
|
141
|
-
isSearchingHub={false}
|
|
142
|
-
selectedSuggestionIndex={-1}
|
|
143
|
-
visibleOffset={0}
|
|
144
|
-
/>
|
|
145
|
-
);
|
|
146
|
-
expect(queryByTestId('suggestion-panel')).toBeNull();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('isLoading prop is passed down to SuggestionPanel when isSearchingHub=true', () => {
|
|
150
|
-
const { getByText } = render(
|
|
151
|
-
<ContainerCreationPrompt
|
|
152
|
-
step={0}
|
|
153
|
-
imageName="ng"
|
|
154
|
-
containerName=""
|
|
155
|
-
portInput=""
|
|
156
|
-
envInput=""
|
|
157
|
-
message=""
|
|
158
|
-
messageColor="yellow"
|
|
159
|
-
suggestions={['nginx']}
|
|
160
|
-
hubResults={null}
|
|
161
|
-
isSearchingHub={true}
|
|
162
|
-
selectedSuggestionIndex={-1}
|
|
163
|
-
visibleOffset={0}
|
|
164
|
-
/>
|
|
165
|
-
);
|
|
166
|
-
expect(getByText(/searching Docker Hub/)).toBeTruthy();
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe('ContainerCreationPrompt — ControlsHUD wiring', () => {
|
|
171
|
-
test('step=0, no suggestions: renders ControlsHUD with [Tab] and Search Hub', () => {
|
|
172
|
-
const { getByText } = render(
|
|
173
|
-
<ContainerCreationPrompt
|
|
174
|
-
step={0}
|
|
175
|
-
imageName=""
|
|
176
|
-
containerName=""
|
|
177
|
-
portInput=""
|
|
178
|
-
envInput=""
|
|
179
|
-
message=""
|
|
180
|
-
messageColor="yellow"
|
|
181
|
-
suggestions={[]}
|
|
182
|
-
hubResults={null}
|
|
183
|
-
isSearchingHub={false}
|
|
184
|
-
selectedSuggestionIndex={-1}
|
|
185
|
-
visibleOffset={0}
|
|
186
|
-
/>
|
|
187
|
-
);
|
|
188
|
-
expect(getByText('[Tab]')).toBeTruthy();
|
|
189
|
-
expect(getByText(/Search Hub/)).toBeTruthy();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('does NOT render old static text "Press Enter to continue, Escape to cancel"', () => {
|
|
193
|
-
const { queryByText } = render(
|
|
194
|
-
<ContainerCreationPrompt
|
|
195
|
-
step={0}
|
|
196
|
-
imageName=""
|
|
197
|
-
containerName=""
|
|
198
|
-
portInput=""
|
|
199
|
-
envInput=""
|
|
200
|
-
message=""
|
|
201
|
-
messageColor="yellow"
|
|
202
|
-
suggestions={[]}
|
|
203
|
-
hubResults={null}
|
|
204
|
-
isSearchingHub={false}
|
|
205
|
-
selectedSuggestionIndex={-1}
|
|
206
|
-
visibleOffset={0}
|
|
207
|
-
/>
|
|
208
|
-
);
|
|
209
|
-
expect(queryByText(/Press Enter to continue, Escape to cancel/)).toBeNull();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test('step=0, hasSuggestions=true: renders [↑↓] not [Tab]', () => {
|
|
213
|
-
const { getByText, queryByText } = render(
|
|
214
|
-
<ContainerCreationPrompt
|
|
215
|
-
step={0}
|
|
216
|
-
imageName="ng"
|
|
217
|
-
containerName=""
|
|
218
|
-
portInput=""
|
|
219
|
-
envInput=""
|
|
220
|
-
message=""
|
|
221
|
-
messageColor="yellow"
|
|
222
|
-
suggestions={['nginx', 'golang']}
|
|
223
|
-
hubResults={null}
|
|
224
|
-
isSearchingHub={false}
|
|
225
|
-
selectedSuggestionIndex={-1}
|
|
226
|
-
visibleOffset={0}
|
|
227
|
-
/>
|
|
228
|
-
);
|
|
229
|
-
expect(getByText('[↑↓]')).toBeTruthy();
|
|
230
|
-
expect(queryByText('[Tab]')).toBeNull();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test('step=0, isSearchingHub=true, no suggestions: ControlsHUD does NOT render [Tab] or Navigate, DOES render [Enter] and [Esc]', () => {
|
|
234
|
-
const { queryByText, getByText } = render(
|
|
235
|
-
<ContainerCreationPrompt
|
|
236
|
-
step={0}
|
|
237
|
-
imageName="ng"
|
|
238
|
-
containerName=""
|
|
239
|
-
portInput=""
|
|
240
|
-
envInput=""
|
|
241
|
-
message=""
|
|
242
|
-
messageColor="yellow"
|
|
243
|
-
suggestions={[]}
|
|
244
|
-
hubResults={null}
|
|
245
|
-
isSearchingHub={true}
|
|
246
|
-
selectedSuggestionIndex={-1}
|
|
247
|
-
visibleOffset={0}
|
|
248
|
-
/>
|
|
249
|
-
);
|
|
250
|
-
expect(queryByText('[Tab]')).toBeNull();
|
|
251
|
-
expect(queryByText(/Navigate/)).toBeNull();
|
|
252
|
-
expect(getByText('[Enter]')).toBeTruthy();
|
|
253
|
-
expect(getByText('[Esc]')).toBeTruthy();
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test('step=1: renders [Enter] and [Esc]', () => {
|
|
257
|
-
const { getByText } = render(
|
|
258
|
-
<ContainerCreationPrompt
|
|
259
|
-
step={1}
|
|
260
|
-
imageName="nginx"
|
|
261
|
-
containerName=""
|
|
262
|
-
portInput=""
|
|
263
|
-
envInput=""
|
|
264
|
-
message=""
|
|
265
|
-
messageColor="yellow"
|
|
266
|
-
suggestions={[]}
|
|
267
|
-
selectedSuggestionIndex={-1}
|
|
268
|
-
visibleOffset={0}
|
|
269
|
-
/>
|
|
270
|
-
);
|
|
271
|
-
expect(getByText('[Enter]')).toBeTruthy();
|
|
272
|
-
expect(getByText('[Esc]')).toBeTruthy();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test('step=3, hasSuggestedEnv=true: renders [Tab] in HUD', () => {
|
|
276
|
-
const { getByText } = render(
|
|
277
|
-
<ContainerCreationPrompt
|
|
278
|
-
step={3}
|
|
279
|
-
imageName="postgres:17-alpine"
|
|
280
|
-
containerName=""
|
|
281
|
-
portInput=""
|
|
282
|
-
envInput=""
|
|
283
|
-
message=""
|
|
284
|
-
messageColor="yellow"
|
|
285
|
-
suggestions={[]}
|
|
286
|
-
selectedSuggestionIndex={-1}
|
|
287
|
-
visibleOffset={0}
|
|
288
|
-
hasSuggestedEnv={true}
|
|
289
|
-
/>
|
|
290
|
-
);
|
|
291
|
-
expect(getByText('[Tab]')).toBeTruthy();
|
|
292
|
-
expect(getByText(/Insert next env/)).toBeTruthy();
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test('step=3, hasSuggestedEnv=false: does NOT render [Tab] in HUD', () => {
|
|
296
|
-
const { queryByText } = render(
|
|
297
|
-
<ContainerCreationPrompt
|
|
298
|
-
step={3}
|
|
299
|
-
imageName="nginx:1.27-alpine"
|
|
300
|
-
containerName=""
|
|
301
|
-
portInput=""
|
|
302
|
-
envInput=""
|
|
303
|
-
message=""
|
|
304
|
-
messageColor="yellow"
|
|
305
|
-
suggestions={[]}
|
|
306
|
-
selectedSuggestionIndex={-1}
|
|
307
|
-
visibleOffset={0}
|
|
308
|
-
hasSuggestedEnv={false}
|
|
309
|
-
/>
|
|
310
|
-
);
|
|
311
|
-
expect(queryByText('[Tab]')).toBeNull();
|
|
312
|
-
});
|
|
313
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import { render } from '@testing-library/react';
|
|
6
|
-
import { getHints, ControlsHUD } from '../src/components/ControlsHUD.jsx';
|
|
7
|
-
|
|
8
|
-
describe('getHints — pure function', () => {
|
|
9
|
-
test('step=0, no suggestions, not searching: includes Tab=Search Hub', () => {
|
|
10
|
-
const hints = getHints(0, false, false);
|
|
11
|
-
const keys = hints.map(h => h.key);
|
|
12
|
-
expect(keys).toContain('Tab');
|
|
13
|
-
const tabHint = hints.find(h => h.key === 'Tab');
|
|
14
|
-
expect(tabHint.label).toBe('Search Hub');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('step=0, hasSuggestions=true: includes ↑↓ Navigate, does NOT include Tab', () => {
|
|
18
|
-
const hints = getHints(0, true, false);
|
|
19
|
-
const keys = hints.map(h => h.key);
|
|
20
|
-
expect(keys).toContain('↑↓');
|
|
21
|
-
expect(keys).not.toContain('Tab');
|
|
22
|
-
const navHint = hints.find(h => h.key === '↑↓');
|
|
23
|
-
expect(navHint.label).toBe('Navigate');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('step=0, isSearchingHub=true: does NOT include Tab, includes Enter and Esc', () => {
|
|
27
|
-
const hints = getHints(0, false, true);
|
|
28
|
-
const keys = hints.map(h => h.key);
|
|
29
|
-
expect(keys).not.toContain('Tab');
|
|
30
|
-
expect(keys).toContain('Enter');
|
|
31
|
-
expect(keys).toContain('Esc');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('step=1: includes Enter=Continue and Esc=Cancel, no Tab, no ↑↓', () => {
|
|
35
|
-
const hints = getHints(1, false, false);
|
|
36
|
-
const keys = hints.map(h => h.key);
|
|
37
|
-
expect(keys).toContain('Enter');
|
|
38
|
-
expect(keys).toContain('Esc');
|
|
39
|
-
expect(keys).not.toContain('Tab');
|
|
40
|
-
expect(keys).not.toContain('↑↓');
|
|
41
|
-
const enterHint = hints.find(h => h.key === 'Enter');
|
|
42
|
-
expect(enterHint.label).toBe('Continue');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('step=2: same as step=1', () => {
|
|
46
|
-
const hints = getHints(2, false, false);
|
|
47
|
-
const keys = hints.map(h => h.key);
|
|
48
|
-
expect(keys).toContain('Enter');
|
|
49
|
-
expect(keys).toContain('Esc');
|
|
50
|
-
expect(keys).not.toContain('Tab');
|
|
51
|
-
expect(keys).not.toContain('↑↓');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('step=3: same as step=1', () => {
|
|
55
|
-
const hints = getHints(3, false, false);
|
|
56
|
-
const keys = hints.map(h => h.key);
|
|
57
|
-
expect(keys).toContain('Enter');
|
|
58
|
-
expect(keys).toContain('Esc');
|
|
59
|
-
expect(keys).not.toContain('Tab');
|
|
60
|
-
expect(keys).not.toContain('↑↓');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('getHints(3, false, false, true) includes Tab=Insert next env', () => {
|
|
64
|
-
const hints = getHints(3, false, false, true);
|
|
65
|
-
const keys = hints.map(h => h.key);
|
|
66
|
-
expect(keys).toContain('Tab');
|
|
67
|
-
const tabHint = hints.find(h => h.key === 'Tab');
|
|
68
|
-
expect(tabHint.label).toBe('Insert next env');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('getHints(3, false, false, false) does NOT include Tab hint', () => {
|
|
72
|
-
const hints = getHints(3, false, false, false);
|
|
73
|
-
const keys = hints.map(h => h.key);
|
|
74
|
-
expect(keys).not.toContain('Tab');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('getHints(3, false, false, true) also includes Enter and Esc', () => {
|
|
78
|
-
const hints = getHints(3, false, false, true);
|
|
79
|
-
const keys = hints.map(h => h.key);
|
|
80
|
-
expect(keys).toContain('Enter');
|
|
81
|
-
expect(keys).toContain('Esc');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('ControlsHUD — component rendering', () => {
|
|
86
|
-
test('step=0, no suggestions: renders [Tab] and "Search Hub" text', () => {
|
|
87
|
-
const { getByText } = render(
|
|
88
|
-
<ControlsHUD step={0} hasSuggestions={false} isSearchingHub={false} />
|
|
89
|
-
);
|
|
90
|
-
expect(getByText('[Tab]')).toBeTruthy();
|
|
91
|
-
expect(getByText(/Search Hub/)).toBeTruthy();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('step=0, hasSuggestions=true: renders [↑↓], does NOT render [Tab]', () => {
|
|
95
|
-
const { getByText, queryByText } = render(
|
|
96
|
-
<ControlsHUD step={0} hasSuggestions={true} isSearchingHub={false} />
|
|
97
|
-
);
|
|
98
|
-
expect(getByText('[↑↓]')).toBeTruthy();
|
|
99
|
-
expect(queryByText('[Tab]')).toBeNull();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('step=1: renders [Enter] and [Esc], does NOT render [Tab]', () => {
|
|
103
|
-
const { getByText, queryByText } = render(
|
|
104
|
-
<ControlsHUD step={1} hasSuggestions={false} isSearchingHub={false} />
|
|
105
|
-
);
|
|
106
|
-
expect(getByText('[Enter]')).toBeTruthy();
|
|
107
|
-
expect(getByText('[Esc]')).toBeTruthy();
|
|
108
|
-
expect(queryByText('[Tab]')).toBeNull();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('step=3, hasSuggestedEnv=true: renders [Tab] and "Insert next env"', () => {
|
|
112
|
-
const { getByText } = render(
|
|
113
|
-
<ControlsHUD step={3} hasSuggestions={false} isSearchingHub={false} hasSuggestedEnv={true} />
|
|
114
|
-
);
|
|
115
|
-
expect(getByText('[Tab]')).toBeTruthy();
|
|
116
|
-
expect(getByText(/Insert next env/)).toBeTruthy();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('step=3, hasSuggestedEnv=false: does NOT render [Tab]', () => {
|
|
120
|
-
const { queryByText } = render(
|
|
121
|
-
<ControlsHUD step={3} hasSuggestions={false} isSearchingHub={false} hasSuggestedEnv={false} />
|
|
122
|
-
);
|
|
123
|
-
expect(queryByText('[Tab]')).toBeNull();
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import { render } from '@testing-library/react';
|
|
6
|
-
import { SuggestionPanel } from '../src/components/SuggestionPanel.jsx';
|
|
7
|
-
|
|
8
|
-
describe('SuggestionPanel', () => {
|
|
9
|
-
const items = ['caddy', 'elasticsearch', 'golang', 'httpd', 'kafka', 'mariadb', 'memcached', 'minio'];
|
|
10
|
-
|
|
11
|
-
test('renders nothing when items is empty', () => {
|
|
12
|
-
const { container } = render(
|
|
13
|
-
<SuggestionPanel items={[]} selectedIndex={-1} visibleOffset={0} maxVisible={5} />
|
|
14
|
-
);
|
|
15
|
-
expect(container.firstChild).toBeNull();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('renders only maxVisible items from visibleOffset', () => {
|
|
19
|
-
const { getByText, queryByText } = render(
|
|
20
|
-
<SuggestionPanel items={items} selectedIndex={-1} visibleOffset={0} maxVisible={5} />
|
|
21
|
-
);
|
|
22
|
-
expect(getByText(/caddy/)).toBeTruthy();
|
|
23
|
-
expect(getByText(/elasticsearch/)).toBeTruthy();
|
|
24
|
-
expect(getByText(/kafka/)).toBeTruthy();
|
|
25
|
-
expect(queryByText(/mariadb/)).toBeNull();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('respects visibleOffset by skipping early items', () => {
|
|
29
|
-
const { queryByText, getByText } = render(
|
|
30
|
-
<SuggestionPanel items={items} selectedIndex={-1} visibleOffset={2} maxVisible={3} />
|
|
31
|
-
);
|
|
32
|
-
expect(getByText(/golang/)).toBeTruthy();
|
|
33
|
-
expect(getByText(/httpd/)).toBeTruthy();
|
|
34
|
-
expect(getByText(/kafka/)).toBeTruthy();
|
|
35
|
-
expect(queryByText(/caddy/)).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('focused row has › prefix marker', () => {
|
|
39
|
-
const { getByText } = render(
|
|
40
|
-
<SuggestionPanel items={items} selectedIndex={1} visibleOffset={0} maxVisible={5} />
|
|
41
|
-
);
|
|
42
|
-
const el = getByText(/› elasticsearch/);
|
|
43
|
-
expect(el).toBeTruthy();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('non-focused rows do NOT have › prefix', () => {
|
|
47
|
-
const { getByText } = render(
|
|
48
|
-
<SuggestionPanel items={items} selectedIndex={1} visibleOffset={0} maxVisible={5} />
|
|
49
|
-
);
|
|
50
|
-
const el = getByText(/caddy/);
|
|
51
|
-
expect(el.textContent).not.toMatch(/›/);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// FR8 — isLoading prop
|
|
55
|
-
test('renders [searching Docker Hub...] row when isLoading=true', () => {
|
|
56
|
-
const { getByText } = render(
|
|
57
|
-
<SuggestionPanel items={[]} selectedIndex={-1} visibleOffset={0} isLoading={true} />
|
|
58
|
-
);
|
|
59
|
-
expect(getByText(/searching Docker Hub/)).toBeTruthy();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('loading row is NOT selectable — no › prefix on loading row', () => {
|
|
63
|
-
const { getByText } = render(
|
|
64
|
-
<SuggestionPanel items={items} selectedIndex={-1} visibleOffset={0} maxVisible={5} isLoading={true} />
|
|
65
|
-
);
|
|
66
|
-
const loadingEl = getByText(/searching Docker Hub/);
|
|
67
|
-
expect(loadingEl.textContent).not.toMatch(/›/);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('does NOT render loading row when isLoading=false', () => {
|
|
71
|
-
const { queryByText } = render(
|
|
72
|
-
<SuggestionPanel items={items} selectedIndex={-1} visibleOffset={0} maxVisible={5} isLoading={false} />
|
|
73
|
-
);
|
|
74
|
-
expect(queryByText(/searching Docker Hub/)).toBeNull();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('renders panel even when items is empty but isLoading=true', () => {
|
|
78
|
-
const { getByText } = render(
|
|
79
|
-
<SuggestionPanel items={[]} selectedIndex={-1} visibleOffset={0} isLoading={true} />
|
|
80
|
-
);
|
|
81
|
-
expect(getByText(/searching Docker Hub/)).toBeTruthy();
|
|
82
|
-
});
|
|
83
|
-
});
|
package/test/constants.test.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment node
|
|
3
|
-
*/
|
|
4
|
-
import { IMAGE_PROFILES } from '../src/helpers/constants.js';
|
|
5
|
-
|
|
6
|
-
describe('IMAGE_PROFILES', () => {
|
|
7
|
-
test('has ~20 entries', () => {
|
|
8
|
-
const count = Object.keys(IMAGE_PROFILES).length;
|
|
9
|
-
expect(count).toBeGreaterThanOrEqual(20);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('every entry has requiredEnv (array), defaultPort (string), and suggestedEnv (array)', () => {
|
|
13
|
-
for (const [key, profile] of Object.entries(IMAGE_PROFILES)) {
|
|
14
|
-
expect(Array.isArray(profile.requiredEnv)).toBe(true);
|
|
15
|
-
expect(typeof profile.defaultPort).toBe('string');
|
|
16
|
-
expect(Array.isArray(profile.suggestedEnv)).toBe(true);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('postgres has POSTGRES_PASSWORD in requiredEnv and suggestedEnv hints', () => {
|
|
21
|
-
expect(IMAGE_PROFILES.postgres.requiredEnv).toContain('POSTGRES_PASSWORD');
|
|
22
|
-
expect(IMAGE_PROFILES.postgres.suggestedEnv.length).toBeGreaterThan(0);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('nginx has empty requiredEnv, defaultPort 80, and empty suggestedEnv', () => {
|
|
26
|
-
expect(IMAGE_PROFILES.nginx.requiredEnv).toEqual([]);
|
|
27
|
-
expect(IMAGE_PROFILES.nginx.defaultPort).toBe('80');
|
|
28
|
-
expect(IMAGE_PROFILES.nginx.suggestedEnv).toEqual([]);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('node has suggestedEnv with NODE_ENV hint', () => {
|
|
32
|
-
expect(IMAGE_PROFILES.node.suggestedEnv).toContain('NODE_ENV=production');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('every entry has defaultTag as a non-empty string', () => {
|
|
36
|
-
for (const [key, profile] of Object.entries(IMAGE_PROFILES)) {
|
|
37
|
-
expect(typeof profile.defaultTag).toBe('string');
|
|
38
|
-
expect(profile.defaultTag.length).toBeGreaterThan(0);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
});
|