Skip to content

Commit 19558b9

Browse files
Cygnusfear43081j
andauthored
feat(@clack/prompts): custom spinner indicator support (#247)
Signed-off-by: Alexander Mangel <cygnusfear@gmail.com> Co-authored-by: James Garbutt <43081j@users.noreply.github.com>
1 parent bb5ef54 commit 19558b9

File tree

4 files changed

+175
-2
lines changed

4 files changed

+175
-2
lines changed

.changeset/thin-socks-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clack/prompts": minor
3+
---
4+
5+
Added support for custom frames in spinner prompt

packages/prompts/src/spinner.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface SpinnerOptions extends CommonOptions {
1616
onCancel?: () => void;
1717
cancelMessage?: string;
1818
errorMessage?: string;
19+
frames?: string[];
20+
delay?: number;
1921
}
2022

2123
export interface SpinnerResult {
@@ -31,9 +33,9 @@ export const spinner = ({
3133
output = process.stdout,
3234
cancelMessage,
3335
errorMessage,
36+
frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'],
37+
delay = unicode ? 80 : 120,
3438
}: SpinnerOptions = {}): SpinnerResult => {
35-
const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'];
36-
const delay = unicode ? 80 : 120;
3739
const isCI = isCIFn();
3840

3941
let unblock: () => void;

packages/prompts/test/__snapshots__/spinner.test.ts.snap

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,51 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`spinner (isCI = false) > indicator customization > custom delay 1`] = `
4+
[
5+
"<cursor.hide>",
6+
"│
7+
",
8+
"◒ ",
9+
"<cursor.backward count=999>",
10+
"<erase.down>",
11+
"◐ ",
12+
"<cursor.backward count=999>",
13+
"<erase.down>",
14+
"◓ ",
15+
"<cursor.backward count=999>",
16+
"<erase.down>",
17+
"◑ ",
18+
"<cursor.backward count=999>",
19+
"<erase.down>",
20+
"◇
21+
",
22+
"<cursor.show>",
23+
]
24+
`;
25+
26+
exports[`spinner (isCI = false) > indicator customization > custom frames 1`] = `
27+
[
28+
"<cursor.hide>",
29+
"│
30+
",
31+
"🐴 ",
32+
"<cursor.backward count=999>",
33+
"<erase.down>",
34+
"🦋 ",
35+
"<cursor.backward count=999>",
36+
"<erase.down>",
37+
"🐙 ",
38+
"<cursor.backward count=999>",
39+
"<erase.down>",
40+
"🐶 ",
41+
"<cursor.backward count=999>",
42+
"<erase.down>",
43+
"◇
44+
",
45+
"<cursor.show>",
46+
]
47+
`;
48+
349
exports[`spinner (isCI = false) > message > sets message for next frame 1`] = `
450
[
551
"<cursor.hide>",
@@ -9,6 +55,11 @@ exports[`spinner (isCI = false) > message > sets message for next frame 1`] = `
955
"<cursor.backward count=999>",
1056
"<erase.down>",
1157
"◐ foo",
58+
"<cursor.backward count=999>",
59+
"<erase.down>",
60+
"◇
61+
",
62+
"<cursor.show>",
1263
]
1364
`;
1465
@@ -104,6 +155,11 @@ exports[`spinner (isCI = false) > start > renders frames at interval 1`] = `
104155
"<cursor.backward count=999>",
105156
"<erase.down>",
106157
"◑ ",
158+
"<cursor.backward count=999>",
159+
"<erase.down>",
160+
"◇
161+
",
162+
"<cursor.show>",
107163
]
108164
`;
109165
@@ -113,6 +169,11 @@ exports[`spinner (isCI = false) > start > renders message 1`] = `
113169
"│
114170
",
115171
"◒ foo",
172+
"<cursor.backward count=999>",
173+
"<erase.down>",
174+
"◇
175+
",
176+
"<cursor.show>",
116177
]
117178
`;
118179
@@ -122,6 +183,11 @@ exports[`spinner (isCI = false) > start > renders timer when indicator is "timer
122183
"│
123184
",
124185
"◒ [0s]",
186+
"<cursor.backward count=999>",
187+
"<erase.down>",
188+
"◇ [0s]
189+
",
190+
"<cursor.show>",
125191
]
126192
`;
127193
@@ -195,6 +261,38 @@ exports[`spinner (isCI = false) > stop > renders submit symbol and stops spinner
195261
]
196262
`;
197263
264+
exports[`spinner (isCI = true) > indicator customization > custom delay 1`] = `
265+
[
266+
"<cursor.hide>",
267+
"│
268+
",
269+
"◒ ...",
270+
"
271+
",
272+
"<cursor.backward count=999>",
273+
"<erase.down>",
274+
"◇
275+
",
276+
"<cursor.show>",
277+
]
278+
`;
279+
280+
exports[`spinner (isCI = true) > indicator customization > custom frames 1`] = `
281+
[
282+
"<cursor.hide>",
283+
"│
284+
",
285+
"🐴 ...",
286+
"
287+
",
288+
"<cursor.backward count=999>",
289+
"<erase.down>",
290+
"◇
291+
",
292+
"<cursor.show>",
293+
]
294+
`;
295+
198296
exports[`spinner (isCI = true) > message > sets message for next frame 1`] = `
199297
[
200298
"<cursor.hide>",
@@ -206,6 +304,13 @@ exports[`spinner (isCI = true) > message > sets message for next frame 1`] = `
206304
"<cursor.backward count=999>",
207305
"<erase.down>",
208306
"◐ foo...",
307+
"
308+
",
309+
"<cursor.backward count=999>",
310+
"<erase.down>",
311+
"◇
312+
",
313+
"<cursor.show>",
209314
]
210315
`;
211316
@@ -292,6 +397,13 @@ exports[`spinner (isCI = true) > start > renders frames at interval 1`] = `
292397
"│
293398
",
294399
"◒ ...",
400+
"
401+
",
402+
"<cursor.backward count=999>",
403+
"<erase.down>",
404+
"◇
405+
",
406+
"<cursor.show>",
295407
]
296408
`;
297409
@@ -301,6 +413,13 @@ exports[`spinner (isCI = true) > start > renders message 1`] = `
301413
"│
302414
",
303415
"◒ foo...",
416+
"
417+
",
418+
"<cursor.backward count=999>",
419+
"<erase.down>",
420+
"◇
421+
",
422+
"<cursor.show>",
304423
]
305424
`;
306425
@@ -310,6 +429,13 @@ exports[`spinner (isCI = true) > start > renders timer when indicator is "timer"
310429
"│
311430
",
312431
"◒ ...",
432+
"
433+
",
434+
"<cursor.backward count=999>",
435+
"<erase.down>",
436+
"◇ [0s]
437+
",
438+
"<cursor.show>",
313439
]
314440
`;
315441

packages/prompts/test/spinner.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => {
4545
vi.advanceTimersByTime(80);
4646
}
4747

48+
result.stop();
49+
4850
expect(output.buffer).toMatchSnapshot();
4951
});
5052

