11/**
2- * Isolated tests for init wizard interactive prompts.
2+ * Interactive Dispatcher Tests
33 *
4- * Uses mock.module() to stub @clack/prompts — kept isolated so the
5- * module-level mock does not leak into other test files .
4+ * Tests for the init wizard interactive prompt handlers. Uses spyOn on
5+ * @clack /prompts namespace to intercept calls from named imports .
66 */
77
8- import { beforeEach , describe , expect , mock , test } from "bun:test" ;
9- import type { WizardOptions } from "../../src/lib/init/types.js" ;
10-
11- // Controllable mock implementations — reset per test via beforeEach
12- let selectImpl : ReturnType < typeof mock > ;
13- let multiselectImpl : ReturnType < typeof mock > ;
14- let confirmImpl : ReturnType < typeof mock > ;
15- const logMock = { info : mock ( ) , error : mock ( ) , warn : mock ( ) } ;
16- const cancelMock = mock ( ) ;
17-
18- mock . module ( "@clack/prompts" , ( ) => ( {
19- select : ( ...args : unknown [ ] ) => selectImpl ( ...args ) ,
20- multiselect : ( ...args : unknown [ ] ) => multiselectImpl ( ...args ) ,
21- confirm : ( ...args : unknown [ ] ) => confirmImpl ( ...args ) ,
22- log : logMock ,
23- cancel : ( ...args : unknown [ ] ) => cancelMock ( ...args ) ,
24- isCancel : ( v : unknown ) => v === Symbol . for ( "cancel" ) ,
25- note : mock ( ) ,
26- outro : mock ( ) ,
27- intro : mock ( ) ,
28- spinner : ( ) => ( { start : mock ( ) , stop : mock ( ) , message : mock ( ) } ) ,
29- } ) ) ;
30-
31- const { handleInteractive } = await import ( "../../src/lib/init/interactive.js" ) ;
8+ import { afterEach , beforeEach , describe , expect , spyOn , test } from "bun:test" ;
9+ // biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
10+ import * as clack from "@clack/prompts" ;
11+ import { handleInteractive } from "../../../src/lib/init/interactive.js" ;
12+ import type { WizardOptions } from "../../../src/lib/init/types.js" ;
13+
14+ const noop = ( ) => {
15+ /* suppress clack output */
16+ } ;
17+
18+ let selectSpy : ReturnType < typeof spyOn > ;
19+ let multiselectSpy : ReturnType < typeof spyOn > ;
20+ let confirmSpy : ReturnType < typeof spyOn > ;
21+ let logInfoSpy : ReturnType < typeof spyOn > ;
22+ let logErrorSpy : ReturnType < typeof spyOn > ;
23+ let logWarnSpy : ReturnType < typeof spyOn > ;
24+ let cancelSpy : ReturnType < typeof spyOn > ;
25+ let isCancelSpy : ReturnType < typeof spyOn > ;
3226
3327function makeOptions ( overrides ?: Partial < WizardOptions > ) : WizardOptions {
3428 return {
@@ -44,13 +38,33 @@ function makeOptions(overrides?: Partial<WizardOptions>): WizardOptions {
4438}
4539
4640beforeEach ( ( ) => {
47- selectImpl = mock ( ( ) => Promise . resolve ( "default" ) ) ;
48- multiselectImpl = mock ( ( ) => Promise . resolve ( [ ] ) ) ;
49- confirmImpl = mock ( ( ) => Promise . resolve ( true ) ) ;
50- logMock . info . mockClear ( ) ;
51- logMock . error . mockClear ( ) ;
52- logMock . warn . mockClear ( ) ;
53- cancelMock . mockClear ( ) ;
41+ selectSpy = spyOn ( clack , "select" ) . mockImplementation (
42+ ( ) => Promise . resolve ( "default" ) as any
43+ ) ;
44+ multiselectSpy = spyOn ( clack , "multiselect" ) . mockImplementation (
45+ ( ) => Promise . resolve ( [ ] ) as any
46+ ) ;
47+ confirmSpy = spyOn ( clack , "confirm" ) . mockImplementation (
48+ ( ) => Promise . resolve ( true ) as any
49+ ) ;
50+ logInfoSpy = spyOn ( clack . log , "info" ) . mockImplementation ( noop ) ;
51+ logErrorSpy = spyOn ( clack . log , "error" ) . mockImplementation ( noop ) ;
52+ logWarnSpy = spyOn ( clack . log , "warn" ) . mockImplementation ( noop ) ;
53+ cancelSpy = spyOn ( clack , "cancel" ) . mockImplementation ( noop ) ;
54+ isCancelSpy = spyOn ( clack , "isCancel" ) . mockImplementation (
55+ ( v : unknown ) => v === Symbol . for ( "cancel" )
56+ ) ;
57+ } ) ;
58+
59+ afterEach ( ( ) => {
60+ selectSpy . mockRestore ( ) ;
61+ multiselectSpy . mockRestore ( ) ;
62+ confirmSpy . mockRestore ( ) ;
63+ logInfoSpy . mockRestore ( ) ;
64+ logErrorSpy . mockRestore ( ) ;
65+ logWarnSpy . mockRestore ( ) ;
66+ cancelSpy . mockRestore ( ) ;
67+ isCancelSpy . mockRestore ( ) ;
5468} ) ;
5569
5670describe ( "handleInteractive dispatcher" , ( ) => {
@@ -76,7 +90,7 @@ describe("handleSelect", () => {
7690 ) ;
7791
7892 expect ( result ) . toEqual ( { selectedApp : "my-app" } ) ;
79- expect ( logMock . info ) . toHaveBeenCalled ( ) ;
93+ expect ( logInfoSpy ) . toHaveBeenCalled ( ) ;
8094 } ) ;
8195
8296 test ( "cancels with --yes when multiple options exist" , async ( ) => {
@@ -91,7 +105,7 @@ describe("handleSelect", () => {
91105 ) ;
92106
93107 expect ( result ) . toEqual ( { cancelled : true } ) ;
94- expect ( logMock . error ) . toHaveBeenCalled ( ) ;
108+ expect ( logErrorSpy ) . toHaveBeenCalled ( ) ;
95109 } ) ;
96110
97111 test ( "cancels when options list is empty" , async ( ) => {
@@ -123,7 +137,7 @@ describe("handleSelect", () => {
123137 } ) ;
124138
125139 test ( "calls clack select in interactive mode" , async ( ) => {
126- selectImpl = mock ( ( ) => Promise . resolve ( "vue" ) ) ;
140+ selectSpy . mockImplementation ( ( ) => Promise . resolve ( "vue" ) as any ) ;
127141
128142 const result = await handleInteractive (
129143 {
@@ -136,11 +150,13 @@ describe("handleSelect", () => {
136150 ) ;
137151
138152 expect ( result ) . toEqual ( { selectedApp : "vue" } ) ;
139- expect ( selectImpl ) . toHaveBeenCalled ( ) ;
153+ expect ( selectSpy ) . toHaveBeenCalled ( ) ;
140154 } ) ;
141155
142156 test ( "throws WizardCancelledError on user cancellation" , async ( ) => {
143- selectImpl = mock ( ( ) => Promise . resolve ( Symbol . for ( "cancel" ) ) ) ;
157+ selectSpy . mockImplementation (
158+ ( ) => Promise . resolve ( Symbol . for ( "cancel" ) ) as any
159+ ) ;
144160
145161 await expect (
146162 handleInteractive (
@@ -195,7 +211,9 @@ describe("handleMultiSelect", () => {
195211
196212 test ( "prepends errorMonitoring when available but not user-selected" , async ( ) => {
197213 // User selects only sessionReplay, but errorMonitoring is available (required)
198- multiselectImpl = mock ( ( ) => Promise . resolve ( [ "sessionReplay" ] ) ) ;
214+ multiselectSpy . mockImplementation (
215+ ( ) => Promise . resolve ( [ "sessionReplay" ] ) as any
216+ ) ;
199217
200218 const result = await handleInteractive (
201219 {
@@ -217,7 +235,9 @@ describe("handleMultiSelect", () => {
217235 } ) ;
218236
219237 test ( "throws WizardCancelledError when user cancels multi-select" , async ( ) => {
220- multiselectImpl = mock ( ( ) => Promise . resolve ( Symbol . for ( "cancel" ) ) ) ;
238+ multiselectSpy . mockImplementation (
239+ ( ) => Promise . resolve ( Symbol . for ( "cancel" ) ) as any
240+ ) ;
221241
222242 await expect (
223243 handleInteractive (
@@ -233,7 +253,9 @@ describe("handleMultiSelect", () => {
233253 } ) ;
234254
235255 test ( "excludes errorMonitoring from multiselect options (always included)" , async ( ) => {
236- multiselectImpl = mock ( ( ) => Promise . resolve ( [ "performanceMonitoring" ] ) ) ;
256+ multiselectSpy . mockImplementation (
257+ ( ) => Promise . resolve ( [ "performanceMonitoring" ] ) as any
258+ ) ;
237259
238260 await handleInteractive (
239261 {
@@ -246,7 +268,7 @@ describe("handleMultiSelect", () => {
246268 ) ;
247269
248270 // The options passed to multiselect should NOT include errorMonitoring
249- const callArgs = multiselectImpl . mock . calls [ 0 ] [ 0 ] as {
271+ const callArgs = multiselectSpy . mock . calls [ 0 ] [ 0 ] as {
250272 options : Array < { value : string } > ;
251273 } ;
252274 const values = callArgs . options . map ( ( o : { value : string } ) => o . value ) ;
@@ -283,7 +305,7 @@ describe("handleConfirm", () => {
283305 } ) ;
284306
285307 test ( "returns addExample based on user choice for example prompts" , async ( ) => {
286- confirmImpl = mock ( ( ) => Promise . resolve ( false ) ) ;
308+ confirmSpy . mockImplementation ( ( ) => Promise . resolve ( false ) as any ) ;
287309
288310 const result = await handleInteractive (
289311 {
@@ -298,7 +320,9 @@ describe("handleConfirm", () => {
298320 } ) ;
299321
300322 test ( "throws WizardCancelledError when user cancels confirm" , async ( ) => {
301- confirmImpl = mock ( ( ) => Promise . resolve ( Symbol . for ( "cancel" ) ) ) ;
323+ confirmSpy . mockImplementation (
324+ ( ) => Promise . resolve ( Symbol . for ( "cancel" ) ) as any
325+ ) ;
302326
303327 await expect (
304328 handleInteractive (
@@ -313,7 +337,7 @@ describe("handleConfirm", () => {
313337 } ) ;
314338
315339 test ( "returns action: stop when user declines non-example prompt" , async ( ) => {
316- confirmImpl = mock ( ( ) => Promise . resolve ( false ) ) ;
340+ confirmSpy . mockImplementation ( ( ) => Promise . resolve ( false ) as any ) ;
317341
318342 const result = await handleInteractive (
319343 {
0 commit comments