@camaradesuk/git-worktree-tools 1.10.0 → 1.12.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.
Files changed (431) hide show
  1. package/README.md +4 -4
  2. package/dist/lib/config.d.ts +2 -1
  3. package/dist/lib/config.d.ts.map +1 -1
  4. package/dist/lib/config.js +11 -0
  5. package/dist/lib/config.js.map +1 -1
  6. package/package.json +12 -3
  7. package/schemas/worktreerc.schema.json +1 -1
  8. package/dist/api/list.test.d.ts +0 -5
  9. package/dist/api/list.test.d.ts.map +0 -1
  10. package/dist/api/list.test.js +0 -390
  11. package/dist/api/list.test.js.map +0 -1
  12. package/dist/cli/cleanpr.test.d.ts +0 -2
  13. package/dist/cli/cleanpr.test.d.ts.map +0 -1
  14. package/dist/cli/cleanpr.test.js +0 -954
  15. package/dist/cli/cleanpr.test.js.map +0 -1
  16. package/dist/cli/lswt.test.d.ts +0 -2
  17. package/dist/cli/lswt.test.d.ts.map +0 -1
  18. package/dist/cli/lswt.test.js +0 -376
  19. package/dist/cli/lswt.test.js.map +0 -1
  20. package/dist/cli/newpr.test.d.ts +0 -2
  21. package/dist/cli/newpr.test.d.ts.map +0 -1
  22. package/dist/cli/newpr.test.js +0 -1182
  23. package/dist/cli/newpr.test.js.map +0 -1
  24. package/dist/cli/prs.test.d.ts +0 -8
  25. package/dist/cli/prs.test.d.ts.map +0 -1
  26. package/dist/cli/prs.test.js +0 -463
  27. package/dist/cli/prs.test.js.map +0 -1
  28. package/dist/cli/wt/clean.test.d.ts +0 -8
  29. package/dist/cli/wt/clean.test.d.ts.map +0 -1
  30. package/dist/cli/wt/clean.test.js +0 -624
  31. package/dist/cli/wt/clean.test.js.map +0 -1
  32. package/dist/cli/wt/completion.test.d.ts +0 -5
  33. package/dist/cli/wt/completion.test.d.ts.map +0 -1
  34. package/dist/cli/wt/completion.test.js +0 -275
  35. package/dist/cli/wt/completion.test.js.map +0 -1
  36. package/dist/cli/wt/config.test.d.ts +0 -7
  37. package/dist/cli/wt/config.test.d.ts.map +0 -1
  38. package/dist/cli/wt/config.test.js +0 -440
  39. package/dist/cli/wt/config.test.js.map +0 -1
  40. package/dist/cli/wt/entry.test.d.ts +0 -8
  41. package/dist/cli/wt/entry.test.d.ts.map +0 -1
  42. package/dist/cli/wt/entry.test.js +0 -201
  43. package/dist/cli/wt/entry.test.js.map +0 -1
  44. package/dist/cli/wt/init.test.d.ts +0 -5
  45. package/dist/cli/wt/init.test.d.ts.map +0 -1
  46. package/dist/cli/wt/init.test.js +0 -165
  47. package/dist/cli/wt/init.test.js.map +0 -1
  48. package/dist/cli/wt/init.unit.test.d.ts +0 -5
  49. package/dist/cli/wt/init.unit.test.d.ts.map +0 -1
  50. package/dist/cli/wt/init.unit.test.js +0 -432
  51. package/dist/cli/wt/init.unit.test.js.map +0 -1
  52. package/dist/cli/wt/interactive-menu.test.d.ts +0 -12
  53. package/dist/cli/wt/interactive-menu.test.d.ts.map +0 -1
  54. package/dist/cli/wt/interactive-menu.test.js +0 -796
  55. package/dist/cli/wt/interactive-menu.test.js.map +0 -1
  56. package/dist/cli/wt/list.test.d.ts +0 -10
  57. package/dist/cli/wt/list.test.d.ts.map +0 -1
  58. package/dist/cli/wt/list.test.js +0 -157
  59. package/dist/cli/wt/list.test.js.map +0 -1
  60. package/dist/cli/wt/prs.test.d.ts +0 -5
  61. package/dist/cli/wt/prs.test.d.ts.map +0 -1
  62. package/dist/cli/wt/prs.test.js +0 -410
  63. package/dist/cli/wt/prs.test.js.map +0 -1
  64. package/dist/cli/wt/run-command.test.d.ts +0 -5
  65. package/dist/cli/wt/run-command.test.d.ts.map +0 -1
  66. package/dist/cli/wt/run-command.test.js +0 -88
  67. package/dist/cli/wt/run-command.test.js.map +0 -1
  68. package/dist/cli/wt/state.test.d.ts +0 -9
  69. package/dist/cli/wt/state.test.d.ts.map +0 -1
  70. package/dist/cli/wt/state.test.js +0 -127
  71. package/dist/cli/wt/state.test.js.map +0 -1
  72. package/dist/cli/wt/wt.test.d.ts +0 -8
  73. package/dist/cli/wt/wt.test.d.ts.map +0 -1
  74. package/dist/cli/wt/wt.test.js +0 -739
  75. package/dist/cli/wt/wt.test.js.map +0 -1
  76. package/dist/cli/wt.unit.test.d.ts +0 -7
  77. package/dist/cli/wt.unit.test.d.ts.map +0 -1
  78. package/dist/cli/wt.unit.test.js +0 -160
  79. package/dist/cli/wt.unit.test.js.map +0 -1
  80. package/dist/cli/wtconfig.test.d.ts +0 -5
  81. package/dist/cli/wtconfig.test.d.ts.map +0 -1
  82. package/dist/cli/wtconfig.test.js +0 -1289
  83. package/dist/cli/wtconfig.test.js.map +0 -1
  84. package/dist/cli/wtlink.test.d.ts +0 -2
  85. package/dist/cli/wtlink.test.d.ts.map +0 -1
  86. package/dist/cli/wtlink.test.js +0 -249
  87. package/dist/cli/wtlink.test.js.map +0 -1
  88. package/dist/cli/wtstate.test.d.ts +0 -5
  89. package/dist/cli/wtstate.test.d.ts.map +0 -1
  90. package/dist/cli/wtstate.test.js +0 -193
  91. package/dist/cli/wtstate.test.js.map +0 -1
  92. package/dist/e2e/cleanpr/cleanpr.e2e.test.d.ts +0 -2
  93. package/dist/e2e/cleanpr/cleanpr.e2e.test.d.ts.map +0 -1
  94. package/dist/e2e/cleanpr/cleanpr.e2e.test.js +0 -326
  95. package/dist/e2e/cleanpr/cleanpr.e2e.test.js.map +0 -1
  96. package/dist/e2e/cli.e2e.test.d.ts +0 -2
  97. package/dist/e2e/cli.e2e.test.d.ts.map +0 -1
  98. package/dist/e2e/cli.e2e.test.js +0 -417
  99. package/dist/e2e/cli.e2e.test.js.map +0 -1
  100. package/dist/e2e/lswt/lswt.e2e.test.d.ts +0 -2
  101. package/dist/e2e/lswt/lswt.e2e.test.d.ts.map +0 -1
  102. package/dist/e2e/lswt/lswt.e2e.test.js +0 -361
  103. package/dist/e2e/lswt/lswt.e2e.test.js.map +0 -1
  104. package/dist/e2e/newpr/newpr.e2e.test.d.ts +0 -2
  105. package/dist/e2e/newpr/newpr.e2e.test.d.ts.map +0 -1
  106. package/dist/e2e/newpr/newpr.e2e.test.js +0 -286
  107. package/dist/e2e/newpr/newpr.e2e.test.js.map +0 -1
  108. package/dist/e2e/newpr/scenarios.e2e.test.d.ts +0 -2
  109. package/dist/e2e/newpr/scenarios.e2e.test.d.ts.map +0 -1
  110. package/dist/e2e/newpr/scenarios.e2e.test.js +0 -426
  111. package/dist/e2e/newpr/scenarios.e2e.test.js.map +0 -1
  112. package/dist/e2e/newpr-full-flow.e2e.test.d.ts +0 -2
  113. package/dist/e2e/newpr-full-flow.e2e.test.d.ts.map +0 -1
  114. package/dist/e2e/newpr-full-flow.e2e.test.js +0 -280
  115. package/dist/e2e/newpr-full-flow.e2e.test.js.map +0 -1
  116. package/dist/e2e/prs/prs.e2e.test.d.ts +0 -7
  117. package/dist/e2e/prs/prs.e2e.test.d.ts.map +0 -1
  118. package/dist/e2e/prs/prs.e2e.test.js +0 -606
  119. package/dist/e2e/prs/prs.e2e.test.js.map +0 -1
  120. package/dist/e2e/workflows/pr-lifecycle.e2e.test.d.ts +0 -2
  121. package/dist/e2e/workflows/pr-lifecycle.e2e.test.d.ts.map +0 -1
  122. package/dist/e2e/workflows/pr-lifecycle.e2e.test.js +0 -298
  123. package/dist/e2e/workflows/pr-lifecycle.e2e.test.js.map +0 -1
  124. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts +0 -8
  125. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts.map +0 -1
  126. package/dist/e2e/wt/interactive-menu.e2e.test.js +0 -583
  127. package/dist/e2e/wt/interactive-menu.e2e.test.js.map +0 -1
  128. package/dist/e2e/wt/wt.e2e.test.d.ts +0 -9
  129. package/dist/e2e/wt/wt.e2e.test.d.ts.map +0 -1
  130. package/dist/e2e/wt/wt.e2e.test.js +0 -597
  131. package/dist/e2e/wt/wt.e2e.test.js.map +0 -1
  132. package/dist/e2e/wtlink/wtlink.e2e.test.d.ts +0 -2
  133. package/dist/e2e/wtlink/wtlink.e2e.test.d.ts.map +0 -1
  134. package/dist/e2e/wtlink/wtlink.e2e.test.js +0 -416
  135. package/dist/e2e/wtlink/wtlink.e2e.test.js.map +0 -1
  136. package/dist/integration/git.integration.test.d.ts +0 -2
  137. package/dist/integration/git.integration.test.d.ts.map +0 -1
  138. package/dist/integration/git.integration.test.js +0 -336
  139. package/dist/integration/git.integration.test.js.map +0 -1
  140. package/dist/integration/lswt-remote-pr.integration.test.d.ts +0 -2
  141. package/dist/integration/lswt-remote-pr.integration.test.d.ts.map +0 -1
  142. package/dist/integration/lswt-remote-pr.integration.test.js +0 -222
  143. package/dist/integration/lswt-remote-pr.integration.test.js.map +0 -1
  144. package/dist/integration/newpr-branchfrom-head.integration.test.d.ts +0 -2
  145. package/dist/integration/newpr-branchfrom-head.integration.test.d.ts.map +0 -1
  146. package/dist/integration/newpr-branchfrom-head.integration.test.js +0 -498
  147. package/dist/integration/newpr-branchfrom-head.integration.test.js.map +0 -1
  148. package/dist/integration/newpr.integration.test.d.ts +0 -2
  149. package/dist/integration/newpr.integration.test.d.ts.map +0 -1
  150. package/dist/integration/newpr.integration.test.js +0 -460
  151. package/dist/integration/newpr.integration.test.js.map +0 -1
  152. package/dist/integration/prs.integration.test.d.ts +0 -8
  153. package/dist/integration/prs.integration.test.d.ts.map +0 -1
  154. package/dist/integration/prs.integration.test.js +0 -478
  155. package/dist/integration/prs.integration.test.js.map +0 -1
  156. package/dist/lib/ai/base-provider.test.d.ts +0 -7
  157. package/dist/lib/ai/base-provider.test.d.ts.map +0 -1
  158. package/dist/lib/ai/base-provider.test.js +0 -319
  159. package/dist/lib/ai/base-provider.test.js.map +0 -1
  160. package/dist/lib/ai/cli-provider.test.d.ts +0 -5
  161. package/dist/lib/ai/cli-provider.test.d.ts.map +0 -1
  162. package/dist/lib/ai/cli-provider.test.js +0 -460
  163. package/dist/lib/ai/cli-provider.test.js.map +0 -1
  164. package/dist/lib/ai/fallback-provider.test.d.ts +0 -7
  165. package/dist/lib/ai/fallback-provider.test.d.ts.map +0 -1
  166. package/dist/lib/ai/fallback-provider.test.js +0 -165
  167. package/dist/lib/ai/fallback-provider.test.js.map +0 -1
  168. package/dist/lib/ai/generation-service.test.d.ts +0 -7
  169. package/dist/lib/ai/generation-service.test.d.ts.map +0 -1
  170. package/dist/lib/ai/generation-service.test.js +0 -213
  171. package/dist/lib/ai/generation-service.test.js.map +0 -1
  172. package/dist/lib/ai/provider-manager.test.d.ts +0 -5
  173. package/dist/lib/ai/provider-manager.test.d.ts.map +0 -1
  174. package/dist/lib/ai/provider-manager.test.js +0 -312
  175. package/dist/lib/ai/provider-manager.test.js.map +0 -1
  176. package/dist/lib/ai/repo-docs.test.d.ts +0 -5
  177. package/dist/lib/ai/repo-docs.test.d.ts.map +0 -1
  178. package/dist/lib/ai/repo-docs.test.js +0 -357
  179. package/dist/lib/ai/repo-docs.test.js.map +0 -1
  180. package/dist/lib/cleanpr/args.test.d.ts +0 -2
  181. package/dist/lib/cleanpr/args.test.d.ts.map +0 -1
  182. package/dist/lib/cleanpr/args.test.js +0 -269
  183. package/dist/lib/cleanpr/args.test.js.map +0 -1
  184. package/dist/lib/cleanpr/cleanup.test.d.ts +0 -2
  185. package/dist/lib/cleanpr/cleanup.test.d.ts.map +0 -1
  186. package/dist/lib/cleanpr/cleanup.test.js +0 -296
  187. package/dist/lib/cleanpr/cleanup.test.js.map +0 -1
  188. package/dist/lib/cleanpr/worktree-info.test.d.ts +0 -2
  189. package/dist/lib/cleanpr/worktree-info.test.d.ts.map +0 -1
  190. package/dist/lib/cleanpr/worktree-info.test.js +0 -228
  191. package/dist/lib/cleanpr/worktree-info.test.js.map +0 -1
  192. package/dist/lib/colors.test.d.ts +0 -2
  193. package/dist/lib/colors.test.d.ts.map +0 -1
  194. package/dist/lib/colors.test.js +0 -142
  195. package/dist/lib/colors.test.js.map +0 -1
  196. package/dist/lib/config-editor.test.d.ts +0 -11
  197. package/dist/lib/config-editor.test.d.ts.map +0 -1
  198. package/dist/lib/config-editor.test.js +0 -526
  199. package/dist/lib/config-editor.test.js.map +0 -1
  200. package/dist/lib/config-migration/detector.test.d.ts +0 -5
  201. package/dist/lib/config-migration/detector.test.d.ts.map +0 -1
  202. package/dist/lib/config-migration/detector.test.js +0 -201
  203. package/dist/lib/config-migration/detector.test.js.map +0 -1
  204. package/dist/lib/config-migration/reporter.test.d.ts +0 -5
  205. package/dist/lib/config-migration/reporter.test.d.ts.map +0 -1
  206. package/dist/lib/config-migration/reporter.test.js +0 -305
  207. package/dist/lib/config-migration/reporter.test.js.map +0 -1
  208. package/dist/lib/config-migration/runner.test.d.ts +0 -5
  209. package/dist/lib/config-migration/runner.test.d.ts.map +0 -1
  210. package/dist/lib/config-migration/runner.test.js +0 -235
  211. package/dist/lib/config-migration/runner.test.js.map +0 -1
  212. package/dist/lib/config-validation.test.d.ts +0 -5
  213. package/dist/lib/config-validation.test.d.ts.map +0 -1
  214. package/dist/lib/config-validation.test.js +0 -423
  215. package/dist/lib/config-validation.test.js.map +0 -1
  216. package/dist/lib/config.test.d.ts +0 -2
  217. package/dist/lib/config.test.d.ts.map +0 -1
  218. package/dist/lib/config.test.js +0 -554
  219. package/dist/lib/config.test.js.map +0 -1
  220. package/dist/lib/constants.test.d.ts +0 -5
  221. package/dist/lib/constants.test.d.ts.map +0 -1
  222. package/dist/lib/constants.test.js +0 -180
  223. package/dist/lib/constants.test.js.map +0 -1
  224. package/dist/lib/deprecation.test.d.ts +0 -2
  225. package/dist/lib/deprecation.test.d.ts.map +0 -1
  226. package/dist/lib/deprecation.test.js +0 -71
  227. package/dist/lib/deprecation.test.js.map +0 -1
  228. package/dist/lib/errors.test.d.ts +0 -2
  229. package/dist/lib/errors.test.d.ts.map +0 -1
  230. package/dist/lib/errors.test.js +0 -117
  231. package/dist/lib/errors.test.js.map +0 -1
  232. package/dist/lib/git.test.d.ts +0 -2
  233. package/dist/lib/git.test.d.ts.map +0 -1
  234. package/dist/lib/git.test.js +0 -608
  235. package/dist/lib/git.test.js.map +0 -1
  236. package/dist/lib/github.test.d.ts +0 -2
  237. package/dist/lib/github.test.d.ts.map +0 -1
  238. package/dist/lib/github.test.js +0 -441
  239. package/dist/lib/github.test.js.map +0 -1
  240. package/dist/lib/global-check.test.d.ts +0 -5
  241. package/dist/lib/global-check.test.d.ts.map +0 -1
  242. package/dist/lib/global-check.test.js +0 -150
  243. package/dist/lib/global-check.test.js.map +0 -1
  244. package/dist/lib/global-config.test.d.ts +0 -5
  245. package/dist/lib/global-config.test.d.ts.map +0 -1
  246. package/dist/lib/global-config.test.js +0 -282
  247. package/dist/lib/global-config.test.js.map +0 -1
  248. package/dist/lib/hooks/confirmation.test.d.ts +0 -7
  249. package/dist/lib/hooks/confirmation.test.d.ts.map +0 -1
  250. package/dist/lib/hooks/confirmation.test.js +0 -300
  251. package/dist/lib/hooks/confirmation.test.js.map +0 -1
  252. package/dist/lib/hooks/executor.test.d.ts +0 -5
  253. package/dist/lib/hooks/executor.test.d.ts.map +0 -1
  254. package/dist/lib/hooks/executor.test.js +0 -648
  255. package/dist/lib/hooks/executor.test.js.map +0 -1
  256. package/dist/lib/hooks/templates.test.d.ts +0 -5
  257. package/dist/lib/hooks/templates.test.d.ts.map +0 -1
  258. package/dist/lib/hooks/templates.test.js +0 -163
  259. package/dist/lib/hooks/templates.test.js.map +0 -1
  260. package/dist/lib/hooks/types.test.d.ts +0 -5
  261. package/dist/lib/hooks/types.test.d.ts.map +0 -1
  262. package/dist/lib/hooks/types.test.js +0 -132
  263. package/dist/lib/hooks/types.test.js.map +0 -1
  264. package/dist/lib/json-output.test.d.ts +0 -5
  265. package/dist/lib/json-output.test.d.ts.map +0 -1
  266. package/dist/lib/json-output.test.js +0 -261
  267. package/dist/lib/json-output.test.js.map +0 -1
  268. package/dist/lib/logger.test.d.ts +0 -14
  269. package/dist/lib/logger.test.d.ts.map +0 -1
  270. package/dist/lib/logger.test.js +0 -692
  271. package/dist/lib/logger.test.js.map +0 -1
  272. package/dist/lib/lswt/action-executors.test.d.ts +0 -2
  273. package/dist/lib/lswt/action-executors.test.d.ts.map +0 -1
  274. package/dist/lib/lswt/action-executors.test.js +0 -1127
  275. package/dist/lib/lswt/action-executors.test.js.map +0 -1
  276. package/dist/lib/lswt/actions.test.d.ts +0 -2
  277. package/dist/lib/lswt/actions.test.d.ts.map +0 -1
  278. package/dist/lib/lswt/actions.test.js +0 -497
  279. package/dist/lib/lswt/actions.test.js.map +0 -1
  280. package/dist/lib/lswt/args.test.d.ts +0 -2
  281. package/dist/lib/lswt/args.test.d.ts.map +0 -1
  282. package/dist/lib/lswt/args.test.js +0 -195
  283. package/dist/lib/lswt/args.test.js.map +0 -1
  284. package/dist/lib/lswt/environment.test.d.ts +0 -2
  285. package/dist/lib/lswt/environment.test.d.ts.map +0 -1
  286. package/dist/lib/lswt/environment.test.js +0 -544
  287. package/dist/lib/lswt/environment.test.js.map +0 -1
  288. package/dist/lib/lswt/formatters.test.d.ts +0 -2
  289. package/dist/lib/lswt/formatters.test.d.ts.map +0 -1
  290. package/dist/lib/lswt/formatters.test.js +0 -323
  291. package/dist/lib/lswt/formatters.test.js.map +0 -1
  292. package/dist/lib/lswt/fuzzy-search.test.d.ts +0 -5
  293. package/dist/lib/lswt/fuzzy-search.test.d.ts.map +0 -1
  294. package/dist/lib/lswt/fuzzy-search.test.js +0 -207
  295. package/dist/lib/lswt/fuzzy-search.test.js.map +0 -1
  296. package/dist/lib/lswt/interactive.test.d.ts +0 -2
  297. package/dist/lib/lswt/interactive.test.d.ts.map +0 -1
  298. package/dist/lib/lswt/interactive.test.js +0 -771
  299. package/dist/lib/lswt/interactive.test.js.map +0 -1
  300. package/dist/lib/lswt/table.test.d.ts +0 -5
  301. package/dist/lib/lswt/table.test.d.ts.map +0 -1
  302. package/dist/lib/lswt/table.test.js +0 -262
  303. package/dist/lib/lswt/table.test.js.map +0 -1
  304. package/dist/lib/lswt/worktree-info.test.d.ts +0 -2
  305. package/dist/lib/lswt/worktree-info.test.d.ts.map +0 -1
  306. package/dist/lib/lswt/worktree-info.test.js +0 -484
  307. package/dist/lib/lswt/worktree-info.test.js.map +0 -1
  308. package/dist/lib/newpr/action-deps.test.d.ts +0 -5
  309. package/dist/lib/newpr/action-deps.test.d.ts.map +0 -1
  310. package/dist/lib/newpr/action-deps.test.js +0 -111
  311. package/dist/lib/newpr/action-deps.test.js.map +0 -1
  312. package/dist/lib/newpr/actions.test.d.ts +0 -2
  313. package/dist/lib/newpr/actions.test.d.ts.map +0 -1
  314. package/dist/lib/newpr/actions.test.js +0 -254
  315. package/dist/lib/newpr/actions.test.js.map +0 -1
  316. package/dist/lib/newpr/args.test.d.ts +0 -2
  317. package/dist/lib/newpr/args.test.d.ts.map +0 -1
  318. package/dist/lib/newpr/args.test.js +0 -479
  319. package/dist/lib/newpr/args.test.js.map +0 -1
  320. package/dist/lib/newpr/hook-runner.test.d.ts +0 -7
  321. package/dist/lib/newpr/hook-runner.test.d.ts.map +0 -1
  322. package/dist/lib/newpr/hook-runner.test.js +0 -422
  323. package/dist/lib/newpr/hook-runner.test.js.map +0 -1
  324. package/dist/lib/newpr/plan-generator.test.d.ts +0 -7
  325. package/dist/lib/newpr/plan-generator.test.d.ts.map +0 -1
  326. package/dist/lib/newpr/plan-generator.test.js +0 -387
  327. package/dist/lib/newpr/plan-generator.test.js.map +0 -1
  328. package/dist/lib/newpr/scenario-handler.test.d.ts +0 -2
  329. package/dist/lib/newpr/scenario-handler.test.d.ts.map +0 -1
  330. package/dist/lib/newpr/scenario-handler.test.js +0 -256
  331. package/dist/lib/newpr/scenario-handler.test.js.map +0 -1
  332. package/dist/lib/prompts.test.d.ts +0 -2
  333. package/dist/lib/prompts.test.d.ts.map +0 -1
  334. package/dist/lib/prompts.test.js +0 -807
  335. package/dist/lib/prompts.test.js.map +0 -1
  336. package/dist/lib/prs/actions.test.d.ts +0 -5
  337. package/dist/lib/prs/actions.test.d.ts.map +0 -1
  338. package/dist/lib/prs/actions.test.js +0 -356
  339. package/dist/lib/prs/actions.test.js.map +0 -1
  340. package/dist/lib/prs/command.test.d.ts +0 -11
  341. package/dist/lib/prs/command.test.d.ts.map +0 -1
  342. package/dist/lib/prs/command.test.js +0 -409
  343. package/dist/lib/prs/command.test.js.map +0 -1
  344. package/dist/lib/prs/data.test.d.ts +0 -5
  345. package/dist/lib/prs/data.test.d.ts.map +0 -1
  346. package/dist/lib/prs/data.test.js +0 -417
  347. package/dist/lib/prs/data.test.js.map +0 -1
  348. package/dist/lib/prs/details.test.d.ts +0 -5
  349. package/dist/lib/prs/details.test.d.ts.map +0 -1
  350. package/dist/lib/prs/details.test.js +0 -325
  351. package/dist/lib/prs/details.test.js.map +0 -1
  352. package/dist/lib/prs/filters.test.d.ts +0 -5
  353. package/dist/lib/prs/filters.test.d.ts.map +0 -1
  354. package/dist/lib/prs/filters.test.js +0 -312
  355. package/dist/lib/prs/filters.test.js.map +0 -1
  356. package/dist/lib/prs/formatters.test.d.ts +0 -2
  357. package/dist/lib/prs/formatters.test.d.ts.map +0 -1
  358. package/dist/lib/prs/formatters.test.js +0 -387
  359. package/dist/lib/prs/formatters.test.js.map +0 -1
  360. package/dist/lib/prs/interactive.test.d.ts +0 -5
  361. package/dist/lib/prs/interactive.test.d.ts.map +0 -1
  362. package/dist/lib/prs/interactive.test.js +0 -517
  363. package/dist/lib/prs/interactive.test.js.map +0 -1
  364. package/dist/lib/schema.test.d.ts +0 -10
  365. package/dist/lib/schema.test.d.ts.map +0 -1
  366. package/dist/lib/schema.test.js +0 -309
  367. package/dist/lib/schema.test.js.map +0 -1
  368. package/dist/lib/state-detection.test.d.ts +0 -2
  369. package/dist/lib/state-detection.test.d.ts.map +0 -1
  370. package/dist/lib/state-detection.test.js +0 -451
  371. package/dist/lib/state-detection.test.js.map +0 -1
  372. package/dist/lib/ui/error.test.d.ts +0 -2
  373. package/dist/lib/ui/error.test.d.ts.map +0 -1
  374. package/dist/lib/ui/error.test.js +0 -143
  375. package/dist/lib/ui/error.test.js.map +0 -1
  376. package/dist/lib/ui/output.test.d.ts +0 -2
  377. package/dist/lib/ui/output.test.d.ts.map +0 -1
  378. package/dist/lib/ui/output.test.js +0 -59
  379. package/dist/lib/ui/output.test.js.map +0 -1
  380. package/dist/lib/ui/status.test.d.ts +0 -2
  381. package/dist/lib/ui/status.test.d.ts.map +0 -1
  382. package/dist/lib/ui/status.test.js +0 -158
  383. package/dist/lib/ui/status.test.js.map +0 -1
  384. package/dist/lib/ui/table.test.d.ts +0 -2
  385. package/dist/lib/ui/table.test.d.ts.map +0 -1
  386. package/dist/lib/ui/table.test.js +0 -115
  387. package/dist/lib/ui/table.test.js.map +0 -1
  388. package/dist/lib/ui/theme.test.d.ts +0 -2
  389. package/dist/lib/ui/theme.test.d.ts.map +0 -1
  390. package/dist/lib/ui/theme.test.js +0 -76
  391. package/dist/lib/ui/theme.test.js.map +0 -1
  392. package/dist/lib/wtconfig/config-manager.test.d.ts +0 -5
  393. package/dist/lib/wtconfig/config-manager.test.d.ts.map +0 -1
  394. package/dist/lib/wtconfig/config-manager.test.js +0 -501
  395. package/dist/lib/wtconfig/config-manager.test.js.map +0 -1
  396. package/dist/lib/wtconfig/environment.test.d.ts +0 -5
  397. package/dist/lib/wtconfig/environment.test.d.ts.map +0 -1
  398. package/dist/lib/wtconfig/environment.test.js +0 -285
  399. package/dist/lib/wtconfig/environment.test.js.map +0 -1
  400. package/dist/lib/wtlink/config-manifest.test.d.ts +0 -2
  401. package/dist/lib/wtlink/config-manifest.test.d.ts.map +0 -1
  402. package/dist/lib/wtlink/config-manifest.test.js +0 -486
  403. package/dist/lib/wtlink/config-manifest.test.js.map +0 -1
  404. package/dist/lib/wtlink/link-configs.test.d.ts +0 -2
  405. package/dist/lib/wtlink/link-configs.test.d.ts.map +0 -1
  406. package/dist/lib/wtlink/link-configs.test.js +0 -612
  407. package/dist/lib/wtlink/link-configs.test.js.map +0 -1
  408. package/dist/lib/wtlink/main-menu.test.d.ts +0 -5
  409. package/dist/lib/wtlink/main-menu.test.d.ts.map +0 -1
  410. package/dist/lib/wtlink/main-menu.test.js +0 -126
  411. package/dist/lib/wtlink/main-menu.test.js.map +0 -1
  412. package/dist/lib/wtlink/manage-manifest.test.d.ts +0 -2
  413. package/dist/lib/wtlink/manage-manifest.test.d.ts.map +0 -1
  414. package/dist/lib/wtlink/manage-manifest.test.js +0 -714
  415. package/dist/lib/wtlink/manage-manifest.test.js.map +0 -1
  416. package/dist/lib/wtlink/validate-manifest.test.d.ts +0 -2
  417. package/dist/lib/wtlink/validate-manifest.test.d.ts.map +0 -1
  418. package/dist/lib/wtlink/validate-manifest.test.js +0 -220
  419. package/dist/lib/wtlink/validate-manifest.test.js.map +0 -1
  420. package/dist/lib/wtstate/analyze.test.d.ts +0 -5
  421. package/dist/lib/wtstate/analyze.test.d.ts.map +0 -1
  422. package/dist/lib/wtstate/analyze.test.js +0 -282
  423. package/dist/lib/wtstate/analyze.test.js.map +0 -1
  424. package/dist/lib/wtstate/args.test.d.ts +0 -5
  425. package/dist/lib/wtstate/args.test.d.ts.map +0 -1
  426. package/dist/lib/wtstate/args.test.js +0 -120
  427. package/dist/lib/wtstate/args.test.js.map +0 -1
  428. package/dist/mcp/server.test.d.ts +0 -9
  429. package/dist/mcp/server.test.d.ts.map +0 -1
  430. package/dist/mcp/server.test.js +0 -550
  431. package/dist/mcp/server.test.js.map +0 -1
