@camaradesuk/git-worktree-tools 1.8.0 → 1.10.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 +48 -27
- package/dist/cli/cleanpr.js +74 -53
- package/dist/cli/cleanpr.js.map +1 -1
- package/dist/cli/cleanpr.test.js +2 -0
- package/dist/cli/cleanpr.test.js.map +1 -1
- package/dist/cli/lswt.js +32 -56
- package/dist/cli/lswt.js.map +1 -1
- package/dist/cli/lswt.test.js +17 -27
- package/dist/cli/lswt.test.js.map +1 -1
- package/dist/cli/newpr.d.ts +13 -1
- package/dist/cli/newpr.d.ts.map +1 -1
- package/dist/cli/newpr.js +350 -151
- package/dist/cli/newpr.js.map +1 -1
- package/dist/cli/newpr.test.js +314 -5
- package/dist/cli/newpr.test.js.map +1 -1
- package/dist/cli/prs.d.ts +3 -10
- package/dist/cli/prs.d.ts.map +1 -1
- package/dist/cli/prs.js +6 -168
- package/dist/cli/prs.js.map +1 -1
- package/dist/cli/prs.test.js +55 -0
- package/dist/cli/prs.test.js.map +1 -1
- package/dist/cli/wt/clean.d.ts +6 -2
- package/dist/cli/wt/clean.d.ts.map +1 -1
- package/dist/cli/wt/clean.js +401 -20
- package/dist/cli/wt/clean.js.map +1 -1
- package/dist/cli/wt/clean.test.d.ts +8 -0
- package/dist/cli/wt/clean.test.d.ts.map +1 -0
- package/dist/cli/wt/clean.test.js +624 -0
- package/dist/cli/wt/clean.test.js.map +1 -0
- package/dist/cli/wt/completion.d.ts +3 -0
- package/dist/cli/wt/completion.d.ts.map +1 -1
- package/dist/cli/wt/completion.js +80 -9
- package/dist/cli/wt/completion.js.map +1 -1
- package/dist/cli/wt/completion.test.js +102 -0
- package/dist/cli/wt/completion.test.js.map +1 -1
- package/dist/cli/wt/config.d.ts +3 -1
- package/dist/cli/wt/config.d.ts.map +1 -1
- package/dist/cli/wt/config.js +323 -32
- package/dist/cli/wt/config.js.map +1 -1
- package/dist/cli/wt/config.test.d.ts +2 -0
- package/dist/cli/wt/config.test.d.ts.map +1 -1
- package/dist/cli/wt/config.test.js +206 -26
- package/dist/cli/wt/config.test.js.map +1 -1
- package/dist/cli/wt/interactive-menu.d.ts +2 -0
- package/dist/cli/wt/interactive-menu.d.ts.map +1 -1
- package/dist/cli/wt/interactive-menu.js +346 -73
- package/dist/cli/wt/interactive-menu.js.map +1 -1
- package/dist/cli/wt/interactive-menu.test.d.ts +4 -2
- package/dist/cli/wt/interactive-menu.test.d.ts.map +1 -1
- package/dist/cli/wt/interactive-menu.test.js +383 -323
- package/dist/cli/wt/interactive-menu.test.js.map +1 -1
- package/dist/cli/wt/link.d.ts +3 -1
- package/dist/cli/wt/link.d.ts.map +1 -1
- package/dist/cli/wt/link.js +125 -38
- package/dist/cli/wt/link.js.map +1 -1
- package/dist/cli/wt/list.d.ts +4 -1
- package/dist/cli/wt/list.d.ts.map +1 -1
- package/dist/cli/wt/list.js +85 -16
- package/dist/cli/wt/list.js.map +1 -1
- package/dist/cli/wt/list.test.d.ts +10 -0
- package/dist/cli/wt/list.test.d.ts.map +1 -0
- package/dist/cli/wt/list.test.js +157 -0
- package/dist/cli/wt/list.test.js.map +1 -0
- package/dist/cli/wt/new.d.ts +8 -2
- package/dist/cli/wt/new.d.ts.map +1 -1
- package/dist/cli/wt/new.js +91 -46
- package/dist/cli/wt/new.js.map +1 -1
- package/dist/cli/wt/prs.d.ts +2 -1
- package/dist/cli/wt/prs.d.ts.map +1 -1
- package/dist/cli/wt/prs.js +3 -164
- package/dist/cli/wt/prs.js.map +1 -1
- package/dist/cli/wt/run-command.d.ts +4 -2
- package/dist/cli/wt/run-command.d.ts.map +1 -1
- package/dist/cli/wt/run-command.js +6 -4
- package/dist/cli/wt/run-command.js.map +1 -1
- package/dist/cli/wt/state.d.ts +3 -1
- package/dist/cli/wt/state.d.ts.map +1 -1
- package/dist/cli/wt/state.js +74 -10
- package/dist/cli/wt/state.js.map +1 -1
- package/dist/cli/wt/state.test.d.ts +9 -0
- package/dist/cli/wt/state.test.d.ts.map +1 -0
- package/dist/cli/wt/state.test.js +127 -0
- package/dist/cli/wt/state.test.js.map +1 -0
- package/dist/cli/wt/wt.test.d.ts +2 -2
- package/dist/cli/wt/wt.test.js +430 -212
- package/dist/cli/wt/wt.test.js.map +1 -1
- package/dist/cli/wt.d.ts.map +1 -1
- package/dist/cli/wt.js +50 -36
- package/dist/cli/wt.js.map +1 -1
- package/dist/cli/wt.unit.test.js +16 -38
- package/dist/cli/wt.unit.test.js.map +1 -1
- package/dist/cli/wtconfig.d.ts +1 -0
- package/dist/cli/wtconfig.d.ts.map +1 -1
- package/dist/cli/wtconfig.js +213 -21
- package/dist/cli/wtconfig.js.map +1 -1
- package/dist/cli/wtconfig.test.js +3 -0
- package/dist/cli/wtconfig.test.js.map +1 -1
- package/dist/cli/wtlink.js +116 -73
- package/dist/cli/wtlink.js.map +1 -1
- package/dist/cli/wtstate.js +21 -2
- package/dist/cli/wtstate.js.map +1 -1
- package/dist/e2e/wt/interactive-menu.e2e.test.js +17 -17
- package/dist/e2e/wt/interactive-menu.e2e.test.js.map +1 -1
- package/dist/lib/ai/types.d.ts +12 -0
- package/dist/lib/ai/types.d.ts.map +1 -1
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/cleanpr/args.d.ts.map +1 -1
- package/dist/lib/cleanpr/args.js +20 -0
- package/dist/lib/cleanpr/args.js.map +1 -1
- package/dist/lib/cleanpr/types.d.ts +6 -0
- package/dist/lib/cleanpr/types.d.ts.map +1 -1
- package/dist/lib/cleanpr/worktree-info.d.ts.map +1 -1
- package/dist/lib/cleanpr/worktree-info.js +1 -6
- package/dist/lib/cleanpr/worktree-info.js.map +1 -1
- package/dist/lib/cleanpr/worktree-info.test.js +10 -13
- package/dist/lib/cleanpr/worktree-info.test.js.map +1 -1
- package/dist/lib/colors.d.ts +5 -0
- package/dist/lib/colors.d.ts.map +1 -1
- package/dist/lib/colors.js +13 -6
- package/dist/lib/colors.js.map +1 -1
- package/dist/lib/config-editor.d.ts.map +1 -1
- package/dist/lib/config-editor.js.map +1 -1
- package/dist/lib/config-migration/detector.d.ts +25 -0
- package/dist/lib/config-migration/detector.d.ts.map +1 -0
- package/dist/lib/config-migration/detector.js +372 -0
- package/dist/lib/config-migration/detector.js.map +1 -0
- package/dist/lib/config-migration/detector.test.d.ts +5 -0
- package/dist/lib/config-migration/detector.test.d.ts.map +1 -0
- package/dist/lib/config-migration/detector.test.js +201 -0
- package/dist/lib/config-migration/detector.test.js.map +1 -0
- package/dist/lib/config-migration/index.d.ts +29 -0
- package/dist/lib/config-migration/index.d.ts.map +1 -0
- package/dist/lib/config-migration/index.js +33 -0
- package/dist/lib/config-migration/index.js.map +1 -0
- package/dist/lib/config-migration/reporter.d.ts +53 -0
- package/dist/lib/config-migration/reporter.d.ts.map +1 -0
- package/dist/lib/config-migration/reporter.js +257 -0
- package/dist/lib/config-migration/reporter.js.map +1 -0
- package/dist/lib/config-migration/reporter.test.d.ts +5 -0
- package/dist/lib/config-migration/reporter.test.d.ts.map +1 -0
- package/dist/lib/config-migration/reporter.test.js +305 -0
- package/dist/lib/config-migration/reporter.test.js.map +1 -0
- package/dist/lib/config-migration/runner.d.ts +46 -0
- package/dist/lib/config-migration/runner.d.ts.map +1 -0
- package/dist/lib/config-migration/runner.js +364 -0
- package/dist/lib/config-migration/runner.js.map +1 -0
- package/dist/lib/config-migration/runner.test.d.ts +5 -0
- package/dist/lib/config-migration/runner.test.d.ts.map +1 -0
- package/dist/lib/config-migration/runner.test.js +235 -0
- package/dist/lib/config-migration/runner.test.js.map +1 -0
- package/dist/lib/config-migration/types.d.ts +120 -0
- package/dist/lib/config-migration/types.d.ts.map +1 -0
- package/dist/lib/config-migration/types.js +70 -0
- package/dist/lib/config-migration/types.js.map +1 -0
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config-validation.js +6 -0
- package/dist/lib/config-validation.js.map +1 -1
- package/dist/lib/config-validation.test.js +25 -0
- package/dist/lib/config-validation.test.js.map +1 -1
- package/dist/lib/config.d.ts +31 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +2 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +3 -15
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/constants.d.ts +12 -4
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +24 -5
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.js +88 -29
- package/dist/lib/constants.test.js.map +1 -1
- package/dist/lib/deprecation.d.ts +18 -0
- package/dist/lib/deprecation.d.ts.map +1 -0
- package/dist/lib/deprecation.js +28 -0
- package/dist/lib/deprecation.js.map +1 -0
- package/dist/lib/deprecation.test.d.ts +2 -0
- package/dist/lib/deprecation.test.d.ts.map +1 -0
- package/dist/lib/deprecation.test.js +71 -0
- package/dist/lib/deprecation.test.js.map +1 -0
- package/dist/lib/hooks/confirmation.d.ts +49 -0
- package/dist/lib/hooks/confirmation.d.ts.map +1 -0
- package/dist/lib/hooks/confirmation.js +147 -0
- package/dist/lib/hooks/confirmation.js.map +1 -0
- package/dist/lib/hooks/confirmation.test.d.ts +7 -0
- package/dist/lib/hooks/confirmation.test.d.ts.map +1 -0
- package/dist/lib/hooks/confirmation.test.js +300 -0
- package/dist/lib/hooks/confirmation.test.js.map +1 -0
- package/dist/lib/hooks/executor.d.ts +16 -1
- package/dist/lib/hooks/executor.d.ts.map +1 -1
- package/dist/lib/hooks/executor.js +53 -4
- package/dist/lib/hooks/executor.js.map +1 -1
- package/dist/lib/hooks/index.d.ts +4 -2
- package/dist/lib/hooks/index.d.ts.map +1 -1
- package/dist/lib/hooks/index.js +3 -2
- package/dist/lib/hooks/index.js.map +1 -1
- package/dist/lib/hooks/types.d.ts +16 -0
- package/dist/lib/hooks/types.d.ts.map +1 -1
- package/dist/lib/hooks/types.js +12 -0
- package/dist/lib/hooks/types.js.map +1 -1
- package/dist/lib/logger.d.ts +40 -155
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +349 -420
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/logger.test.d.ts +10 -1
- package/dist/lib/logger.test.d.ts.map +1 -1
- package/dist/lib/logger.test.js +658 -258
- package/dist/lib/logger.test.js.map +1 -1
- package/dist/lib/lswt/action-executors.d.ts +2 -0
- package/dist/lib/lswt/action-executors.d.ts.map +1 -1
- package/dist/lib/lswt/action-executors.js +4 -3
- package/dist/lib/lswt/action-executors.js.map +1 -1
- package/dist/lib/lswt/action-executors.test.js +7 -0
- package/dist/lib/lswt/action-executors.test.js.map +1 -1
- package/dist/lib/lswt/args.d.ts.map +1 -1
- package/dist/lib/lswt/args.js +15 -1
- package/dist/lib/lswt/args.js.map +1 -1
- package/dist/lib/lswt/environment.d.ts +21 -2
- package/dist/lib/lswt/environment.d.ts.map +1 -1
- package/dist/lib/lswt/environment.js +73 -32
- package/dist/lib/lswt/environment.js.map +1 -1
- package/dist/lib/lswt/environment.test.js +79 -1
- package/dist/lib/lswt/environment.test.js.map +1 -1
- package/dist/lib/lswt/index.d.ts +1 -0
- package/dist/lib/lswt/index.d.ts.map +1 -1
- package/dist/lib/lswt/index.js +2 -0
- package/dist/lib/lswt/index.js.map +1 -1
- package/dist/lib/lswt/table.d.ts +15 -0
- package/dist/lib/lswt/table.d.ts.map +1 -0
- package/dist/lib/lswt/table.js +61 -0
- package/dist/lib/lswt/table.js.map +1 -0
- package/dist/lib/lswt/table.test.d.ts +5 -0
- package/dist/lib/lswt/table.test.d.ts.map +1 -0
- package/dist/lib/lswt/table.test.js +262 -0
- package/dist/lib/lswt/table.test.js.map +1 -0
- package/dist/lib/lswt/types.d.ts +4 -0
- package/dist/lib/lswt/types.d.ts.map +1 -1
- package/dist/lib/lswt/worktree-info.d.ts.map +1 -1
- package/dist/lib/lswt/worktree-info.js +1 -6
- package/dist/lib/lswt/worktree-info.js.map +1 -1
- package/dist/lib/lswt/worktree-info.test.js +5 -17
- package/dist/lib/lswt/worktree-info.test.js.map +1 -1
- package/dist/lib/newpr/args.d.ts.map +1 -1
- package/dist/lib/newpr/args.js +36 -1
- package/dist/lib/newpr/args.js.map +1 -1
- package/dist/lib/newpr/hook-runner.d.ts +11 -0
- package/dist/lib/newpr/hook-runner.d.ts.map +1 -1
- package/dist/lib/newpr/hook-runner.js +49 -1
- package/dist/lib/newpr/hook-runner.js.map +1 -1
- package/dist/lib/newpr/hook-runner.test.js +121 -0
- package/dist/lib/newpr/hook-runner.test.js.map +1 -1
- package/dist/lib/newpr/plan-generator.d.ts +121 -0
- package/dist/lib/newpr/plan-generator.d.ts.map +1 -0
- package/dist/lib/newpr/plan-generator.js +185 -0
- package/dist/lib/newpr/plan-generator.js.map +1 -0
- package/dist/lib/newpr/plan-generator.test.d.ts +7 -0
- package/dist/lib/newpr/plan-generator.test.d.ts.map +1 -0
- package/dist/lib/newpr/plan-generator.test.js +387 -0
- package/dist/lib/newpr/plan-generator.test.js.map +1 -0
- package/dist/lib/newpr/types.d.ts +12 -0
- package/dist/lib/newpr/types.d.ts.map +1 -1
- package/dist/lib/prs/actions.d.ts +5 -1
- package/dist/lib/prs/actions.d.ts.map +1 -1
- package/dist/lib/prs/actions.js +12 -10
- package/dist/lib/prs/actions.js.map +1 -1
- package/dist/lib/prs/actions.test.js +48 -5
- package/dist/lib/prs/actions.test.js.map +1 -1
- package/dist/lib/prs/command.d.ts +21 -0
- package/dist/lib/prs/command.d.ts.map +1 -0
- package/dist/lib/prs/command.js +175 -0
- package/dist/lib/prs/command.js.map +1 -0
- package/dist/lib/prs/command.test.d.ts +11 -0
- package/dist/lib/prs/command.test.d.ts.map +1 -0
- package/dist/lib/prs/command.test.js +409 -0
- package/dist/lib/prs/command.test.js.map +1 -0
- package/dist/lib/prs/interactive.d.ts.map +1 -1
- package/dist/lib/prs/interactive.js +15 -2
- package/dist/lib/prs/interactive.js.map +1 -1
- package/dist/lib/prs/interactive.test.js +153 -0
- package/dist/lib/prs/interactive.test.js.map +1 -1
- package/dist/lib/prs/types.d.ts +15 -0
- package/dist/lib/prs/types.d.ts.map +1 -1
- package/dist/lib/ui/error.d.ts +31 -0
- package/dist/lib/ui/error.d.ts.map +1 -0
- package/dist/lib/ui/error.js +47 -0
- package/dist/lib/ui/error.js.map +1 -0
- package/dist/lib/ui/error.test.d.ts +2 -0
- package/dist/lib/ui/error.test.d.ts.map +1 -0
- package/dist/lib/ui/error.test.js +143 -0
- package/dist/lib/ui/error.test.js.map +1 -0
- package/dist/lib/ui/index.d.ts +15 -0
- package/dist/lib/ui/index.d.ts.map +1 -0
- package/dist/lib/ui/index.js +19 -0
- package/dist/lib/ui/index.js.map +1 -0
- package/dist/lib/ui/output.d.ts +18 -0
- package/dist/lib/ui/output.d.ts.map +1 -0
- package/dist/lib/ui/output.js +31 -0
- package/dist/lib/ui/output.js.map +1 -0
- package/dist/lib/ui/output.test.d.ts +2 -0
- package/dist/lib/ui/output.test.d.ts.map +1 -0
- package/dist/lib/ui/output.test.js +59 -0
- package/dist/lib/ui/output.test.js.map +1 -0
- package/dist/lib/ui/spinner.d.ts +10 -0
- package/dist/lib/ui/spinner.d.ts.map +1 -0
- package/dist/lib/ui/spinner.js +10 -0
- package/dist/lib/ui/spinner.js.map +1 -0
- package/dist/lib/ui/status.d.ts +65 -0
- package/dist/lib/ui/status.d.ts.map +1 -0
- package/dist/lib/ui/status.js +100 -0
- package/dist/lib/ui/status.js.map +1 -0
- package/dist/lib/ui/status.test.d.ts +2 -0
- package/dist/lib/ui/status.test.d.ts.map +1 -0
- package/dist/lib/ui/status.test.js +158 -0
- package/dist/lib/ui/status.test.js.map +1 -0
- package/dist/lib/ui/table.d.ts +39 -0
- package/dist/lib/ui/table.d.ts.map +1 -0
- package/dist/lib/ui/table.js +45 -0
- package/dist/lib/ui/table.js.map +1 -0
- package/dist/lib/ui/table.test.d.ts +2 -0
- package/dist/lib/ui/table.test.d.ts.map +1 -0
- package/dist/lib/ui/table.test.js +115 -0
- package/dist/lib/ui/table.test.js.map +1 -0
- package/dist/lib/ui/theme.d.ts +34 -0
- package/dist/lib/ui/theme.d.ts.map +1 -0
- package/dist/lib/ui/theme.js +37 -0
- package/dist/lib/ui/theme.js.map +1 -0
- package/dist/lib/ui/theme.test.d.ts +2 -0
- package/dist/lib/ui/theme.test.d.ts.map +1 -0
- package/dist/lib/ui/theme.test.js +76 -0
- package/dist/lib/ui/theme.test.js.map +1 -0
- package/dist/lib/wtconfig/environment.d.ts +18 -1
- package/dist/lib/wtconfig/environment.d.ts.map +1 -1
- package/dist/lib/wtconfig/environment.js +60 -24
- package/dist/lib/wtconfig/environment.js.map +1 -1
- package/dist/lib/wtconfig/environment.test.js +45 -1
- package/dist/lib/wtconfig/environment.test.js.map +1 -1
- package/dist/lib/wtlink/config-manifest.test.js +26 -0
- package/dist/lib/wtlink/config-manifest.test.js.map +1 -1
- package/dist/lib/wtlink/link-configs.js +7 -7
- package/dist/lib/wtlink/link-configs.js.map +1 -1
- package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/validate-manifest.js +5 -5
- package/dist/lib/wtlink/validate-manifest.js.map +1 -1
- package/dist/lib/wtstate/args.d.ts.map +1 -1
- package/dist/lib/wtstate/args.js +2 -0
- package/dist/lib/wtstate/args.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +264 -44
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/server.test.js +111 -0
- package/dist/mcp/server.test.js.map +1 -1
- package/package.json +3 -1
- package/schemas/worktreerc.schema.json +23 -0
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These tests verify that each menu flow:
|
|
5
5
|
* 1. Gathers the correct user inputs
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
6
|
+
* 2. Calls the correct library functions with proper arguments
|
|
7
|
+
* 3. Returns to menu after operation execution (not exit)
|
|
8
|
+
* 4. Handles cancellation and back navigation correctly
|
|
9
|
+
* 5. Uses direct library calls (no subprocess spawning)
|
|
8
10
|
*/
|
|
9
11
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
10
12
|
// Mock modules before importing the module under test
|
|
@@ -23,14 +25,9 @@ vi.mock('../../lib/prompts.js', () => {
|
|
|
23
25
|
UserNavigatedBack: MockUserNavigatedBack,
|
|
24
26
|
};
|
|
25
27
|
});
|
|
26
|
-
vi.mock('./run-command.js', () => ({
|
|
27
|
-
runSubcommand: vi.fn(() => {
|
|
28
|
-
// Mock never returns - simulate process.exit
|
|
29
|
-
throw new Error('process.exit called');
|
|
30
|
-
}),
|
|
31
|
-
}));
|
|
32
28
|
vi.mock('../../lib/config.js', () => ({
|
|
33
29
|
loadConfig: vi.fn(() => ({
|
|
30
|
+
configVersion: 1,
|
|
34
31
|
sharedRepos: [],
|
|
35
32
|
baseBranch: 'main',
|
|
36
33
|
draftPr: true,
|
|
@@ -62,12 +59,90 @@ vi.mock('../../lib/config.js', () => ({
|
|
|
62
59
|
vi.mock('../../lib/git.js', () => ({
|
|
63
60
|
getRepoRoot: vi.fn(() => '/mock/repo'),
|
|
64
61
|
listLocalBranches: vi.fn(() => ['feat/existing-branch', 'fix/bug-fix', 'main', 'develop']),
|
|
62
|
+
removeWorktree: vi.fn(),
|
|
63
|
+
pruneWorktrees: vi.fn(),
|
|
64
|
+
}));
|
|
65
|
+
vi.mock('../../lib/wtlink/config-manifest.js', () => ({
|
|
66
|
+
loadManifestData: vi.fn(() => ({
|
|
67
|
+
enabled: ['.env', '.env.local'],
|
|
68
|
+
disabled: ['config.json'],
|
|
69
|
+
source: 'config',
|
|
70
|
+
})),
|
|
71
|
+
saveManifestData: vi.fn(),
|
|
72
|
+
}));
|
|
73
|
+
// Mock direct library imports
|
|
74
|
+
vi.mock('../../lib/lswt/index.js', () => ({
|
|
75
|
+
gatherWorktreeInfo: vi.fn(async () => []),
|
|
76
|
+
createDefaultDeps: vi.fn(() => ({})),
|
|
77
|
+
runInteractiveMode: vi.fn(async () => { }),
|
|
78
|
+
}));
|
|
79
|
+
vi.mock('../../lib/prs/command.js', () => ({
|
|
80
|
+
runPrsCommand: vi.fn(async () => { }),
|
|
81
|
+
}));
|
|
82
|
+
vi.mock('../newpr.js', () => ({
|
|
83
|
+
runNewprHandler: vi.fn(async () => { }),
|
|
84
|
+
}));
|
|
85
|
+
vi.mock('../../lib/cleanpr/index.js', () => ({
|
|
86
|
+
gatherPrWorktreeInfo: vi.fn(async () => []),
|
|
87
|
+
createDefaultDeps: vi.fn(() => ({})),
|
|
88
|
+
getCleanableWorktrees: vi.fn(() => []),
|
|
89
|
+
cleanWorktree: vi.fn(() => ({ success: true, message: 'Cleaned', prNumber: 42 })),
|
|
90
|
+
findWorktreeByPrNumber: vi.fn(() => null),
|
|
91
|
+
summarizeResults: vi.fn(() => ({ cleaned: 0, total: 0 })),
|
|
92
|
+
}));
|
|
93
|
+
vi.mock('../../lib/wtstate/index.js', () => ({
|
|
94
|
+
analyzeState: vi.fn(() => ({
|
|
95
|
+
scenario: 'main_clean_same',
|
|
96
|
+
scenarioDescription: 'On main, clean, same as origin',
|
|
97
|
+
currentBranch: 'main',
|
|
98
|
+
baseBranch: 'main',
|
|
99
|
+
worktreeType: 'main_worktree',
|
|
100
|
+
hasChanges: false,
|
|
101
|
+
hasStagedChanges: false,
|
|
102
|
+
hasUnstagedChanges: false,
|
|
103
|
+
localCommits: 0,
|
|
104
|
+
stagedFiles: [],
|
|
105
|
+
unstagedFiles: [],
|
|
106
|
+
availableActions: [],
|
|
107
|
+
recommendedAction: null,
|
|
108
|
+
})),
|
|
109
|
+
formatText: vi.fn(() => 'State: main_clean_same'),
|
|
110
|
+
}));
|
|
111
|
+
vi.mock('../../lib/wtlink/link-configs.js', () => ({
|
|
112
|
+
run: vi.fn(async () => { }),
|
|
113
|
+
}));
|
|
114
|
+
vi.mock('../../lib/wtlink/validate-manifest.js', () => ({
|
|
115
|
+
run: vi.fn(() => { }),
|
|
116
|
+
}));
|
|
117
|
+
vi.mock('../../lib/wtconfig/index.js', () => ({
|
|
118
|
+
formatConfigDisplay: vi.fn(() => '{ baseBranch: "main" }'),
|
|
119
|
+
setConfigValue: vi.fn((config, _key, _value) => config),
|
|
120
|
+
loadRepoConfig: vi.fn(() => ({})),
|
|
121
|
+
saveRepoConfig: vi.fn(),
|
|
122
|
+
validateConfig: vi.fn(() => ({ valid: true, errors: [], warnings: [] })),
|
|
123
|
+
}));
|
|
124
|
+
vi.mock('../../lib/constants.js', () => ({
|
|
125
|
+
DEFAULT_MANIFEST_FILE: '.wtlinkrc',
|
|
126
|
+
}));
|
|
127
|
+
vi.mock('../../lib/ui/index.js', () => ({
|
|
128
|
+
printStatus: vi.fn(),
|
|
129
|
+
}));
|
|
130
|
+
vi.mock('child_process', () => ({
|
|
131
|
+
execSync: vi.fn(),
|
|
65
132
|
}));
|
|
66
133
|
// Import mocked modules
|
|
67
134
|
import { promptChoice, promptInput, promptConfirm } from '../../lib/prompts.js';
|
|
68
|
-
import { runSubcommand } from './run-command.js';
|
|
69
135
|
import { loadConfig } from '../../lib/config.js';
|
|
70
136
|
import * as git from '../../lib/git.js';
|
|
137
|
+
import { loadManifestData, saveManifestData } from '../../lib/wtlink/config-manifest.js';
|
|
138
|
+
import { gatherWorktreeInfo, runInteractiveMode } from '../../lib/lswt/index.js';
|
|
139
|
+
import { runPrsCommand } from '../../lib/prs/command.js';
|
|
140
|
+
import { runNewprHandler } from '../newpr.js';
|
|
141
|
+
import { gatherPrWorktreeInfo, getCleanableWorktrees, } from '../../lib/cleanpr/index.js';
|
|
142
|
+
import { analyzeState, formatText } from '../../lib/wtstate/index.js';
|
|
143
|
+
import { run as runWtlinkLink } from '../../lib/wtlink/link-configs.js';
|
|
144
|
+
import { run as runWtlinkValidate } from '../../lib/wtlink/validate-manifest.js';
|
|
145
|
+
import { formatConfigDisplay, setConfigValue, loadRepoConfig, saveRepoConfig, } from '../../lib/wtconfig/index.js';
|
|
71
146
|
// Import flows after mocks are set up
|
|
72
147
|
import { flows, showMainMenu } from './interactive-menu.js';
|
|
73
148
|
// Mock console.log to keep test output clean
|
|
@@ -80,36 +155,41 @@ describe('Interactive Menu Flows', () => {
|
|
|
80
155
|
consoleSpy.mockClear();
|
|
81
156
|
});
|
|
82
157
|
describe('handleListWorktrees', () => {
|
|
83
|
-
it('calls
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
158
|
+
it('calls gatherWorktreeInfo and runInteractiveMode and returns to menu', async () => {
|
|
159
|
+
const result = await flows.handleListWorktrees();
|
|
160
|
+
expect(gatherWorktreeInfo).toHaveBeenCalledWith('/mock/repo', { verbose: false, json: false, showStatus: false }, expect.anything());
|
|
161
|
+
expect(runInteractiveMode).toHaveBeenCalled();
|
|
162
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
163
|
+
});
|
|
164
|
+
it('returns to menu with error message when library call fails', async () => {
|
|
165
|
+
vi.mocked(gatherWorktreeInfo).mockRejectedValueOnce(new Error('git error'));
|
|
166
|
+
const result = await flows.handleListWorktrees();
|
|
167
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
168
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('git error'));
|
|
91
169
|
});
|
|
92
170
|
});
|
|
93
171
|
describe('handleBrowsePRs', () => {
|
|
94
|
-
it('calls
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
172
|
+
it('calls runPrsCommand and returns to menu', async () => {
|
|
173
|
+
const result = await flows.handleBrowsePRs();
|
|
174
|
+
expect(runPrsCommand).toHaveBeenCalledWith({
|
|
175
|
+
state: 'open',
|
|
176
|
+
limit: 50,
|
|
177
|
+
json: false,
|
|
178
|
+
noInteractive: false,
|
|
179
|
+
});
|
|
180
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
102
181
|
});
|
|
103
182
|
});
|
|
104
183
|
describe('handleShowState', () => {
|
|
105
|
-
it('calls
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
expect(
|
|
184
|
+
it('calls analyzeState and formatText and returns to menu', async () => {
|
|
185
|
+
const result = await flows.handleShowState();
|
|
186
|
+
expect(analyzeState).toHaveBeenCalledWith({
|
|
187
|
+
verbose: false,
|
|
188
|
+
json: false,
|
|
189
|
+
baseBranch: 'main',
|
|
190
|
+
});
|
|
191
|
+
expect(formatText).toHaveBeenCalled();
|
|
192
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
113
193
|
});
|
|
114
194
|
});
|
|
115
195
|
describe('handleNewPR', () => {
|
|
@@ -117,7 +197,7 @@ describe('Interactive Menu Flows', () => {
|
|
|
117
197
|
vi.mocked(promptChoice).mockResolvedValueOnce('back');
|
|
118
198
|
const result = await flows.handleNewPR();
|
|
119
199
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
120
|
-
expect(
|
|
200
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
121
201
|
});
|
|
122
202
|
it('handles user cancellation (Ctrl+C)', async () => {
|
|
123
203
|
vi.mocked(promptChoice).mockRejectedValueOnce(new Error('User cancelled'));
|
|
@@ -125,7 +205,7 @@ describe('Interactive Menu Flows', () => {
|
|
|
125
205
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
126
206
|
});
|
|
127
207
|
describe('from-description flow', () => {
|
|
128
|
-
it('gathers all inputs and calls
|
|
208
|
+
it('gathers all inputs and calls runNewprHandler with correct Options', async () => {
|
|
129
209
|
vi.mocked(promptChoice)
|
|
130
210
|
.mockResolvedValueOnce('from-description') // New PR sub-menu
|
|
131
211
|
.mockResolvedValueOnce(true); // Draft PR selection
|
|
@@ -135,15 +215,18 @@ describe('Interactive Menu Flows', () => {
|
|
|
135
215
|
vi.mocked(promptConfirm)
|
|
136
216
|
.mockResolvedValueOnce(false) // Install deps
|
|
137
217
|
.mockResolvedValueOnce(false); // Open VS Code
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
218
|
+
const result = await flows.handleNewPR();
|
|
219
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
220
|
+
mode: 'new',
|
|
221
|
+
description: 'Add dark mode support',
|
|
222
|
+
baseBranch: 'main',
|
|
223
|
+
draft: true,
|
|
224
|
+
installDeps: false,
|
|
225
|
+
openEditor: false,
|
|
226
|
+
}));
|
|
227
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
228
|
+
});
|
|
229
|
+
it('passes ready flag when not draft', async () => {
|
|
147
230
|
vi.mocked(promptChoice)
|
|
148
231
|
.mockResolvedValueOnce('from-description')
|
|
149
232
|
.mockResolvedValueOnce(false); // Ready for review (not draft)
|
|
@@ -151,15 +234,15 @@ describe('Interactive Menu Flows', () => {
|
|
|
151
234
|
.mockResolvedValueOnce('Fix critical bug')
|
|
152
235
|
.mockResolvedValueOnce('main');
|
|
153
236
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false).mockResolvedValueOnce(false);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
expect(
|
|
161
|
-
});
|
|
162
|
-
it('passes
|
|
237
|
+
const result = await flows.handleNewPR();
|
|
238
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
239
|
+
mode: 'new',
|
|
240
|
+
description: 'Fix critical bug',
|
|
241
|
+
draft: false,
|
|
242
|
+
}));
|
|
243
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
244
|
+
});
|
|
245
|
+
it('passes non-main base branch', async () => {
|
|
163
246
|
vi.mocked(promptChoice)
|
|
164
247
|
.mockResolvedValueOnce('from-description')
|
|
165
248
|
.mockResolvedValueOnce(true);
|
|
@@ -167,15 +250,13 @@ describe('Interactive Menu Flows', () => {
|
|
|
167
250
|
.mockResolvedValueOnce('Feature work')
|
|
168
251
|
.mockResolvedValueOnce('develop'); // Non-main base branch
|
|
169
252
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false).mockResolvedValueOnce(false);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
});
|
|
178
|
-
it('passes --install flag when requested', async () => {
|
|
253
|
+
const result = await flows.handleNewPR();
|
|
254
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
255
|
+
baseBranch: 'develop',
|
|
256
|
+
}));
|
|
257
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
258
|
+
});
|
|
259
|
+
it('passes install flag when requested', async () => {
|
|
179
260
|
vi.mocked(promptChoice)
|
|
180
261
|
.mockResolvedValueOnce('from-description')
|
|
181
262
|
.mockResolvedValueOnce(true);
|
|
@@ -183,27 +264,23 @@ describe('Interactive Menu Flows', () => {
|
|
|
183
264
|
vi.mocked(promptConfirm)
|
|
184
265
|
.mockResolvedValueOnce(true) // Install deps
|
|
185
266
|
.mockResolvedValueOnce(false);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
});
|
|
194
|
-
it('passes --code flag when requested', async () => {
|
|
267
|
+
const result = await flows.handleNewPR();
|
|
268
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
269
|
+
installDeps: true,
|
|
270
|
+
}));
|
|
271
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
272
|
+
});
|
|
273
|
+
it('passes code flag when requested', async () => {
|
|
195
274
|
vi.mocked(promptChoice)
|
|
196
275
|
.mockResolvedValueOnce('from-description')
|
|
197
276
|
.mockResolvedValueOnce(true);
|
|
198
277
|
vi.mocked(promptInput).mockResolvedValueOnce('Add feature').mockResolvedValueOnce('main');
|
|
199
278
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false).mockResolvedValueOnce(true); // Open VS Code
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
expect(runSubcommand).toHaveBeenCalledWith('newpr', ['Add feature', '--code']);
|
|
279
|
+
const result = await flows.handleNewPR();
|
|
280
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
281
|
+
openEditor: true,
|
|
282
|
+
}));
|
|
283
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
207
284
|
});
|
|
208
285
|
it('passes all optional flags together', async () => {
|
|
209
286
|
vi.mocked(promptChoice)
|
|
@@ -215,27 +292,23 @@ describe('Interactive Menu Flows', () => {
|
|
|
215
292
|
vi.mocked(promptConfirm)
|
|
216
293
|
.mockResolvedValueOnce(true) // Install
|
|
217
294
|
.mockResolvedValueOnce(true); // VS Code
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
'--ready',
|
|
229
|
-
'--install',
|
|
230
|
-
'--code',
|
|
231
|
-
]);
|
|
295
|
+
const result = await flows.handleNewPR();
|
|
296
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
297
|
+
mode: 'new',
|
|
298
|
+
description: 'Full feature',
|
|
299
|
+
baseBranch: 'develop',
|
|
300
|
+
draft: false,
|
|
301
|
+
installDeps: true,
|
|
302
|
+
openEditor: true,
|
|
303
|
+
}));
|
|
304
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
232
305
|
});
|
|
233
306
|
it('returns CANCELLED when description is empty', async () => {
|
|
234
307
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-description');
|
|
235
308
|
vi.mocked(promptInput).mockResolvedValueOnce(''); // Empty description
|
|
236
309
|
const result = await flows.handleNewPR();
|
|
237
310
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
238
|
-
expect(
|
|
311
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
239
312
|
});
|
|
240
313
|
it('handles user cancellation during input', async () => {
|
|
241
314
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-description');
|
|
@@ -245,59 +318,59 @@ describe('Interactive Menu Flows', () => {
|
|
|
245
318
|
});
|
|
246
319
|
});
|
|
247
320
|
describe('from-pr flow', () => {
|
|
248
|
-
it('gathers PR number and calls
|
|
321
|
+
it('gathers PR number and calls runNewprHandler with mode pr', async () => {
|
|
249
322
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-pr');
|
|
250
323
|
vi.mocked(promptInput).mockResolvedValueOnce('42');
|
|
251
324
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false).mockResolvedValueOnce(false);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
it('passes --install and --code flags', async () => {
|
|
325
|
+
const result = await flows.handleNewPR();
|
|
326
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
327
|
+
mode: 'pr',
|
|
328
|
+
prNumber: 42,
|
|
329
|
+
}));
|
|
330
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
331
|
+
});
|
|
332
|
+
it('passes install and code flags', async () => {
|
|
261
333
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-pr');
|
|
262
334
|
vi.mocked(promptInput).mockResolvedValueOnce('123');
|
|
263
335
|
vi.mocked(promptConfirm)
|
|
264
336
|
.mockResolvedValueOnce(true) // Install
|
|
265
337
|
.mockResolvedValueOnce(true); // VS Code
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
338
|
+
const result = await flows.handleNewPR();
|
|
339
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
340
|
+
mode: 'pr',
|
|
341
|
+
prNumber: 123,
|
|
342
|
+
installDeps: true,
|
|
343
|
+
openEditor: true,
|
|
344
|
+
}));
|
|
345
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
273
346
|
});
|
|
274
347
|
it('returns CANCELLED when PR number is empty', async () => {
|
|
275
348
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-pr');
|
|
276
349
|
vi.mocked(promptInput).mockResolvedValueOnce('');
|
|
277
350
|
const result = await flows.handleNewPR();
|
|
278
351
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
279
|
-
expect(
|
|
352
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
280
353
|
});
|
|
281
354
|
it('returns CANCELLED when PR number is invalid', async () => {
|
|
282
355
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-pr');
|
|
283
356
|
vi.mocked(promptInput).mockResolvedValueOnce('not-a-number');
|
|
284
357
|
const result = await flows.handleNewPR();
|
|
285
358
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
286
|
-
expect(
|
|
359
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
287
360
|
});
|
|
288
361
|
it('returns CANCELLED when PR number is zero', async () => {
|
|
289
362
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-pr');
|
|
290
363
|
vi.mocked(promptInput).mockResolvedValueOnce('0');
|
|
291
364
|
const result = await flows.handleNewPR();
|
|
292
365
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
293
|
-
expect(
|
|
366
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
294
367
|
});
|
|
295
368
|
it('returns CANCELLED when PR number is negative', async () => {
|
|
296
369
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-pr');
|
|
297
370
|
vi.mocked(promptInput).mockResolvedValueOnce('-5');
|
|
298
371
|
const result = await flows.handleNewPR();
|
|
299
372
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
300
|
-
expect(
|
|
373
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
301
374
|
});
|
|
302
375
|
});
|
|
303
376
|
describe('from-branch flow', () => {
|
|
@@ -307,13 +380,12 @@ describe('Interactive Menu Flows', () => {
|
|
|
307
380
|
.mockResolvedValueOnce('feat/existing-branch') // Select branch
|
|
308
381
|
.mockResolvedValueOnce(true); // Draft PR
|
|
309
382
|
vi.mocked(promptInput).mockResolvedValueOnce('main');
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
expect(runSubcommand).toHaveBeenCalledWith('newpr', ['--branch', 'feat/existing-branch']);
|
|
383
|
+
const result = await flows.handleNewPR();
|
|
384
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
385
|
+
mode: 'branch',
|
|
386
|
+
branchName: 'feat/existing-branch',
|
|
387
|
+
}));
|
|
388
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
317
389
|
});
|
|
318
390
|
it('allows typing custom branch name', async () => {
|
|
319
391
|
vi.mocked(promptChoice)
|
|
@@ -323,33 +395,27 @@ describe('Interactive Menu Flows', () => {
|
|
|
323
395
|
vi.mocked(promptInput)
|
|
324
396
|
.mockResolvedValueOnce('feat/my-new-branch') // Custom branch name
|
|
325
397
|
.mockResolvedValueOnce('main');
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
it('passes --base and --ready flags', async () => {
|
|
398
|
+
const result = await flows.handleNewPR();
|
|
399
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
400
|
+
mode: 'branch',
|
|
401
|
+
branchName: 'feat/my-new-branch',
|
|
402
|
+
}));
|
|
403
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
404
|
+
});
|
|
405
|
+
it('passes non-main base branch and ready flag', async () => {
|
|
335
406
|
vi.mocked(promptChoice)
|
|
336
407
|
.mockResolvedValueOnce('from-branch')
|
|
337
408
|
.mockResolvedValueOnce('fix/bug-fix')
|
|
338
409
|
.mockResolvedValueOnce(false); // Ready for review
|
|
339
410
|
vi.mocked(promptInput).mockResolvedValueOnce('develop');
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
'fix/bug-fix',
|
|
349
|
-
'--base',
|
|
350
|
-
'develop',
|
|
351
|
-
'--ready',
|
|
352
|
-
]);
|
|
411
|
+
const result = await flows.handleNewPR();
|
|
412
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
413
|
+
mode: 'branch',
|
|
414
|
+
branchName: 'fix/bug-fix',
|
|
415
|
+
baseBranch: 'develop',
|
|
416
|
+
draft: false,
|
|
417
|
+
}));
|
|
418
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
353
419
|
});
|
|
354
420
|
it('returns CANCELLED when branch name is empty', async () => {
|
|
355
421
|
vi.mocked(promptChoice)
|
|
@@ -358,7 +424,7 @@ describe('Interactive Menu Flows', () => {
|
|
|
358
424
|
vi.mocked(promptInput).mockResolvedValueOnce(''); // Empty branch name
|
|
359
425
|
const result = await flows.handleNewPR();
|
|
360
426
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
361
|
-
expect(
|
|
427
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
362
428
|
});
|
|
363
429
|
it('handles empty branch list gracefully', async () => {
|
|
364
430
|
// Mock empty branch list
|
|
@@ -368,14 +434,10 @@ describe('Interactive Menu Flows', () => {
|
|
|
368
434
|
.mockResolvedValueOnce('feat/new-branch') // Manual branch input
|
|
369
435
|
.mockResolvedValueOnce('main');
|
|
370
436
|
vi.mocked(promptChoice).mockResolvedValueOnce(true); // Draft
|
|
371
|
-
|
|
372
|
-
await flows.handleNewPR();
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
// Expected
|
|
376
|
-
}
|
|
437
|
+
const result = await flows.handleNewPR();
|
|
377
438
|
// Should have prompted for branch name directly
|
|
378
439
|
expect(promptInput).toHaveBeenCalledWith('Branch name');
|
|
440
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
379
441
|
});
|
|
380
442
|
});
|
|
381
443
|
});
|
|
@@ -384,39 +446,31 @@ describe('Interactive Menu Flows', () => {
|
|
|
384
446
|
vi.mocked(promptChoice).mockResolvedValueOnce('back');
|
|
385
447
|
const result = await flows.handleCleanPRs();
|
|
386
448
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
387
|
-
expect(
|
|
449
|
+
expect(gatherPrWorktreeInfo).not.toHaveBeenCalled();
|
|
388
450
|
});
|
|
389
451
|
describe('clean-all', () => {
|
|
390
|
-
it('calls cleanpr
|
|
452
|
+
it('calls cleanpr library after confirmation and returns to menu', async () => {
|
|
391
453
|
vi.mocked(promptChoice).mockResolvedValueOnce('clean-all');
|
|
392
454
|
vi.mocked(promptConfirm).mockResolvedValueOnce(true);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
catch {
|
|
397
|
-
// Expected
|
|
398
|
-
}
|
|
399
|
-
expect(runSubcommand).toHaveBeenCalledWith('cleanpr', ['--all']);
|
|
455
|
+
const result = await flows.handleCleanPRs();
|
|
456
|
+
expect(gatherPrWorktreeInfo).toHaveBeenCalled();
|
|
457
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
400
458
|
});
|
|
401
459
|
it('returns CANCELLED when not confirmed', async () => {
|
|
402
460
|
vi.mocked(promptChoice).mockResolvedValueOnce('clean-all');
|
|
403
461
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false);
|
|
404
462
|
const result = await flows.handleCleanPRs();
|
|
405
463
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
406
|
-
expect(
|
|
464
|
+
expect(gatherPrWorktreeInfo).not.toHaveBeenCalled();
|
|
407
465
|
});
|
|
408
466
|
});
|
|
409
467
|
describe('clean-specific', () => {
|
|
410
|
-
it('calls cleanpr with PR number', async () => {
|
|
468
|
+
it('calls cleanpr with PR number and returns to menu', async () => {
|
|
411
469
|
vi.mocked(promptChoice).mockResolvedValueOnce('clean-specific');
|
|
412
470
|
vi.mocked(promptInput).mockResolvedValueOnce('42');
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
// Expected
|
|
418
|
-
}
|
|
419
|
-
expect(runSubcommand).toHaveBeenCalledWith('cleanpr', ['42']);
|
|
471
|
+
const result = await flows.handleCleanPRs();
|
|
472
|
+
expect(gatherPrWorktreeInfo).toHaveBeenCalled();
|
|
473
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
420
474
|
});
|
|
421
475
|
it('returns CANCELLED when PR number is empty', async () => {
|
|
422
476
|
vi.mocked(promptChoice).mockResolvedValueOnce('clean-specific');
|
|
@@ -432,15 +486,12 @@ describe('Interactive Menu Flows', () => {
|
|
|
432
486
|
});
|
|
433
487
|
});
|
|
434
488
|
describe('dry-run', () => {
|
|
435
|
-
it('calls cleanpr with
|
|
489
|
+
it('calls cleanpr with dry-run and returns to menu', async () => {
|
|
436
490
|
vi.mocked(promptChoice).mockResolvedValueOnce('dry-run');
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// Expected
|
|
442
|
-
}
|
|
443
|
-
expect(runSubcommand).toHaveBeenCalledWith('cleanpr', ['--dry-run']);
|
|
491
|
+
const result = await flows.handleCleanPRs();
|
|
492
|
+
expect(gatherPrWorktreeInfo).toHaveBeenCalled();
|
|
493
|
+
expect(getCleanableWorktrees).toHaveBeenCalled();
|
|
494
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
444
495
|
});
|
|
445
496
|
});
|
|
446
497
|
it('handles user cancellation', async () => {
|
|
@@ -454,72 +505,110 @@ describe('Interactive Menu Flows', () => {
|
|
|
454
505
|
vi.mocked(promptChoice).mockResolvedValueOnce('back');
|
|
455
506
|
const result = await flows.handleLinkConfig();
|
|
456
507
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
457
|
-
expect(runSubcommand).not.toHaveBeenCalled();
|
|
458
508
|
});
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
await flows.handleLinkConfig();
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
expect(runSubcommand).toHaveBeenCalledWith('wtlink', ['add', '.env']);
|
|
489
|
-
});
|
|
490
|
-
it('add returns CANCELLED when file path is empty', async () => {
|
|
491
|
-
vi.mocked(promptChoice).mockResolvedValueOnce('add');
|
|
492
|
-
vi.mocked(promptInput).mockResolvedValueOnce('');
|
|
493
|
-
const result = await flows.handleLinkConfig();
|
|
494
|
-
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
495
|
-
expect(runSubcommand).not.toHaveBeenCalled();
|
|
496
|
-
});
|
|
497
|
-
it('remove calls wtlink remove with file path', async () => {
|
|
498
|
-
vi.mocked(promptChoice).mockResolvedValueOnce('remove');
|
|
499
|
-
vi.mocked(promptInput).mockResolvedValueOnce('.env.local');
|
|
500
|
-
try {
|
|
501
|
-
await flows.handleLinkConfig();
|
|
502
|
-
}
|
|
503
|
-
catch {
|
|
504
|
-
// Expected
|
|
505
|
-
}
|
|
506
|
-
expect(runSubcommand).toHaveBeenCalledWith('wtlink', ['remove', '.env.local']);
|
|
507
|
-
});
|
|
508
|
-
it('remove returns CANCELLED when file path is empty', async () => {
|
|
509
|
-
vi.mocked(promptChoice).mockResolvedValueOnce('remove');
|
|
510
|
-
vi.mocked(promptInput).mockResolvedValueOnce('');
|
|
511
|
-
const result = await flows.handleLinkConfig();
|
|
512
|
-
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
509
|
+
describe('view via library', () => {
|
|
510
|
+
it('displays manifest contents from loadManifestData', async () => {
|
|
511
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('view');
|
|
512
|
+
const result = await flows.handleLinkConfig();
|
|
513
|
+
expect(loadManifestData).toHaveBeenCalledWith('/mock/repo');
|
|
514
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
515
|
+
});
|
|
516
|
+
it('shows empty message when manifest has no files', async () => {
|
|
517
|
+
vi.mocked(loadManifestData).mockReturnValueOnce({
|
|
518
|
+
enabled: [],
|
|
519
|
+
disabled: [],
|
|
520
|
+
source: 'empty',
|
|
521
|
+
});
|
|
522
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('view');
|
|
523
|
+
const result = await flows.handleLinkConfig();
|
|
524
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
525
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No files'));
|
|
526
|
+
});
|
|
527
|
+
it('displays enabled and disabled files', async () => {
|
|
528
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('view');
|
|
529
|
+
const result = await flows.handleLinkConfig();
|
|
530
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
531
|
+
// Check enabled files are shown
|
|
532
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Enabled'));
|
|
533
|
+
expect(consoleSpy).toHaveBeenCalledWith(' .env');
|
|
534
|
+
expect(consoleSpy).toHaveBeenCalledWith(' .env.local');
|
|
535
|
+
// Check disabled files are shown
|
|
536
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Disabled'));
|
|
537
|
+
});
|
|
513
538
|
});
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
await flows.handleLinkConfig();
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
539
|
+
describe('sync via wtlink link', () => {
|
|
540
|
+
it('calls wtlink link library function', async () => {
|
|
541
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('sync');
|
|
542
|
+
const result = await flows.handleLinkConfig();
|
|
543
|
+
expect(runWtlinkLink).toHaveBeenCalledWith(expect.objectContaining({
|
|
544
|
+
manifestFile: '.wtlinkrc',
|
|
545
|
+
dryRun: false,
|
|
546
|
+
type: 'hard',
|
|
547
|
+
}));
|
|
548
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
549
|
+
});
|
|
550
|
+
it('shows error when sync fails', async () => {
|
|
551
|
+
vi.mocked(runWtlinkLink).mockRejectedValueOnce(new Error('Link failed'));
|
|
552
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('sync');
|
|
553
|
+
const result = await flows.handleLinkConfig();
|
|
554
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
555
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Link failed'));
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
describe('add via library', () => {
|
|
559
|
+
it('adds file to manifest via saveManifestData', async () => {
|
|
560
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('add');
|
|
561
|
+
vi.mocked(promptInput).mockResolvedValueOnce('.npmrc');
|
|
562
|
+
const result = await flows.handleLinkConfig();
|
|
563
|
+
expect(saveManifestData).toHaveBeenCalledWith('/mock/repo', ['.env', '.env.local', '.npmrc'], ['config.json']);
|
|
564
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
565
|
+
});
|
|
566
|
+
it('skips duplicate files', async () => {
|
|
567
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('add');
|
|
568
|
+
vi.mocked(promptInput).mockResolvedValueOnce('.env');
|
|
569
|
+
const result = await flows.handleLinkConfig();
|
|
570
|
+
expect(saveManifestData).not.toHaveBeenCalled();
|
|
571
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
572
|
+
});
|
|
573
|
+
it('returns CANCELLED when file path is empty', async () => {
|
|
574
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('add');
|
|
575
|
+
vi.mocked(promptInput).mockResolvedValueOnce('');
|
|
576
|
+
const result = await flows.handleLinkConfig();
|
|
577
|
+
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
578
|
+
expect(saveManifestData).not.toHaveBeenCalled();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
describe('remove via library', () => {
|
|
582
|
+
it('removes file from manifest via saveManifestData', async () => {
|
|
583
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('remove');
|
|
584
|
+
vi.mocked(promptInput).mockResolvedValueOnce('.env');
|
|
585
|
+
const result = await flows.handleLinkConfig();
|
|
586
|
+
expect(saveManifestData).toHaveBeenCalledWith('/mock/repo', ['.env.local'], ['config.json']);
|
|
587
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
588
|
+
});
|
|
589
|
+
it('handles file not in manifest', async () => {
|
|
590
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('remove');
|
|
591
|
+
vi.mocked(promptInput).mockResolvedValueOnce('nonexistent.txt');
|
|
592
|
+
const result = await flows.handleLinkConfig();
|
|
593
|
+
expect(saveManifestData).not.toHaveBeenCalled();
|
|
594
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
595
|
+
});
|
|
596
|
+
it('returns CANCELLED when file path is empty', async () => {
|
|
597
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('remove');
|
|
598
|
+
vi.mocked(promptInput).mockResolvedValueOnce('');
|
|
599
|
+
const result = await flows.handleLinkConfig();
|
|
600
|
+
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
describe('validate', () => {
|
|
604
|
+
it('calls wtlink validate and returns to menu', async () => {
|
|
605
|
+
vi.mocked(promptChoice).mockResolvedValueOnce('validate');
|
|
606
|
+
const result = await flows.handleLinkConfig();
|
|
607
|
+
expect(runWtlinkValidate).toHaveBeenCalledWith(expect.objectContaining({
|
|
608
|
+
manifestFile: '.wtlinkrc',
|
|
609
|
+
}));
|
|
610
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
611
|
+
});
|
|
523
612
|
});
|
|
524
613
|
it('handles user cancellation', async () => {
|
|
525
614
|
vi.mocked(promptChoice).mockRejectedValueOnce(new Error('User cancelled'));
|
|
@@ -532,53 +621,41 @@ describe('Interactive Menu Flows', () => {
|
|
|
532
621
|
vi.mocked(promptChoice).mockResolvedValueOnce('back');
|
|
533
622
|
const result = await flows.handleConfigure();
|
|
534
623
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
535
|
-
expect(runSubcommand).not.toHaveBeenCalled();
|
|
536
624
|
});
|
|
537
|
-
it('view calls
|
|
625
|
+
it('view calls formatConfigDisplay and returns to menu', async () => {
|
|
538
626
|
vi.mocked(promptChoice).mockResolvedValueOnce('view');
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
expect(runSubcommand).toHaveBeenCalledWith('wtconfig', ['show']);
|
|
546
|
-
});
|
|
547
|
-
it('init calls wtconfig init after confirmation', async () => {
|
|
627
|
+
const result = await flows.handleConfigure();
|
|
628
|
+
expect(loadRepoConfig).toHaveBeenCalledWith('/mock/repo');
|
|
629
|
+
expect(formatConfigDisplay).toHaveBeenCalled();
|
|
630
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
631
|
+
});
|
|
632
|
+
it('init shows redirect message after confirmation and returns to menu', async () => {
|
|
548
633
|
vi.mocked(promptChoice).mockResolvedValueOnce('init');
|
|
549
634
|
vi.mocked(promptConfirm).mockResolvedValueOnce(true);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
catch {
|
|
554
|
-
// Expected
|
|
555
|
-
}
|
|
556
|
-
expect(runSubcommand).toHaveBeenCalledWith('wtconfig', ['init']);
|
|
635
|
+
const result = await flows.handleConfigure();
|
|
636
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('wt init'));
|
|
637
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
557
638
|
});
|
|
558
639
|
it('init returns CANCELLED when not confirmed', async () => {
|
|
559
640
|
vi.mocked(promptChoice).mockResolvedValueOnce('init');
|
|
560
641
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false);
|
|
561
642
|
const result = await flows.handleConfigure();
|
|
562
643
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
563
|
-
expect(runSubcommand).not.toHaveBeenCalled();
|
|
564
644
|
});
|
|
565
|
-
it('edit calls
|
|
645
|
+
it('edit calls setConfigValue and saveRepoConfig with setting and value', async () => {
|
|
566
646
|
vi.mocked(promptChoice).mockResolvedValueOnce('edit').mockResolvedValueOnce('baseBranch');
|
|
567
647
|
vi.mocked(promptInput).mockResolvedValueOnce('develop');
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
// Expected
|
|
573
|
-
}
|
|
574
|
-
expect(runSubcommand).toHaveBeenCalledWith('wtconfig', ['set', 'baseBranch', 'develop']);
|
|
648
|
+
const result = await flows.handleConfigure();
|
|
649
|
+
expect(setConfigValue).toHaveBeenCalledWith({}, 'baseBranch', 'develop');
|
|
650
|
+
expect(saveRepoConfig).toHaveBeenCalled();
|
|
651
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
575
652
|
});
|
|
576
653
|
it('edit returns CANCELLED when value is empty', async () => {
|
|
577
654
|
vi.mocked(promptChoice).mockResolvedValueOnce('edit').mockResolvedValueOnce('branchPrefix');
|
|
578
655
|
vi.mocked(promptInput).mockResolvedValueOnce('');
|
|
579
656
|
const result = await flows.handleConfigure();
|
|
580
657
|
expect(result).toEqual({ completed: false, returnToMenu: true });
|
|
581
|
-
expect(
|
|
658
|
+
expect(saveRepoConfig).not.toHaveBeenCalled();
|
|
582
659
|
});
|
|
583
660
|
it('handles user cancellation', async () => {
|
|
584
661
|
vi.mocked(promptChoice).mockRejectedValueOnce(new Error('User cancelled'));
|
|
@@ -590,12 +667,12 @@ describe('Interactive Menu Flows', () => {
|
|
|
590
667
|
it('exits on exit selection', async () => {
|
|
591
668
|
vi.mocked(promptChoice).mockResolvedValueOnce('exit');
|
|
592
669
|
await showMainMenu();
|
|
593
|
-
expect(
|
|
670
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
594
671
|
});
|
|
595
672
|
it('exits on user cancellation', async () => {
|
|
596
673
|
vi.mocked(promptChoice).mockRejectedValueOnce(new Error('User cancelled'));
|
|
597
674
|
await showMainMenu();
|
|
598
|
-
expect(
|
|
675
|
+
expect(runNewprHandler).not.toHaveBeenCalled();
|
|
599
676
|
});
|
|
600
677
|
it('re-throws non-cancellation errors', async () => {
|
|
601
678
|
vi.mocked(promptChoice).mockRejectedValueOnce(new Error('Some other error'));
|
|
@@ -610,35 +687,29 @@ describe('Interactive Menu Flows', () => {
|
|
|
610
687
|
// Should have called promptChoice 3 times (menu -> sub-menu -> back to menu -> exit)
|
|
611
688
|
expect(promptChoice).toHaveBeenCalledTimes(3);
|
|
612
689
|
});
|
|
613
|
-
it('handles list worktrees
|
|
614
|
-
vi.mocked(promptChoice)
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
await showMainMenu();
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
// Expected
|
|
640
|
-
}
|
|
641
|
-
expect(runSubcommand).toHaveBeenCalledWith('wtstate', []);
|
|
690
|
+
it('handles list worktrees and returns to menu', async () => {
|
|
691
|
+
vi.mocked(promptChoice)
|
|
692
|
+
.mockResolvedValueOnce('list') // Select list
|
|
693
|
+
.mockResolvedValueOnce('exit'); // Then exit
|
|
694
|
+
await showMainMenu();
|
|
695
|
+
expect(gatherWorktreeInfo).toHaveBeenCalled();
|
|
696
|
+
expect(promptChoice).toHaveBeenCalledTimes(2);
|
|
697
|
+
});
|
|
698
|
+
it('handles browse PRs and returns to menu', async () => {
|
|
699
|
+
vi.mocked(promptChoice)
|
|
700
|
+
.mockResolvedValueOnce('browse-prs') // Select browse-prs
|
|
701
|
+
.mockResolvedValueOnce('exit'); // Then exit
|
|
702
|
+
await showMainMenu();
|
|
703
|
+
expect(runPrsCommand).toHaveBeenCalled();
|
|
704
|
+
expect(promptChoice).toHaveBeenCalledTimes(2);
|
|
705
|
+
});
|
|
706
|
+
it('handles show state and returns to menu', async () => {
|
|
707
|
+
vi.mocked(promptChoice)
|
|
708
|
+
.mockResolvedValueOnce('state') // Select state
|
|
709
|
+
.mockResolvedValueOnce('exit'); // Then exit
|
|
710
|
+
await showMainMenu();
|
|
711
|
+
expect(analyzeState).toHaveBeenCalled();
|
|
712
|
+
expect(promptChoice).toHaveBeenCalledTimes(2);
|
|
642
713
|
});
|
|
643
714
|
});
|
|
644
715
|
describe('FlowResult types', () => {
|
|
@@ -648,17 +719,10 @@ describe('Interactive Menu Flows', () => {
|
|
|
648
719
|
expect(result.completed).toBe(false);
|
|
649
720
|
expect(result.returnToMenu).toBe(true);
|
|
650
721
|
});
|
|
651
|
-
it('flows that run
|
|
722
|
+
it('flows that run operations return completed with returnToMenu=true', async () => {
|
|
652
723
|
vi.mocked(promptChoice).mockResolvedValueOnce('dry-run');
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
await flows.handleCleanPRs();
|
|
657
|
-
}
|
|
658
|
-
catch {
|
|
659
|
-
// Expected
|
|
660
|
-
}
|
|
661
|
-
expect(runSubcommand).toHaveBeenCalled();
|
|
724
|
+
const result = await flows.handleCleanPRs();
|
|
725
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
662
726
|
});
|
|
663
727
|
});
|
|
664
728
|
});
|
|
@@ -669,6 +733,7 @@ describe('Config loading in flows', () => {
|
|
|
669
733
|
it('uses config default for base branch', async () => {
|
|
670
734
|
// Set up config mock to return custom baseBranch
|
|
671
735
|
vi.mocked(loadConfig).mockReturnValueOnce({
|
|
736
|
+
configVersion: 1,
|
|
672
737
|
sharedRepos: [],
|
|
673
738
|
baseBranch: 'develop',
|
|
674
739
|
draftPr: true,
|
|
@@ -695,20 +760,19 @@ describe('Config loading in flows', () => {
|
|
|
695
760
|
logging: { level: 'info', timestamps: true },
|
|
696
761
|
global: { warnNotGlobal: true },
|
|
697
762
|
wtlink: { enabled: [], disabled: [] },
|
|
763
|
+
linkConfigFiles: undefined,
|
|
698
764
|
});
|
|
699
765
|
vi.mocked(promptChoice).mockResolvedValueOnce('from-description').mockResolvedValueOnce(true);
|
|
700
766
|
vi.mocked(promptInput).mockResolvedValueOnce('Test feature').mockResolvedValueOnce('develop'); // User accepts default
|
|
701
767
|
vi.mocked(promptConfirm).mockResolvedValueOnce(false).mockResolvedValueOnce(false);
|
|
702
|
-
|
|
703
|
-
await flows.handleNewPR();
|
|
704
|
-
}
|
|
705
|
-
catch {
|
|
706
|
-
// Expected
|
|
707
|
-
}
|
|
768
|
+
const result = await flows.handleNewPR();
|
|
708
769
|
// Verify loadConfig was called
|
|
709
770
|
expect(loadConfig).toHaveBeenCalled();
|
|
710
|
-
//
|
|
711
|
-
expect(
|
|
771
|
+
// Verify runNewprHandler was called with develop base branch
|
|
772
|
+
expect(runNewprHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
773
|
+
baseBranch: 'develop',
|
|
774
|
+
}));
|
|
775
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
712
776
|
});
|
|
713
777
|
});
|
|
714
778
|
describe('Git branch listing in flows', () => {
|
|
@@ -723,14 +787,10 @@ describe('Git branch listing in flows', () => {
|
|
|
723
787
|
.mockResolvedValueOnce('feat/existing-branch')
|
|
724
788
|
.mockResolvedValueOnce(true);
|
|
725
789
|
vi.mocked(promptInput).mockResolvedValueOnce('main');
|
|
726
|
-
|
|
727
|
-
await flows.handleNewPR();
|
|
728
|
-
}
|
|
729
|
-
catch {
|
|
730
|
-
// Expected
|
|
731
|
-
}
|
|
790
|
+
const result = await flows.handleNewPR();
|
|
732
791
|
// Check that listLocalBranches was called
|
|
733
792
|
expect(git.listLocalBranches).toHaveBeenCalled();
|
|
793
|
+
expect(result).toEqual({ completed: true, returnToMenu: true });
|
|
734
794
|
});
|
|
735
795
|
});
|
|
736
796
|
//# sourceMappingURL=interactive-menu.test.js.map
|