@@ -55,6 +57,8 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => {
5557

5658
vi.advanceTimersByTime(80);
5759

60+
result.stop();
61+
5862
expect(output.buffer).toMatchSnapshot();
5963
});
6064

@@ -65,6 +69,8 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => {
6569

6670
vi.advanceTimersByTime(80);
6771

72+
result.stop();
73+
6874
expect(output.buffer).toMatchSnapshot();
6975
});
7076
});
@@ -145,6 +151,40 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => {
145151

146152
vi.advanceTimersByTime(80);
147153

154+
result.stop();
155+
156+
expect(output.buffer).toMatchSnapshot();
157+
});
158+
});
159+
160+
describe('indicator customization', () => {
161+
test('custom frames', () => {
162+
const result = prompts.spinner({ output, frames: ['🐴', '🦋', '🐙', '🐶'] });
163+
164+
result.start();
165+
166+
// there are 4 frames
167+
for (let i = 0; i < 4; i++) {
168+
vi.advanceTimersByTime(80);
169+
}
170+
171+
result.stop();
172+
173+
expect(output.buffer).toMatchSnapshot();
174+
});
175+
176+
test('custom delay', () => {
177+
const result = prompts.spinner({ output, delay: 200 });
178+
179+
result.start();
180+
181+
// there are 4 frames
182+
for (let i = 0; i < 4; i++) {
183+
vi.advanceTimersByTime(200);
184+
}
185+
186+
result.stop();
187+
148188
expect(output.buffer).toMatchSnapshot();
149189
});
150190
});

0 commit comments

Comments
 (0)