cdd-cli 3.2.2 → 4.0.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/.~lock.ROADMAP.md# +1 -0
- package/CHANGELOG.md +51 -0
- package/README.es.md +198 -0
- package/README.md +81 -23
- package/ROADMAP.md +647 -0
- package/coverage/clover.xml +400 -519
- package/coverage/coverage-final.json +16 -20
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
- package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +31 -13
- package/coverage/lcov-report/components/PromptField.jsx.html +4 -4
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +37 -13
- package/coverage/lcov-report/components/index.html +14 -14
- package/coverage/lcov-report/helpers/constants.js.html +87 -33
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +40 -40
- package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +1 -1
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +1 -1
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +1 -1
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +18 -18
- package/coverage/lcov-report/helpers/index.html +77 -62
- package/coverage/lcov-report/helpers/logger.js.html +60 -60
- package/coverage/lcov-report/helpers/safeCall.js.html +14 -14
- package/coverage/lcov-report/helpers/validationHelpers.js.html +52 -52
- package/coverage/lcov-report/hooks/creation/index.html +19 -19
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +4 -4
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +225 -48
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +5 -5
- package/coverage/lcov-report/hooks/debug/index.html +1 -1
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +7 -7
- package/coverage/lcov-report/hooks/index.html +15 -15
- package/coverage/lcov-report/hooks/navigation/index.html +1 -1
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +6 -6
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +13 -13
- package/coverage/lcov-report/hooks/useControls.js.html +82 -40
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +5 -5
- package/coverage/lcov-report/hooks/useExitHandler.js.html +6 -6
- package/coverage/lcov-report/index.html +38 -68
- package/coverage/lcov.info +742 -981
- package/dist/App.js +7 -1
- package/dist/components/ContainerCreationPrompt.js +26 -10
- package/dist/components/ControlsHUD.js +106 -0
- package/dist/components/SuggestionPanel.js +16 -7
- package/dist/helpers/constants.js +60 -21
- package/dist/helpers/dockerHubService.js +111 -0
- package/dist/helpers/logger.js +1 -1
- package/dist/hooks/creation/useContainerActions.js +22 -5
- package/dist/hooks/creation/useContainerCreation.js +395 -107
- package/dist/hooks/useContainerCommandRouter.js +1 -1
- package/dist/hooks/useControls.js +23 -7
- package/dist/index.js +7 -0
- package/package.json +1 -1
- package/src/App.jsx +6 -0
- package/src/components/ContainerCreationPrompt.jsx +15 -5
- package/src/components/ControlsHUD.jsx +66 -0
- package/src/components/SuggestionPanel.jsx +10 -2
- package/src/helpers/constants.js +38 -20
- package/src/helpers/dockerHubService.js +75 -0
- package/src/helpers/logger.js +1 -2
- package/src/hooks/creation/useContainerActions.js +19 -6
- package/src/hooks/creation/useContainerCreation.js +224 -87
- package/src/hooks/useContainerCommandRouter.js +1 -1
- package/src/hooks/useControls.js +24 -8
- package/src/index.js +4 -0
- package/test/ContainerCreationPrompt.dom.test.js +245 -0
- package/test/ControlsHUD.dom.test.js +125 -0
- package/test/SuggestionPanel.dom.test.js +30 -0
- package/test/constants.test.js +7 -0
- package/test/dockerHubService.test.js +213 -0
- package/test/logger.test.js +58 -0
- package/test/resolveImageTag.test.js +26 -0
- package/test/useContainerCreation.dom.test.js +306 -3
- package/test/useContainerCreationHub.dom.test.js +184 -0
- package/test/useControls.dom.test.js +212 -6
|
@@ -38,8 +38,8 @@ await jest.unstable_mockModule(
|
|
|
38
38
|
|
|
39
39
|
const { useControls } = await import('../src/hooks/useControls.js');
|
|
40
40
|
|
|
41
|
-
function HookTester({ containers, expose }) {
|
|
42
|
-
const hook = useControls(containers);
|
|
41
|
+
function HookTester({ containers, expose, overrides }) {
|
|
42
|
+
const hook = useControls(containers, overrides);
|
|
43
43
|
useEffect(() => {
|
|
44
44
|
if (expose) expose.current = hook;
|
|
45
45
|
});
|
|
@@ -148,7 +148,7 @@ describe('useControls — step 0 keyboard routing integration', () => {
|
|
|
148
148
|
act(() => { expose.current.creation.moveSuggestionSelection(1); });
|
|
149
149
|
act(() => { expose.current.creation.applyFocusedSuggestion(); });
|
|
150
150
|
|
|
151
|
-
expect(expose.current.creation.imageName).toBe('nginx');
|
|
151
|
+
expect(expose.current.creation.imageName).toBe('nginx:1.27-alpine');
|
|
152
152
|
expect(expose.current.creation.step).toBe(0);
|
|
153
153
|
expect(expose.current.creation.suggestions).toHaveLength(0);
|
|
154
154
|
});
|
|
@@ -302,12 +302,218 @@ describe('useControls — FR4/FR5/FR6 keyboard routing via processCreationInput'
|
|
|
302
302
|
const expose = renderAndStartCreation();
|
|
303
303
|
|
|
304
304
|
act(() => { expose.current.creation.setImageName('nginx'); });
|
|
305
|
-
const nameBefore = expose.current.creation.imageName;
|
|
306
305
|
|
|
307
306
|
act(() => { triggerInput('\r', {}); }); // advance to step 1
|
|
308
307
|
|
|
309
|
-
//
|
|
310
|
-
|
|
308
|
+
// nextStep resolves the tag — nginx becomes nginx:1.27-alpine; step advances to 1
|
|
309
|
+
// applyFocusedSuggestion was NOT called (no suggestion was focused)
|
|
310
|
+
expect(expose.current.creation.imageName).toBe('nginx:1.27-alpine');
|
|
311
311
|
expect(expose.current.creation.step).toBe(1);
|
|
312
312
|
});
|
|
313
313
|
});
|
|
314
|
+
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
// Tab on step 3 → insertNextSuggestedEnv
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
describe('useControls — Tab on step 3 calls insertNextSuggestedEnv', () => {
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
_inputHandler = null;
|
|
321
|
+
mockSvcCreateContainer.mockReset();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('Tab on step 3 calls insertNextSuggestedEnv()', () => {
|
|
325
|
+
const mockInsertNextSuggestedEnv = jest.fn();
|
|
326
|
+
const expose = { current: null };
|
|
327
|
+
render(
|
|
328
|
+
<HookTester
|
|
329
|
+
containers={[]}
|
|
330
|
+
expose={expose}
|
|
331
|
+
overrides={{ insertNextSuggestedEnv: mockInsertNextSuggestedEnv }}
|
|
332
|
+
/>
|
|
333
|
+
);
|
|
334
|
+
// Enter creation mode
|
|
335
|
+
act(() => { triggerInput('c', {}); });
|
|
336
|
+
// Advance to step 3
|
|
337
|
+
act(() => { expose.current.creation.setImageName('postgres'); });
|
|
338
|
+
act(() => { triggerInput('\r', {}); }); // step 0 → 1
|
|
339
|
+
act(() => { triggerInput('\r', {}); }); // step 1 → 2
|
|
340
|
+
act(() => { triggerInput('\r', {}); }); // step 2 → 3
|
|
341
|
+
expect(expose.current.creation.step).toBe(3);
|
|
342
|
+
|
|
343
|
+
act(() => { triggerInput('', { tab: true }); });
|
|
344
|
+
|
|
345
|
+
expect(mockInsertNextSuggestedEnv).toHaveBeenCalledTimes(1);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('Tab on step 3 when insertNextSuggestedEnv is undefined → no-op (safe guard)', () => {
|
|
349
|
+
const expose = { current: null };
|
|
350
|
+
render(
|
|
351
|
+
<HookTester
|
|
352
|
+
containers={[]}
|
|
353
|
+
expose={expose}
|
|
354
|
+
overrides={{ insertNextSuggestedEnv: undefined }}
|
|
355
|
+
/>
|
|
356
|
+
);
|
|
357
|
+
act(() => { triggerInput('c', {}); });
|
|
358
|
+
act(() => { expose.current.creation.setImageName('nginx'); });
|
|
359
|
+
act(() => { triggerInput('\r', {}); });
|
|
360
|
+
act(() => { triggerInput('\r', {}); });
|
|
361
|
+
act(() => { triggerInput('\r', {}); });
|
|
362
|
+
|
|
363
|
+
// Should not throw
|
|
364
|
+
expect(() => {
|
|
365
|
+
act(() => { triggerInput('', { tab: true }); });
|
|
366
|
+
}).not.toThrow();
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
describe('useControls — FR7 Tab triggers hub search on step 0', () => {
|
|
370
|
+
beforeEach(() => {
|
|
371
|
+
_inputHandler = null;
|
|
372
|
+
mockSvcCreateContainer.mockReset();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
/** Helper: render in creation mode with a triggerHubSearch mock override */
|
|
376
|
+
function renderCreationWithSearch({ isSearchingHub = false, imageName: initialImageName = '' } = {}) {
|
|
377
|
+
const mockTriggerHubSearch = jest.fn();
|
|
378
|
+
const expose = { current: null };
|
|
379
|
+
|
|
380
|
+
render(
|
|
381
|
+
<HookTester
|
|
382
|
+
containers={[]}
|
|
383
|
+
expose={expose}
|
|
384
|
+
overrides={{ triggerHubSearch: mockTriggerHubSearch, isSearchingHub }}
|
|
385
|
+
/>
|
|
386
|
+
);
|
|
387
|
+
// Enter creation mode
|
|
388
|
+
act(() => { triggerInput('c', {}); });
|
|
389
|
+
// Set imageName if provided
|
|
390
|
+
if (initialImageName) {
|
|
391
|
+
act(() => { expose.current.creation.setImageName(initialImageName); });
|
|
392
|
+
}
|
|
393
|
+
return { expose, mockTriggerHubSearch };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
test('FR7 — Tab on step 0 calls triggerHubSearch()', () => {
|
|
397
|
+
const { mockTriggerHubSearch } = renderCreationWithSearch({ imageName: 'nginx' });
|
|
398
|
+
|
|
399
|
+
act(() => { triggerInput('', { tab: true }); });
|
|
400
|
+
|
|
401
|
+
expect(mockTriggerHubSearch).toHaveBeenCalledTimes(1);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('FR7 — Tab on step 0 when isSearchingHub=true does NOT call triggerHubSearch (guard)', () => {
|
|
405
|
+
const { mockTriggerHubSearch } = renderCreationWithSearch({
|
|
406
|
+
isSearchingHub: true,
|
|
407
|
+
imageName: 'nginx',
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
act(() => { triggerInput('', { tab: true }); });
|
|
411
|
+
|
|
412
|
+
expect(mockTriggerHubSearch).not.toHaveBeenCalled();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('FR7 — Tab on step 0 when imageName is empty string does NOT call triggerHubSearch (guard)', () => {
|
|
416
|
+
const { mockTriggerHubSearch } = renderCreationWithSearch({ imageName: '' });
|
|
417
|
+
|
|
418
|
+
act(() => { triggerInput('', { tab: true }); });
|
|
419
|
+
|
|
420
|
+
expect(mockTriggerHubSearch).not.toHaveBeenCalled();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('FR7 — Tab on step > 0 does NOT call triggerHubSearch (only active on step 0)', () => {
|
|
424
|
+
const { expose, mockTriggerHubSearch } = renderCreationWithSearch({ imageName: 'nginx' });
|
|
425
|
+
|
|
426
|
+
// Advance to step 1
|
|
427
|
+
act(() => { triggerInput('\r', {}); });
|
|
428
|
+
expect(expose.current.creation.step).toBe(1);
|
|
429
|
+
|
|
430
|
+
act(() => { triggerInput('', { tab: true }); });
|
|
431
|
+
|
|
432
|
+
expect(mockTriggerHubSearch).not.toHaveBeenCalled();
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('useControls — success/error message clears after 4000ms', () => {
|
|
437
|
+
|
|
438
|
+
test('success message from container creation is scheduled to clear (setTimedMessage called)', async () => {
|
|
439
|
+
const timeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
440
|
+
mockSvcCreateContainer.mockResolvedValue({ id: 'cid-timer', ports: [] });
|
|
441
|
+
|
|
442
|
+
const expose = { current: null };
|
|
443
|
+
render(<HookTester containers={[]} expose={expose} />);
|
|
444
|
+
|
|
445
|
+
await act(async () => {
|
|
446
|
+
await completeCreationWizard(expose, 'nginx');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// setTimedMessage must have scheduled a timeout to clear the message
|
|
450
|
+
const clearCallArgs = timeoutSpy.mock.calls.find(
|
|
451
|
+
([fn, delay]) => delay === 4000
|
|
452
|
+
);
|
|
453
|
+
expect(clearCallArgs).toBeDefined();
|
|
454
|
+
timeoutSpy.mockRestore();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('error message from container creation is scheduled to clear (setTimedMessage called)', async () => {
|
|
458
|
+
const timeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
459
|
+
mockSvcCreateContainer.mockRejectedValue(new Error('not found'));
|
|
460
|
+
|
|
461
|
+
const expose = { current: null };
|
|
462
|
+
render(<HookTester containers={[]} expose={expose} />);
|
|
463
|
+
|
|
464
|
+
await act(async () => {
|
|
465
|
+
await completeCreationWizard(expose, 'badimage');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const clearCallArgs = timeoutSpy.mock.calls.find(
|
|
469
|
+
([fn, delay]) => delay === 4000
|
|
470
|
+
);
|
|
471
|
+
expect(clearCallArgs).toBeDefined();
|
|
472
|
+
timeoutSpy.mockRestore();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
describe('useControls — pressing C calls resetCreation BEFORE onStartCreate', () => {
|
|
477
|
+
beforeEach(() => {
|
|
478
|
+
_inputHandler = null;
|
|
479
|
+
mockSvcCreateContainer.mockReset();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('pressing C resets creation state before entering creation mode', () => {
|
|
483
|
+
const callOrder = [];
|
|
484
|
+
const expose = { current: null };
|
|
485
|
+
|
|
486
|
+
// We render with no overrides, but we can spy on the internal calls
|
|
487
|
+
// by checking that imageName is reset (empty) when creatingContainer becomes true.
|
|
488
|
+
// Strategy: set imageName to something, then press C, verify state is reset in same render.
|
|
489
|
+
render(<HookTester containers={[]} expose={expose} />);
|
|
490
|
+
|
|
491
|
+
// Give the hook an existing state to verify it gets reset
|
|
492
|
+
act(() => { triggerInput('c', {}); }); // enter creation mode
|
|
493
|
+
act(() => { expose.current.creation.setImageName('dirty-state'); });
|
|
494
|
+
|
|
495
|
+
// Exit creation mode and re-enter via C to test reset order
|
|
496
|
+
act(() => {
|
|
497
|
+
// Press escape to cancel creation
|
|
498
|
+
if (_inputHandler) _inputHandler('', { escape: true });
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Now press C again — this should reset THEN show creation mode
|
|
502
|
+
act(() => { triggerInput('c', {}); });
|
|
503
|
+
|
|
504
|
+
// After pressing C: creatingContainer should be true AND imageName should be reset to ''
|
|
505
|
+
expect(expose.current.creatingContainer).toBe(true);
|
|
506
|
+
// imageName is '' (from resetCreation), NOT 'dirty-state'
|
|
507
|
+
expect(expose.current.creation.imageName).toBe('');
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test('after pressing C, creation.message is set to the wizard prompt (resetCreation ran)', () => {
|
|
511
|
+
const expose = { current: null };
|
|
512
|
+
render(<HookTester containers={[]} expose={expose} />);
|
|
513
|
+
|
|
514
|
+
act(() => { triggerInput('c', {}); });
|
|
515
|
+
|
|
516
|
+
expect(expose.current.creatingContainer).toBe(true);
|
|
517
|
+
expect(expose.current.creation.message).toBe('Insert the name of the image to create: ');
|
|
518
|
+
});
|
|
519
|
+
});
|