@@ -1,1127 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { executeAction, createDefaultExecutorDeps, formatBranchAsTitle, } from './action-executors.js';
3
- // Mock inquirer
4
- vi.mock('inquirer', () => ({
5
- default: {
6
- prompt: vi.fn(),
7
- },
8
- }));
9
- // Mock github
10
- vi.mock('../github.js', () => ({
11
- getPr: vi.fn(),
12
- getPrByBranch: vi.fn(),
13
- createPr: vi.fn(),
14
- }));
15
- // Mock git
16
- vi.mock('../git.js', () => ({
17
- getRepoRoot: vi.fn(),
18
- removeWorktree: vi.fn(),
19
- getMainWorktreeRoot: vi.fn(),
20
- addWorktree: vi.fn(),
21
- deleteBranch: vi.fn(),
22
- exec: vi.fn(),
23
- }));
24
- // Mock child_process for spawn/spawnSync
25
- vi.mock('child_process', async () => {
26
- const actual = await vi.importActual('child_process');
27
- return {
28
- ...actual,
29
- spawnSync: vi.fn(),
30
- spawn: actual.spawn,
31
- };
32
- });
33
- import inquirer from 'inquirer';
34
- import * as github from '../github.js';
35
- import * as git from '../git.js';
36
- import { spawnSync } from 'child_process';
37
- describe('lswt/action-executors', () => {
38
- beforeEach(() => {
39
- vi.clearAllMocks();
40
- // Reset spawnSync mock for WSL path conversion
41
- vi.mocked(spawnSync).mockReturnValue({
42
- status: 0,
43
- stdout: '\\\\wsl.localhost\\Ubuntu\\home\\user\\repo',
44
- stderr: '',
45
- pid: 0,
46
- signal: null,
47
- output: ['', '\\\\wsl.localhost\\Ubuntu\\home\\user\\repo', ''],
48
- });
49
- });
50
- afterEach(() => {
51
- vi.restoreAllMocks();
52
- });
53
- const makeWorktree = (overrides = {}) => ({
54
- path: '/home/user/repo',
55
- name: 'repo',
56
- branch: 'main',
57
- commit: 'abc123',
58
- type: 'main',
59
- prNumber: null,
60
- prState: null,
61
- isDraft: null,
62
- hasChanges: false,
63
- ...overrides,
64
- });
65
- const makeEnv = (overrides = {}) => ({
66
- hasVscode: true,
67
- hasCursor: false,
68
- defaultEditor: 'vscode',
69
- platform: 'linux',
70
- isInteractive: true,
71
- shell: '/bin/bash',
72
- gitVersion: { major: 2, minor: 39, patch: 0, raw: 'git version 2.39.0' },
73
- isWSL: false,
74
- ...overrides,
75
- });
76
- const makeConfig = (overrides = {}) => ({
77
- baseBranch: 'main',
78
- worktreePattern: '{repo}.pr{number}',
79
- worktreeParent: '..',
80
- draftPr: false,
81
- sharedRepos: [],
82
- branchPrefix: 'feature',
83
- previewLabel: 'preview',
84
- syncPatterns: [],
85
- preferredEditor: 'auto',
86
- ai: { provider: 'none' },
87
- hooks: {},
88
- hookDefaults: { timeout: 30000, maxTimeout: 60000 },
89
- plugins: [],
90
- generators: {},
91
- integrations: {},
92
- logging: { level: 'info', timestamps: true },
93
- global: { warnNotGlobal: true },
94
- ...overrides,
95
- });
96
- const makeDeps = (overrides = {}) => ({
97
- execCommand: vi.fn(),
98
- spawnDetached: vi.fn(),
99
- copyToClipboard: vi.fn(),
100
- openUrl: vi.fn(),
101
- wslPathToWindows: vi.fn().mockReturnValue('\\\\wsl.localhost\\Ubuntu\\home\\user\\repo'),
102
- ...overrides,
103
- });
104
- describe('createDefaultExecutorDeps', () => {
105
- it('returns an object with all required methods', () => {
106
- const deps = createDefaultExecutorDeps();
107
- expect(deps).toHaveProperty('execCommand');
108
- expect(deps).toHaveProperty('spawnDetached');
109
- expect(deps).toHaveProperty('copyToClipboard');
110
- expect(deps).toHaveProperty('openUrl');
111
- expect(deps).toHaveProperty('wslPathToWindows');
112
- expect(typeof deps.execCommand).toBe('function');
113
- expect(typeof deps.spawnDetached).toBe('function');
114
- expect(typeof deps.copyToClipboard).toBe('function');
115
- expect(typeof deps.openUrl).toBe('function');
116
- expect(typeof deps.wslPathToWindows).toBe('function');
117
- });
118
- });
119
- describe('executeAction', () => {
120
- describe('back action', () => {
121
- it('returns success with no message', async () => {
122
- const result = await executeAction('back', makeWorktree(), makeEnv(), makeConfig());
123
- expect(result).toEqual({ success: true });
124
- });
125
- });
126
- describe('exit action', () => {
127
- it('returns success with shouldExit true', async () => {
128
- const result = await executeAction('exit', makeWorktree(), makeEnv(), makeConfig());
129
- expect(result).toEqual({ success: true, shouldExit: true });
130
- });
131
- });
132
- describe('open_editor action', () => {
133
- it('spawns VSCode when preferredEditor is vscode', async () => {
134
- const deps = makeDeps();
135
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
136
- const env = makeEnv({ hasVscode: true, defaultEditor: 'vscode' });
137
- const config = makeConfig({ preferredEditor: 'vscode' });
138
- const result = await executeAction('open_editor', worktree, env, config, deps);
139
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', ['/home/user/repo.pr1']);
140
- expect(result.success).toBe(true);
141
- expect(result.message).toContain('VSCode');
142
- });
143
- it('spawns Cursor when preferredEditor is cursor', async () => {
144
- const deps = makeDeps();
145
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
146
- const env = makeEnv({ hasCursor: true, defaultEditor: 'cursor' });
147
- const config = makeConfig({ preferredEditor: 'cursor' });
148
- const result = await executeAction('open_editor', worktree, env, config, deps);
149
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', ['/home/user/repo.pr1']);
150
- expect(result.success).toBe(true);
151
- expect(result.message).toContain('Cursor');
152
- });
153
- it('uses VSCode when preferredEditor is auto and VSCode available', async () => {
154
- const deps = makeDeps();
155
- const worktree = makeWorktree();
156
- const env = makeEnv({ hasVscode: true, hasCursor: false, defaultEditor: 'vscode' });
157
- const config = makeConfig({ preferredEditor: 'auto' });
158
- const result = await executeAction('open_editor', worktree, env, config, deps);
159
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', expect.any(Array));
160
- expect(result.success).toBe(true);
161
- });
162
- it('uses Cursor when preferredEditor is auto and only Cursor available', async () => {
163
- const deps = makeDeps();
164
- const worktree = makeWorktree();
165
- const env = makeEnv({ hasVscode: false, hasCursor: true, defaultEditor: 'cursor' });
166
- const config = makeConfig({ preferredEditor: 'auto' });
167
- const result = await executeAction('open_editor', worktree, env, config, deps);
168
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', expect.any(Array));
169
- expect(result.success).toBe(true);
170
- });
171
- it('returns error when no editor is available', async () => {
172
- const deps = makeDeps();
173
- const worktree = makeWorktree();
174
- const env = makeEnv({ hasVscode: false, hasCursor: false, defaultEditor: null });
175
- const config = makeConfig({ preferredEditor: 'auto' });
176
- const result = await executeAction('open_editor', worktree, env, config, deps);
177
- expect(result.success).toBe(false);
178
- expect(result.message).toContain('No editor found');
179
- expect(deps.spawnDetached).not.toHaveBeenCalled();
180
- });
181
- it('handles spawn errors gracefully', async () => {
182
- const deps = makeDeps({
183
- spawnDetached: vi.fn().mockImplementation(() => {
184
- throw new Error('spawn failed');
185
- }),
186
- });
187
- const worktree = makeWorktree();
188
- const env = makeEnv({ hasVscode: true, defaultEditor: 'vscode' });
189
- const config = makeConfig();
190
- const result = await executeAction('open_editor', worktree, env, config, deps);
191
- expect(result.success).toBe(false);
192
- expect(result.message).toContain('Failed to open editor');
193
- });
194
- });
195
- describe('copy_path action', () => {
196
- it('copies path to clipboard', async () => {
197
- const deps = makeDeps();
198
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
199
- const result = await executeAction('copy_path', worktree, makeEnv(), makeConfig(), deps);
200
- expect(deps.copyToClipboard).toHaveBeenCalledWith('/home/user/repo.pr1');
201
- expect(result.success).toBe(true);
202
- expect(result.message).toContain('Copied');
203
- expect(result.message).toContain('/home/user/repo.pr1');
204
- });
205
- it('handles clipboard errors gracefully', async () => {
206
- const deps = makeDeps({
207
- copyToClipboard: vi.fn().mockImplementation(() => {
208
- throw new Error('clipboard unavailable');
209
- }),
210
- });
211
- const worktree = makeWorktree();
212
- const result = await executeAction('copy_path', worktree, makeEnv(), makeConfig(), deps);
213
- expect(result.success).toBe(false);
214
- expect(result.message).toContain('Failed to copy');
215
- });
216
- });
217
- describe('show_details action', () => {
218
- it('returns success (details are printed to console)', async () => {
219
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
220
- const worktree = makeWorktree({
221
- type: 'pr',
222
- prNumber: 42,
223
- prState: 'OPEN',
224
- branch: 'feature-42',
225
- });
226
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
227
- expect(result.success).toBe(true);
228
- consoleSpy.mockRestore();
229
- });
230
- });
231
- describe('open_pr_url action', () => {
232
- it('returns error when worktree has no PR number', async () => {
233
- const deps = makeDeps();
234
- const worktree = makeWorktree({ type: 'branch', prNumber: null });
235
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
236
- expect(result.success).toBe(false);
237
- expect(result.message).toContain('No PR associated');
238
- expect(deps.openUrl).not.toHaveBeenCalled();
239
- });
240
- });
241
- });
242
- describe('createDefaultExecutorDeps', () => {
243
- it('returns object with all required methods', () => {
244
- const deps = createDefaultExecutorDeps();
245
- expect(deps).toHaveProperty('execCommand');
246
- expect(deps).toHaveProperty('spawnDetached');
247
- expect(deps).toHaveProperty('copyToClipboard');
248
- expect(deps).toHaveProperty('openUrl');
249
- expect(deps).toHaveProperty('wslPathToWindows');
250
- expect(typeof deps.execCommand).toBe('function');
251
- expect(typeof deps.spawnDetached).toBe('function');
252
- expect(typeof deps.copyToClipboard).toBe('function');
253
- expect(typeof deps.openUrl).toBe('function');
254
- expect(typeof deps.wslPathToWindows).toBe('function');
255
- });
256
- });
257
- describe('open_terminal action', () => {
258
- it('spawns terminal on Linux', async () => {
259
- const deps = makeDeps();
260
- const worktree = makeWorktree({ path: '/home/user/repo' });
261
- const env = makeEnv({ platform: 'linux' });
262
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
263
- // Should attempt to spawn a terminal
264
- expect(deps.spawnDetached).toHaveBeenCalled();
265
- expect(result.success).toBe(true);
266
- expect(result.message).toContain('terminal');
267
- });
268
- it('uses Windows Terminal via cmd.exe in WSL', async () => {
269
- const deps = makeDeps();
270
- const worktree = makeWorktree({ path: '/home/user/repo' });
271
- const env = makeEnv({ platform: 'linux', isWSL: true });
272
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
273
- // Should call deps.wslPathToWindows to convert path
274
- expect(deps.wslPathToWindows).toHaveBeenCalledWith('/home/user/repo');
275
- // Should try to use cmd.exe to launch Windows Terminal
276
- expect(deps.execCommand).toHaveBeenCalledWith(expect.stringContaining('cmd.exe'));
277
- expect(result.success).toBe(true);
278
- });
279
- it('shows cd command fallback when WSL Windows Terminal fails', async () => {
280
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
281
- const deps = makeDeps({
282
- execCommand: vi.fn().mockImplementation(() => {
283
- throw new Error('cmd.exe failed');
284
- }),
285
- });
286
- const worktree = makeWorktree({ path: '/home/user/repo' });
287
- const env = makeEnv({ platform: 'linux', isWSL: true });
288
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
289
- // Should fall back to showing cd command
290
- expect(result.success).toBe(true);
291
- expect(result.message).toContain('copy the cd command');
292
- // Should print cd command to console
293
- const output = consoleSpy.mock.calls.map((c) => String(c[0])).join('\n');
294
- expect(output).toContain('cd');
295
- consoleSpy.mockRestore();
296
- });
297
- it('uses osascript on macOS', async () => {
298
- const deps = makeDeps();
299
- const worktree = makeWorktree({ path: '/Users/user/repo' });
300
- const env = makeEnv({ platform: 'darwin' });
301
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
302
- expect(deps.execCommand).toHaveBeenCalledWith(expect.stringContaining('osascript'));
303
- expect(result.success).toBe(true);
304
- });
305
- it('tries Windows Terminal on Windows', async () => {
306
- const deps = makeDeps();
307
- const worktree = makeWorktree({ path: 'C:\\Users\\user\\repo' });
308
- const env = makeEnv({ platform: 'win32' });
309
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
310
- // Should try wt first
311
- expect(deps.spawnDetached).toHaveBeenCalledWith('wt', expect.any(Array));
312
- expect(result.success).toBe(true);
313
- });
314
- it('falls back to cmd on Windows when wt fails', async () => {
315
- let callCount = 0;
316
- const deps = makeDeps({
317
- spawnDetached: vi.fn().mockImplementation((cmd) => {
318
- callCount++;
319
- if (cmd === 'wt') {
320
- throw new Error('wt not found');
321
- }
322
- }),
323
- });
324
- const worktree = makeWorktree({ path: 'C:\\Users\\user\\repo' });
325
- const env = makeEnv({ platform: 'win32' });
326
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
327
- // Should have tried wt, then cmd
328
- expect(callCount).toBe(2);
329
- expect(result.success).toBe(true);
330
- });
331
- it('handles no terminal available on Linux', async () => {
332
- const deps = makeDeps({
333
- spawnDetached: vi.fn().mockImplementation(() => {
334
- throw new Error('terminal not found');
335
- }),
336
- });
337
- const worktree = makeWorktree();
338
- const env = makeEnv({ platform: 'linux' });
339
- const result = await executeAction('open_terminal', worktree, env, makeConfig(), deps);
340
- expect(result.success).toBe(false);
341
- expect(result.message).toContain('No terminal emulator found');
342
- });
343
- });
344
- describe('remove_worktree action', () => {
345
- it('returns error for main worktree', async () => {
346
- const worktree = makeWorktree({ type: 'main' });
347
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
348
- expect(result.success).toBe(false);
349
- expect(result.message).toContain('Cannot remove main worktree');
350
- });
351
- it('cancels when user declines confirmation', async () => {
352
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: false });
353
- const worktree = makeWorktree({
354
- type: 'branch',
355
- branch: 'feature-branch',
356
- name: 'feature-branch',
357
- });
358
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
359
- expect(result.success).toBe(true);
360
- expect(result.message).toBe('Cancelled');
361
- expect(git.removeWorktree).not.toHaveBeenCalled();
362
- });
363
- it('removes worktree when user confirms', async () => {
364
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
365
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: true });
366
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
367
- const worktree = makeWorktree({
368
- type: 'branch',
369
- branch: 'feature-branch',
370
- name: 'my-worktree',
371
- });
372
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
373
- expect(result.success).toBe(true);
374
- expect(result.message).toContain('Removed worktree');
375
- expect(result.shouldRefresh).toBe(true);
376
- expect(git.removeWorktree).toHaveBeenCalledWith(worktree.path);
377
- consoleSpy.mockRestore();
378
- });
379
- it('warns about uncommitted changes', async () => {
380
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
381
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: false });
382
- const worktree = makeWorktree({
383
- type: 'branch',
384
- branch: 'dirty-branch',
385
- hasChanges: true,
386
- });
387
- await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
388
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
389
- expect(output).toContain('uncommitted changes');
390
- consoleSpy.mockRestore();
391
- });
392
- it('prompts to delete branch for merged PR worktrees', async () => {
393
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
394
- vi.mocked(inquirer.prompt)
395
- .mockResolvedValueOnce({ confirm: true })
396
- .mockResolvedValueOnce({ shouldDelete: true });
397
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
398
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
399
- const worktree = makeWorktree({
400
- type: 'pr',
401
- prNumber: 42,
402
- prState: 'MERGED',
403
- branch: 'feature-42',
404
- name: 'repo.pr42',
405
- });
406
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
407
- expect(result.success).toBe(true);
408
- expect(inquirer.prompt).toHaveBeenCalledTimes(2);
409
- consoleSpy.mockRestore();
410
- });
411
- it('prompts to delete branch for closed PR worktrees', async () => {
412
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
413
- vi.mocked(inquirer.prompt)
414
- .mockResolvedValueOnce({ confirm: true })
415
- .mockResolvedValueOnce({ shouldDelete: false });
416
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
417
- const worktree = makeWorktree({
418
- type: 'pr',
419
- prNumber: 42,
420
- prState: 'CLOSED',
421
- branch: 'feature-42',
422
- name: 'repo.pr42',
423
- });
424
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
425
- expect(result.success).toBe(true);
426
- expect(inquirer.prompt).toHaveBeenCalledTimes(2);
427
- consoleSpy.mockRestore();
428
- });
429
- it('continues successfully when branch deletion fails', async () => {
430
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
431
- vi.mocked(inquirer.prompt)
432
- .mockResolvedValueOnce({ confirm: true })
433
- .mockResolvedValueOnce({ shouldDelete: true });
434
- vi.mocked(git.removeWorktree).mockImplementation(() => { });
435
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
436
- // Branch deletion fails (branch might not exist locally)
437
- vi.mocked(git.deleteBranch).mockImplementation(() => {
438
- throw new Error('Branch not found');
439
- });
440
- const worktree = makeWorktree({
441
- type: 'pr',
442
- prNumber: 42,
443
- prState: 'MERGED',
444
- branch: 'feature-42',
445
- name: 'repo.pr42',
446
- });
447
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
448
- // Should still succeed even though branch deletion failed
449
- expect(result.success).toBe(true);
450
- expect(result.message).toContain('Removed worktree');
451
- consoleSpy.mockRestore();
452
- });
453
- it('handles removal failure', async () => {
454
- vi.mocked(inquirer.prompt).mockResolvedValue({ confirm: true });
455
- vi.mocked(git.removeWorktree).mockImplementation(() => {
456
- throw new Error('Worktree has changes');
457
- });
458
- const worktree = makeWorktree({
459
- type: 'branch',
460
- branch: 'feature-branch',
461
- name: 'my-worktree',
462
- });
463
- const result = await executeAction('remove_worktree', worktree, makeEnv(), makeConfig(), makeDeps());
464
- expect(result.success).toBe(false);
465
- expect(result.message).toContain('Failed to remove worktree');
466
- });
467
- });
468
- describe('create_pr action', () => {
469
- it('returns error for detached HEAD', async () => {
470
- const worktree = makeWorktree({ type: 'detached', branch: null });
471
- const result = await executeAction('create_pr', worktree, makeEnv(), makeConfig(), makeDeps());
472
- expect(result.success).toBe(false);
473
- expect(result.message).toContain('detached HEAD');
474
- });
475
- it('returns error when PR already exists for branch', async () => {
476
- vi.mocked(github.getPrByBranch).mockReturnValue({
477
- number: 42,
478
- url: 'https://github.com/owner/repo/pull/42',
479
- state: 'OPEN',
480
- isDraft: false,
481
- title: 'Existing PR',
482
- headBranch: 'feature-branch',
483
- baseBranch: 'main',
484
- });
485
- const worktree = makeWorktree({
486
- type: 'branch',
487
- branch: 'feature-branch',
488
- });
489
- const result = await executeAction('create_pr', worktree, makeEnv(), makeConfig(), makeDeps());
490
- expect(result.success).toBe(false);
491
- expect(result.message).toContain('PR already exists');
492
- expect(result.message).toContain('#42');
493
- });
494
- it('creates PR successfully with configured draftPr', async () => {
495
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
496
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
497
- vi.mocked(github.createPr).mockReturnValue({
498
- number: 123,
499
- url: 'https://github.com/owner/repo/pull/123',
500
- state: 'OPEN',
501
- isDraft: true,
502
- title: 'New PR',
503
- headBranch: 'feature-branch',
504
- baseBranch: 'main',
505
- });
506
- vi.mocked(inquirer.prompt).mockResolvedValue({ title: 'My New PR' });
507
- const worktree = makeWorktree({
508
- type: 'branch',
509
- branch: 'feature-branch',
510
- });
511
- const config = makeConfig({ draftPr: true });
512
- const result = await executeAction('create_pr', worktree, makeEnv(), config, makeDeps());
513
- expect(result.success).toBe(true);
514
- expect(result.message).toContain('Created PR #123');
515
- expect(result.shouldRefresh).toBe(true);
516
- expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({ draft: true }), expect.any(String));
517
- consoleSpy.mockRestore();
518
- });
519
- it('prompts for draft status when not configured', async () => {
520
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
521
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
522
- vi.mocked(github.createPr).mockReturnValue({
523
- number: 123,
524
- url: 'https://github.com/owner/repo/pull/123',
525
- state: 'OPEN',
526
- isDraft: false,
527
- title: 'New PR',
528
- headBranch: 'feature-branch',
529
- baseBranch: 'main',
530
- });
531
- vi.mocked(inquirer.prompt)
532
- .mockResolvedValueOnce({ title: 'My PR Title' })
533
- .mockResolvedValueOnce({ draft: false });
534
- const worktree = makeWorktree({
535
- type: 'branch',
536
- branch: 'feature-branch',
537
- });
538
- const config = makeConfig({ draftPr: undefined });
539
- const result = await executeAction('create_pr', worktree, makeEnv(), config, makeDeps());
540
- expect(result.success).toBe(true);
541
- expect(inquirer.prompt).toHaveBeenCalledTimes(2);
542
- consoleSpy.mockRestore();
543
- });
544
- it('handles PR creation failure', async () => {
545
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
546
- vi.mocked(github.createPr).mockImplementation(() => {
547
- throw new Error('GitHub API error');
548
- });
549
- vi.mocked(inquirer.prompt).mockResolvedValue({ title: 'My PR' });
550
- const worktree = makeWorktree({
551
- type: 'branch',
552
- branch: 'feature-branch',
553
- });
554
- const config = makeConfig({ draftPr: false });
555
- const result = await executeAction('create_pr', worktree, makeEnv(), config, makeDeps());
556
- expect(result.success).toBe(false);
557
- expect(result.message).toContain('Failed to create PR');
558
- });
559
- });
560
- describe('link_configs action', () => {
561
- it('returns error when repo root not found', async () => {
562
- vi.mocked(git.getRepoRoot).mockReturnValue(null);
563
- const worktree = makeWorktree({
564
- type: 'branch',
565
- branch: 'feature-branch',
566
- });
567
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
568
- expect(result.success).toBe(false);
569
- expect(result.message).toContain('Could not find repository root');
570
- });
571
- it('returns error for main worktree', async () => {
572
- vi.mocked(git.getRepoRoot).mockReturnValue('/home/user/repo');
573
- const worktree = makeWorktree({ type: 'main' });
574
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
575
- expect(result.success).toBe(false);
576
- expect(result.message).toContain('Cannot link configs to main worktree');
577
- });
578
- it('handles link configs failure', async () => {
579
- vi.mocked(git.getRepoRoot).mockReturnValue('/home/user/repo');
580
- // Mock the dynamic import to throw
581
- vi.doMock('../wtlink/link-configs.js', () => ({
582
- run: vi.fn().mockRejectedValue(new Error('Link failed')),
583
- }));
584
- const worktree = makeWorktree({
585
- type: 'branch',
586
- branch: 'feature-branch',
587
- path: '/home/user/feature-branch',
588
- });
589
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
590
- // Either succeeds or fails with proper message
591
- if (!result.success) {
592
- expect(result.message).toContain('Failed to link configs');
593
- }
594
- });
595
- it('successfully links configs', async () => {
596
- vi.mocked(git.getRepoRoot).mockReturnValue('/home/user/repo');
597
- // Mock the dynamic import to succeed
598
- vi.doMock('../wtlink/link-configs.js', () => ({
599
- run: vi.fn().mockResolvedValue(undefined),
600
- }));
601
- const worktree = makeWorktree({
602
- type: 'branch',
603
- branch: 'feature-branch',
604
- path: '/home/user/feature-branch',
605
- });
606
- const result = await executeAction('link_configs', worktree, makeEnv(), makeConfig(), makeDeps());
607
- // Should succeed
608
- expect(result.success).toBe(true);
609
- expect(result.message).toContain('linked successfully');
610
- });
611
- });
612
- describe('show_details action', () => {
613
- it('shows draft indicator for draft PRs', async () => {
614
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
615
- const worktree = makeWorktree({
616
- type: 'pr',
617
- prNumber: 42,
618
- prState: 'OPEN',
619
- isDraft: true,
620
- branch: 'feature-42',
621
- });
622
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
623
- expect(result.success).toBe(true);
624
- // Check that draft was mentioned in output
625
- const calls = consoleSpy.mock.calls.map((call) => String(call[0]));
626
- expect(calls.some((c) => c.includes('Draft'))).toBe(true);
627
- consoleSpy.mockRestore();
628
- });
629
- it('shows branch worktree details', async () => {
630
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
631
- const worktree = makeWorktree({
632
- type: 'branch',
633
- branch: 'feature-branch',
634
- hasChanges: true,
635
- });
636
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
637
- expect(result.success).toBe(true);
638
- consoleSpy.mockRestore();
639
- });
640
- it('shows detached worktree details', async () => {
641
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
642
- const worktree = makeWorktree({
643
- type: 'detached',
644
- branch: null,
645
- });
646
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
647
- expect(result.success).toBe(true);
648
- consoleSpy.mockRestore();
649
- });
650
- it('shows all worktree fields in output', async () => {
651
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
652
- const worktree = makeWorktree({
653
- path: '/home/user/repo.pr1',
654
- name: 'repo.pr1',
655
- type: 'pr',
656
- prNumber: 123,
657
- prState: 'OPEN',
658
- branch: 'feat/test',
659
- commit: 'abc123def',
660
- hasChanges: false,
661
- });
662
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
663
- expect(result.success).toBe(true);
664
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
665
- expect(output).toContain('Path');
666
- expect(output).toContain('Name');
667
- expect(output).toContain('Branch');
668
- expect(output).toContain('Commit');
669
- expect(output).toContain('Type');
670
- consoleSpy.mockRestore();
671
- });
672
- it('shows clean status for worktree without changes', async () => {
673
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
674
- const worktree = makeWorktree({
675
- type: 'branch',
676
- branch: 'clean-branch',
677
- hasChanges: false,
678
- });
679
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
680
- expect(result.success).toBe(true);
681
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
682
- expect(output).toContain('Clean');
683
- consoleSpy.mockRestore();
684
- });
685
- it('shows uncommitted changes warning', async () => {
686
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
687
- const worktree = makeWorktree({
688
- type: 'branch',
689
- branch: 'dirty-branch',
690
- hasChanges: true,
691
- });
692
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
693
- expect(result.success).toBe(true);
694
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
695
- expect(output).toContain('uncommitted');
696
- consoleSpy.mockRestore();
697
- });
698
- it('shows PR URL from github.getPr when prUrl is not stored', async () => {
699
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
700
- vi.mocked(github.getPr).mockReturnValue({
701
- number: 42,
702
- url: 'https://github.com/owner/repo/pull/42',
703
- state: 'OPEN',
704
- isDraft: false,
705
- title: 'Test PR',
706
- headBranch: 'feature-42',
707
- baseBranch: 'main',
708
- });
709
- const worktree = makeWorktree({
710
- type: 'pr',
711
- prNumber: 42,
712
- prState: 'OPEN',
713
- branch: 'feature-42',
714
- // prUrl is not set
715
- });
716
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
717
- expect(result.success).toBe(true);
718
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
719
- expect(output).toContain('https://github.com/owner/repo/pull/42');
720
- consoleSpy.mockRestore();
721
- });
722
- it('handles PR URL fetch failure gracefully', async () => {
723
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
724
- vi.mocked(github.getPr).mockReturnValue(null);
725
- const worktree = makeWorktree({
726
- type: 'pr',
727
- prNumber: 42,
728
- prState: 'OPEN',
729
- branch: 'feature-42',
730
- // prUrl is not set
731
- });
732
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
733
- // Should succeed even if PR URL fetch fails
734
- expect(result.success).toBe(true);
735
- consoleSpy.mockRestore();
736
- });
737
- it('shows recent commits when git log succeeds', async () => {
738
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
739
- vi.mocked(git.exec).mockReturnValue('abc1234 First commit\ndef5678 Second commit\nghi9012 Third commit');
740
- const worktree = makeWorktree({
741
- type: 'branch',
742
- branch: 'feature-branch',
743
- });
744
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
745
- expect(result.success).toBe(true);
746
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
747
- expect(output).toContain('Recent commits');
748
- expect(output).toContain('First commit');
749
- consoleSpy.mockRestore();
750
- });
751
- it('handles git log failure gracefully', async () => {
752
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
753
- vi.mocked(git.exec).mockImplementation(() => {
754
- throw new Error('git log failed');
755
- });
756
- const worktree = makeWorktree({
757
- type: 'branch',
758
- branch: 'feature-branch',
759
- });
760
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
761
- // Should succeed even if git log fails
762
- expect(result.success).toBe(true);
763
- consoleSpy.mockRestore();
764
- });
765
- });
766
- describe('open_pr_url action with mocked github', () => {
767
- it('returns error when PR is not found', async () => {
768
- vi.mocked(github.getPr).mockReturnValue(null);
769
- const deps = makeDeps();
770
- const worktree = makeWorktree({
771
- type: 'pr',
772
- prNumber: 999,
773
- prState: 'OPEN',
774
- });
775
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
776
- expect(result.success).toBe(false);
777
- expect(result.message).toContain('Could not find PR');
778
- });
779
- it('opens PR URL successfully when PR is found', async () => {
780
- vi.mocked(github.getPr).mockReturnValue({
781
- number: 42,
782
- url: 'https://github.com/owner/repo/pull/42',
783
- state: 'OPEN',
784
- isDraft: false,
785
- title: 'Test PR',
786
- headBranch: 'feature-42',
787
- baseBranch: 'main',
788
- });
789
- const deps = makeDeps();
790
- const worktree = makeWorktree({
791
- type: 'pr',
792
- prNumber: 42,
793
- prState: 'OPEN',
794
- });
795
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
796
- expect(result.success).toBe(true);
797
- expect(result.message).toContain('Opened PR');
798
- expect(deps.openUrl).toHaveBeenCalledWith('https://github.com/owner/repo/pull/42');
799
- });
800
- it('handles error when opening URL fails', async () => {
801
- vi.mocked(github.getPr).mockReturnValue({
802
- number: 42,
803
- url: 'https://github.com/owner/repo/pull/42',
804
- state: 'OPEN',
805
- isDraft: false,
806
- title: 'Test PR',
807
- headBranch: 'feature-42',
808
- baseBranch: 'main',
809
- });
810
- const deps = makeDeps({
811
- openUrl: vi.fn().mockImplementation(() => {
812
- throw new Error('Failed to open browser');
813
- }),
814
- });
815
- const worktree = makeWorktree({
816
- type: 'pr',
817
- prNumber: 42,
818
- prState: 'OPEN',
819
- });
820
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
821
- expect(result.success).toBe(false);
822
- expect(result.message).toContain('Failed to open PR');
823
- });
824
- });
825
- describe('open_editor action edge cases', () => {
826
- it('prefers vscode when preferredEditor is vscode even if cursor available', async () => {
827
- const deps = makeDeps();
828
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
829
- const env = makeEnv({ hasVscode: true, hasCursor: true, defaultEditor: 'vscode' });
830
- const config = makeConfig({ preferredEditor: 'vscode' });
831
- const result = await executeAction('open_editor', worktree, env, config, deps);
832
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', ['/home/user/repo.pr1']);
833
- expect(result.success).toBe(true);
834
- });
835
- it('prefers cursor when preferredEditor is cursor even if vscode available', async () => {
836
- const deps = makeDeps();
837
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
838
- const env = makeEnv({ hasVscode: true, hasCursor: true, defaultEditor: 'vscode' });
839
- const config = makeConfig({ preferredEditor: 'cursor' });
840
- const result = await executeAction('open_editor', worktree, env, config, deps);
841
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', ['/home/user/repo.pr1']);
842
- expect(result.success).toBe(true);
843
- });
844
- it('falls back to cursor when preferredEditor is vscode but vscode not installed', async () => {
845
- const deps = makeDeps();
846
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
847
- const env = makeEnv({ hasVscode: false, hasCursor: true, defaultEditor: 'cursor' });
848
- const config = makeConfig({ preferredEditor: 'vscode' });
849
- const result = await executeAction('open_editor', worktree, env, config, deps);
850
- expect(deps.spawnDetached).toHaveBeenCalledWith('cursor', ['/home/user/repo.pr1']);
851
- expect(result.success).toBe(true);
852
- expect(result.message).toContain('Cursor');
853
- });
854
- it('falls back to vscode when preferredEditor is cursor but cursor not installed', async () => {
855
- const deps = makeDeps();
856
- const worktree = makeWorktree({ path: '/home/user/repo.pr1' });
857
- const env = makeEnv({ hasVscode: true, hasCursor: false, defaultEditor: 'vscode' });
858
- const config = makeConfig({ preferredEditor: 'cursor' });
859
- const result = await executeAction('open_editor', worktree, env, config, deps);
860
- expect(deps.spawnDetached).toHaveBeenCalledWith('code', ['/home/user/repo.pr1']);
861
- expect(result.success).toBe(true);
862
- expect(result.message).toContain('VSCode');
863
- });
864
- });
865
- describe('copy_path action', () => {
866
- it('copies the correct path', async () => {
867
- const deps = makeDeps();
868
- const worktree = makeWorktree({ path: '/home/user/my-project' });
869
- const result = await executeAction('copy_path', worktree, makeEnv(), makeConfig(), deps);
870
- expect(deps.copyToClipboard).toHaveBeenCalledWith('/home/user/my-project');
871
- expect(result.success).toBe(true);
872
- expect(result.message).toContain('/home/user/my-project');
873
- });
874
- });
875
- describe('formatBranchAsTitle', () => {
876
- it('removes feat/ prefix', () => {
877
- expect(formatBranchAsTitle('feat/add-new-api')).toBe('Add new api');
878
- });
879
- it('removes fix/ prefix', () => {
880
- expect(formatBranchAsTitle('fix/bad-login')).toBe('Bad login');
881
- });
882
- it('removes chore/ prefix', () => {
883
- expect(formatBranchAsTitle('chore/update-deps')).toBe('Update deps');
884
- });
885
- it('removes docs/ prefix', () => {
886
- expect(formatBranchAsTitle('docs/add-guide')).toBe('Add guide');
887
- });
888
- it('removes refactor/ prefix', () => {
889
- expect(formatBranchAsTitle('refactor/clean-code')).toBe('Clean code');
890
- });
891
- it('removes test/ prefix', () => {
892
- expect(formatBranchAsTitle('test/add-more-tests')).toBe('Add more tests');
893
- });
894
- it('removes style/ prefix', () => {
895
- expect(formatBranchAsTitle('style/fix-lint')).toBe('Fix lint');
896
- });
897
- it('removes feature/ prefix', () => {
898
- expect(formatBranchAsTitle('feature/new-api')).toBe('New api');
899
- });
900
- it('removes bugfix/ prefix', () => {
901
- expect(formatBranchAsTitle('bugfix/fix-null')).toBe('Fix null');
902
- });
903
- it('removes trailing random suffixes', () => {
904
- expect(formatBranchAsTitle('feat/add-login-abc123')).toBe('Add login');
905
- expect(formatBranchAsTitle('add-feature-xyz789')).toBe('Add feature');
906
- });
907
- it('replaces hyphens with spaces', () => {
908
- // Note: 'branch' is 6 chars so it gets removed as a suffix
909
- expect(formatBranchAsTitle('my-feature-thing')).toBe('My feature thing');
910
- });
911
- it('replaces underscores with spaces', () => {
912
- expect(formatBranchAsTitle('my_new_api')).toBe('My new api');
913
- });
914
- it('capitalizes first letter', () => {
915
- expect(formatBranchAsTitle('lower')).toBe('Lower');
916
- });
917
- it('removes trailing 6+ char random suffix', () => {
918
- // Words like 'branch' (6 chars) are also removed - intentional behavior
919
- expect(formatBranchAsTitle('my-feature-branch')).toBe('My feature');
920
- });
921
- it('handles already capitalized input', () => {
922
- expect(formatBranchAsTitle('Already-Capitalized')).toBe('Already Capitalized');
923
- });
924
- it('handles simple branch name', () => {
925
- expect(formatBranchAsTitle('main')).toBe('Main');
926
- });
927
- it('handles complex branch names', () => {
928
- expect(formatBranchAsTitle('feat/make-lswt-more-interactive-b5y1o2')).toBe('Make lswt more interactive');
929
- });
930
- it('handles mixed separators', () => {
931
- expect(formatBranchAsTitle('my_feature-branch_name')).toBe('My feature branch name');
932
- });
933
- });
934
- describe('checkout_pr action', () => {
935
- it('returns error for non-remote_pr worktree type', async () => {
936
- const worktree = makeWorktree({
937
- type: 'pr',
938
- prNumber: 42,
939
- prState: 'OPEN',
940
- });
941
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
942
- expect(result.success).toBe(false);
943
- expect(result.message).toContain('Can only checkout remote PRs');
944
- });
945
- it('returns error when worktree has no PR number', async () => {
946
- const worktree = makeWorktree({
947
- type: 'remote_pr',
948
- prNumber: null,
949
- prState: 'OPEN',
950
- });
951
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
952
- expect(result.success).toBe(false);
953
- expect(result.message).toContain('Can only checkout remote PRs');
954
- });
955
- it('returns error when worktree has no branch', async () => {
956
- const worktree = makeWorktree({
957
- type: 'remote_pr',
958
- prNumber: 42,
959
- prState: 'OPEN',
960
- branch: null,
961
- });
962
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
963
- expect(result.success).toBe(false);
964
- expect(result.message).toContain('no associated branch');
965
- });
966
- it('returns error when repo root cannot be found', async () => {
967
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue(null);
968
- const worktree = makeWorktree({
969
- type: 'remote_pr',
970
- prNumber: 42,
971
- prState: 'OPEN',
972
- branch: 'feat/remote-feature',
973
- });
974
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
975
- expect(result.success).toBe(false);
976
- expect(result.message).toContain('Could not find repository root');
977
- });
978
- it('successfully creates worktree for remote PR', async () => {
979
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
980
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
981
- vi.mocked(git.addWorktree).mockImplementation(() => { });
982
- // Mock git.exec to return empty string (git fetch succeeds)
983
- vi.mocked(git.exec).mockReturnValue('');
984
- const worktree = makeWorktree({
985
- type: 'remote_pr',
986
- prNumber: 42,
987
- prState: 'OPEN',
988
- branch: 'feat/remote-feature',
989
- prTitle: 'Add remote feature',
990
- prUrl: 'https://github.com/owner/repo/pull/42',
991
- });
992
- const config = makeConfig({ worktreePattern: '{repo}.pr{number}', worktreeParent: '..' });
993
- const result = await executeAction('checkout_pr', worktree, makeEnv(), config, makeDeps());
994
- expect(result.success).toBe(true);
995
- expect(result.message).toContain('Created worktree for PR #42');
996
- expect(result.shouldRefresh).toBe(true);
997
- expect(git.addWorktree).toHaveBeenCalledWith(expect.stringContaining('.pr42'), 'feat/remote-feature', expect.objectContaining({ cwd: '/home/user/repo' }));
998
- consoleSpy.mockRestore();
999
- });
1000
- it('handles git fetch failure', async () => {
1001
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1002
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/home/user/repo');
1003
- // Mock git.exec to throw (git fetch fails)
1004
- vi.mocked(git.exec).mockImplementation(() => {
1005
- throw new Error('Failed to fetch branch');
1006
- });
1007
- const worktree = makeWorktree({
1008
- type: 'remote_pr',
1009
- prNumber: 42,
1010
- prState: 'OPEN',
1011
- branch: 'feat/remote-feature',
1012
- });
1013
- const result = await executeAction('checkout_pr', worktree, makeEnv(), makeConfig(), makeDeps());
1014
- expect(result.success).toBe(false);
1015
- expect(result.message).toContain('Failed to checkout PR');
1016
- consoleSpy.mockRestore();
1017
- });
1018
- });
1019
- describe('show_details action for remote_pr', () => {
1020
- it('shows PR title for remote PRs', async () => {
1021
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1022
- const worktree = makeWorktree({
1023
- type: 'remote_pr',
1024
- prNumber: 42,
1025
- prState: 'OPEN',
1026
- branch: 'feat/remote-feature',
1027
- prTitle: 'Add amazing new feature',
1028
- prUrl: 'https://github.com/owner/repo/pull/42',
1029
- });
1030
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1031
- expect(result.success).toBe(true);
1032
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1033
- expect(output).toContain('Add amazing new feature');
1034
- consoleSpy.mockRestore();
1035
- });
1036
- it('shows PR URL for remote PRs', async () => {
1037
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1038
- const worktree = makeWorktree({
1039
- type: 'remote_pr',
1040
- prNumber: 42,
1041
- prState: 'OPEN',
1042
- branch: 'feat/remote-feature',
1043
- prTitle: 'Add feature',
1044
- prUrl: 'https://github.com/owner/repo/pull/42',
1045
- });
1046
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1047
- expect(result.success).toBe(true);
1048
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1049
- expect(output).toContain('https://github.com/owner/repo/pull/42');
1050
- consoleSpy.mockRestore();
1051
- });
1052
- it('shows message about no local checkout for remote PRs', async () => {
1053
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1054
- const worktree = makeWorktree({
1055
- type: 'remote_pr',
1056
- prNumber: 42,
1057
- prState: 'OPEN',
1058
- branch: 'feat/remote-feature',
1059
- prTitle: 'Add feature',
1060
- prUrl: 'https://github.com/owner/repo/pull/42',
1061
- });
1062
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1063
- expect(result.success).toBe(true);
1064
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1065
- expect(output).toContain('No local checkout');
1066
- consoleSpy.mockRestore();
1067
- });
1068
- it('does not show "Changes" line for remote PRs', async () => {
1069
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
1070
- const worktree = makeWorktree({
1071
- type: 'remote_pr',
1072
- prNumber: 42,
1073
- prState: 'OPEN',
1074
- branch: 'feat/remote-feature',
1075
- prTitle: 'Add feature',
1076
- prUrl: 'https://github.com/owner/repo/pull/42',
1077
- hasChanges: false,
1078
- });
1079
- const result = await executeAction('show_details', worktree, makeEnv(), makeConfig(), makeDeps());
1080
- expect(result.success).toBe(true);
1081
- const output = consoleSpy.mock.calls.map((call) => String(call[0])).join('\n');
1082
- // For remote PRs, "Changes:" line should not appear since there's no local path
1083
- expect(output).not.toMatch(/Changes:.*Clean/);
1084
- consoleSpy.mockRestore();
1085
- });
1086
- });
1087
- describe('open_pr_url action for remote_pr', () => {
1088
- it('uses stored prUrl for remote PRs', async () => {
1089
- const deps = makeDeps();
1090
- const worktree = makeWorktree({
1091
- type: 'remote_pr',
1092
- prNumber: 42,
1093
- prState: 'OPEN',
1094
- prUrl: 'https://github.com/owner/repo/pull/42',
1095
- });
1096
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
1097
- expect(result.success).toBe(true);
1098
- expect(result.message).toContain('Opened PR #42');
1099
- expect(deps.openUrl).toHaveBeenCalledWith('https://github.com/owner/repo/pull/42');
1100
- // Should not call github.getPr since we have the URL stored
1101
- expect(github.getPr).not.toHaveBeenCalled();
1102
- });
1103
- it('falls back to fetching URL when prUrl is not stored', async () => {
1104
- vi.mocked(github.getPr).mockReturnValue({
1105
- number: 42,
1106
- url: 'https://github.com/owner/repo/pull/42',
1107
- state: 'OPEN',
1108
- isDraft: false,
1109
- title: 'Test PR',
1110
- headBranch: 'feature-42',
1111
- baseBranch: 'main',
1112
- });
1113
- const deps = makeDeps();
1114
- const worktree = makeWorktree({
1115
- type: 'remote_pr',
1116
- prNumber: 42,
1117
- prState: 'OPEN',
1118
- prUrl: undefined, // No stored URL
1119
- });
1120
- const result = await executeAction('open_pr_url', worktree, makeEnv(), makeConfig(), deps);
1121
- expect(result.success).toBe(true);
1122
- expect(github.getPr).toHaveBeenCalledWith(42);
1123
- expect(deps.openUrl).toHaveBeenCalledWith('https://github.com/owner/repo/pull/42');
1124
- });
1125
- });
1126
- });
1127
- //# sourceMappingURL=action-executors.test.js.map