[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

wip: add a bunch more components

James Garbutt (Jul 8, 2025, 11:05 PM +0100) c26349e9 c933bd91

+657 -498
+16 -15
packages/jsx/src/components/option.ts
··· 1 - import { type Option, isCancel } from '@clack/prompts'; 1 + import { type Option as PromptOption, isCancel } from '@clack/prompts'; 2 2 import type { JSX } from '../types.js'; 3 3 import { resolveChildren } from '../utils.js'; 4 4 5 - export interface OptionProps { 6 - value: unknown; 5 + export interface OptionProps<T> { 6 + value: T; 7 + hint?: string; 7 8 children?: JSX.Element | JSX.Element[] | string; 8 9 } 9 10 10 - export async function Option(props: OptionProps): Promise<Option<unknown>> { 11 - let label = ''; 12 - if (props.children) { 13 - const children = await resolveChildren(props.children); 11 + export async function Option<T>(props: OptionProps<T>): Promise<PromptOption<T>> { 12 + const { children, ...opts } = props; 13 + 14 + if (children) { 15 + const resolvedChildren = await resolveChildren(children); 14 16 const childStrings: string[] = []; 15 17 16 - for (const child of children) { 18 + for (const child of resolvedChildren) { 17 19 if (isCancel(child)) { 18 20 continue; 19 21 } 20 22 childStrings.push(String(child)); 21 23 } 22 24 23 - label = childStrings.join('\n'); 24 - } else { 25 - label = String(props.value); 25 + return { 26 + ...opts, 27 + label: childStrings.join('\n'), 28 + } as PromptOption<T>; 26 29 } 27 - return { 28 - value: props.value, 29 - label, 30 - }; 30 + 31 + return opts as PromptOption<T>; 31 32 }
+2 -8
packages/jsx/src/components/select.ts
··· 4 4 import { resolveChildren } from '../utils.js'; 5 5 6 6 export interface SelectProps extends Omit<SelectOptions<unknown>, 'options'> { 7 - children: JSX.Element[] | JSX.Element; 7 + children: JSX.Element[] | JSX.Element | string; 8 8 } 9 9 10 10 const isOptionLike = (obj: unknown): obj is Option<unknown> => { 11 - return ( 12 - obj !== null && 13 - typeof obj === 'object' && 14 - Object.hasOwnProperty.call(obj, 'label') && 15 - Object.hasOwnProperty.call(obj, 'value') && 16 - typeof (obj as { label: string }).label === 'string' 17 - ); 11 + return obj !== null && typeof obj === 'object' && Object.hasOwnProperty.call(obj, 'value'); 18 12 }; 19 13 20 14 export async function Select(props: SelectProps): ReturnType<typeof select> {
+1 -1
packages/jsx/src/types.ts
··· 1 1 namespace JSX { 2 - export type IntrinsicElements = {}; 2 + export type IntrinsicElements = never; 3 3 4 4 export type Element = Promise<unknown>; 5 5 }
-302
packages/jsx/test/__snapshots__/jsx.test.tsx.snap
··· 1 - // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 - 3 - exports[`jsx > Confirm > can set active text 1`] = ` 4 - [ 5 - "<cursor.hide>", 6 - "│ 7 - ◆ foo? 8 - │ ● DO IT / ○ No 9 - └ 10 - ", 11 - "<cursor.backward count=999><cursor.up count=4>", 12 - "<cursor.down count=1>", 13 - "<erase.down>", 14 - "◇ foo? 15 - │ DO IT", 16 - " 17 - ", 18 - "<cursor.show>", 19 - ] 20 - `; 21 - 22 - exports[`jsx > Confirm > can set inactive text 1`] = ` 23 - [ 24 - "<cursor.hide>", 25 - "│ 26 - ◆ foo? 27 - │ ● Yes / ○ DONT DO IT 28 - └ 29 - ", 30 - "<cursor.backward count=999><cursor.up count=4>", 31 - "<cursor.down count=1>", 32 - "<erase.down>", 33 - "◇ foo? 34 - │ Yes", 35 - " 36 - ", 37 - "<cursor.show>", 38 - ] 39 - `; 40 - 41 - exports[`jsx > Confirm > can set message 1`] = ` 42 - [ 43 - "<cursor.hide>", 44 - "│ 45 - ◆ foo? 46 - │ ● Yes / ○ No 47 - └ 48 - ", 49 - "<cursor.backward count=999><cursor.up count=4>", 50 - "<cursor.down count=1>", 51 - "<erase.down>", 52 - "◇ foo? 53 - │ Yes", 54 - " 55 - ", 56 - "<cursor.show>", 57 - ] 58 - `; 59 - 60 - exports[`jsx > Note > can render children as message 1`] = ` 61 - [ 62 - "│ 63 - ◇  ──────────╮ 64 - │ │ 65 - │ a message │ 66 - │ │ 67 - ├─────────────╯ 68 - ", 69 - ] 70 - `; 71 - 72 - exports[`jsx > Note > can render complex results as message 1`] = ` 73 - [ 74 - "<cursor.hide>", 75 - "│ 76 - ◆ say yes 77 - │ ● Yes / ○ No 78 - └ 79 - ", 80 - "<cursor.backward count=999><cursor.up count=4>", 81 - "<cursor.down count=1>", 82 - "<erase.down>", 83 - "◇ say yes 84 - │ Yes", 85 - " 86 - ", 87 - "<cursor.show>", 88 - "│ 89 - ◇  ─────╮ 90 - │ │ 91 - │ true │ 92 - │ │ 93 - ├────────╯ 94 - ", 95 - ] 96 - `; 97 - 98 - exports[`jsx > Note > can render multiple children as message 1`] = ` 99 - [ 100 - "<cursor.hide>", 101 - "│ 102 - ◆ say yes 103 - │ ● Yes / ○ No 104 - └ 105 - ", 106 - "<cursor.hide>", 107 - "│ 108 - ◆ say yes again 109 - │ ● Yes / ○ No 110 - └ 111 - ", 112 - "<cursor.backward count=999><cursor.up count=4>", 113 - "<cursor.down count=1>", 114 - "<erase.down>", 115 - "◇ say yes 116 - │ Yes", 117 - " 118 - ", 119 - "<cursor.show>", 120 - "<cursor.backward count=999><cursor.up count=4>", 121 - "<cursor.down count=1>", 122 - "<erase.down>", 123 - "◇ say yes again 124 - │ Yes", 125 - " 126 - ", 127 - "<cursor.show>", 128 - "│ 129 - ◇  ─────╮ 130 - │ │ 131 - │ true │ 132 - │ true │ 133 - │ │ 134 - ├────────╯ 135 - ", 136 - ] 137 - `; 138 - 139 - exports[`jsx > Note > can render string message 1`] = ` 140 - [ 141 - "│ 142 - ◇  ────╮ 143 - │ │ 144 - │ foo │ 145 - │ │ 146 - ├───────╯ 147 - ", 148 - ] 149 - `; 150 - 151 - exports[`jsx > Password > can set custom mask 1`] = ` 152 - [ 153 - "<cursor.hide>", 154 - "│ 155 - ◆ foo 156 - │ _ 157 - └ 158 - ", 159 - "<cursor.backward count=999><cursor.up count=4>", 160 - "<cursor.down count=2>", 161 - "<erase.line><cursor.left count=1>", 162 - "│ !_", 163 - "<cursor.down count=2>", 164 - "<cursor.backward count=999><cursor.up count=4>", 165 - "<cursor.down count=2>", 166 - "<erase.line><cursor.left count=1>", 167 - "│ !!_", 168 - "<cursor.down count=2>", 169 - "<cursor.backward count=999><cursor.up count=4>", 170 - "<cursor.down count=1>", 171 - "<erase.down>", 172 - "◇ foo 173 - │ !!", 174 - " 175 - ", 176 - "<cursor.show>", 177 - ] 178 - `; 179 - 180 - exports[`jsx > Password > renders password input 1`] = ` 181 - [ 182 - "<cursor.hide>", 183 - "│ 184 - ◆ foo 185 - │ _ 186 - └ 187 - ", 188 - "<cursor.backward count=999><cursor.up count=4>", 189 - "<cursor.down count=1>", 190 - "<erase.down>", 191 - "◇ foo 192 - │", 193 - " 194 - ", 195 - "<cursor.show>", 196 - ] 197 - `; 198 - 199 - exports[`jsx > Password > renders user input 1`] = ` 200 - [ 201 - "<cursor.hide>", 202 - "│ 203 - ◆ foo 204 - │ _ 205 - └ 206 - ", 207 - "<cursor.backward count=999><cursor.up count=4>", 208 - "<cursor.down count=2>", 209 - "<erase.line><cursor.left count=1>", 210 - "│ ▪_", 211 - "<cursor.down count=2>", 212 - "<cursor.backward count=999><cursor.up count=4>", 213 - "<cursor.down count=2>", 214 - "<erase.line><cursor.left count=1>", 215 - "│ ▪▪_", 216 - "<cursor.down count=2>", 217 - "<cursor.backward count=999><cursor.up count=4>", 218 - "<cursor.down count=1>", 219 - "<erase.down>", 220 - "◇ foo 221 - │ ▪▪", 222 - " 223 - ", 224 - "<cursor.show>", 225 - ] 226 - `; 227 - 228 - exports[`jsx > Text > can set default value 1`] = ` 229 - [ 230 - "<cursor.hide>", 231 - "│ 232 - ◆ foo 233 - │ _ 234 - └ 235 - ", 236 - "<cursor.backward count=999><cursor.up count=4>", 237 - "<cursor.down count=1>", 238 - "<erase.down>", 239 - "◇ foo 240 - │ bar", 241 - " 242 - ", 243 - "<cursor.show>", 244 - ] 245 - `; 246 - 247 - exports[`jsx > Text > can set initial value 1`] = ` 248 - [ 249 - "<cursor.hide>", 250 - "│ 251 - ◆ foo 252 - │ bar█ 253 - └ 254 - ", 255 - "<cursor.backward count=999><cursor.up count=4>", 256 - "<cursor.down count=1>", 257 - "<erase.down>", 258 - "◇ foo 259 - │ bar", 260 - " 261 - ", 262 - "<cursor.show>", 263 - ] 264 - `; 265 - 266 - exports[`jsx > Text > can set placeholder 1`] = ` 267 - [ 268 - "<cursor.hide>", 269 - "│ 270 - ◆ foo 271 - │ bar 272 - └ 273 - ", 274 - "<cursor.backward count=999><cursor.up count=4>", 275 - "<cursor.down count=1>", 276 - "<erase.down>", 277 - "◇ foo 278 - │", 279 - " 280 - ", 281 - "<cursor.show>", 282 - ] 283 - `; 284 - 285 - exports[`jsx > Text > renders text input 1`] = ` 286 - [ 287 - "<cursor.hide>", 288 - "│ 289 - ◆ foo 290 - │ _ 291 - └ 292 - ", 293 - "<cursor.backward count=999><cursor.up count=4>", 294 - "<cursor.down count=1>", 295 - "<erase.down>", 296 - "◇ foo 297 - │", 298 - " 299 - ", 300 - "<cursor.show>", 301 - ] 302 - `;
+58
packages/jsx/test/components/__snapshots__/confirm.test.tsx.snap
··· 1 + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 + 3 + exports[`Confirm > can set active text 1`] = ` 4 + [ 5 + "<cursor.hide>", 6 + "│ 7 + ◆ foo? 8 + │ ● DO IT / ○ No 9 + └ 10 + ", 11 + "<cursor.backward count=999><cursor.up count=4>", 12 + "<cursor.down count=1>", 13 + "<erase.down>", 14 + "◇ foo? 15 + │ DO IT", 16 + " 17 + ", 18 + "<cursor.show>", 19 + ] 20 + `; 21 + 22 + exports[`Confirm > can set inactive text 1`] = ` 23 + [ 24 + "<cursor.hide>", 25 + "│ 26 + ◆ foo? 27 + │ ● Yes / ○ DONT DO IT 28 + └ 29 + ", 30 + "<cursor.backward count=999><cursor.up count=4>", 31 + "<cursor.down count=1>", 32 + "<erase.down>", 33 + "◇ foo? 34 + │ Yes", 35 + " 36 + ", 37 + "<cursor.show>", 38 + ] 39 + `; 40 + 41 + exports[`Confirm > can set message 1`] = ` 42 + [ 43 + "<cursor.hide>", 44 + "│ 45 + ◆ foo? 46 + │ ● Yes / ○ No 47 + └ 48 + ", 49 + "<cursor.backward count=999><cursor.up count=4>", 50 + "<cursor.down count=1>", 51 + "<erase.down>", 52 + "◇ foo? 53 + │ Yes", 54 + " 55 + ", 56 + "<cursor.show>", 57 + ] 58 + `;
+92
packages/jsx/test/components/__snapshots__/note.test.tsx.snap
··· 1 + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 + 3 + exports[`Note > can render children as message 1`] = ` 4 + [ 5 + "│ 6 + ◇  ──────────╮ 7 + │ │ 8 + │ a message │ 9 + │ │ 10 + ├─────────────╯ 11 + ", 12 + ] 13 + `; 14 + 15 + exports[`Note > can render complex results as message 1`] = ` 16 + [ 17 + "<cursor.hide>", 18 + "│ 19 + ◆ say yes 20 + │ ● Yes / ○ No 21 + └ 22 + ", 23 + "<cursor.backward count=999><cursor.up count=4>", 24 + "<cursor.down count=1>", 25 + "<erase.down>", 26 + "◇ say yes 27 + │ Yes", 28 + " 29 + ", 30 + "<cursor.show>", 31 + "│ 32 + ◇  ─────╮ 33 + │ │ 34 + │ true │ 35 + │ │ 36 + ├────────╯ 37 + ", 38 + ] 39 + `; 40 + 41 + exports[`Note > can render multiple children as message 1`] = ` 42 + [ 43 + "<cursor.hide>", 44 + "│ 45 + ◆ say yes 46 + │ ● Yes / ○ No 47 + └ 48 + ", 49 + "<cursor.hide>", 50 + "│ 51 + ◆ say yes again 52 + │ ● Yes / ○ No 53 + └ 54 + ", 55 + "<cursor.backward count=999><cursor.up count=4>", 56 + "<cursor.down count=1>", 57 + "<erase.down>", 58 + "◇ say yes 59 + │ Yes", 60 + " 61 + ", 62 + "<cursor.show>", 63 + "<cursor.backward count=999><cursor.up count=4>", 64 + "<cursor.down count=1>", 65 + "<erase.down>", 66 + "◇ say yes again 67 + │ Yes", 68 + " 69 + ", 70 + "<cursor.show>", 71 + "│ 72 + ◇  ─────╮ 73 + │ │ 74 + │ true │ 75 + │ true │ 76 + │ │ 77 + ├────────╯ 78 + ", 79 + ] 80 + `; 81 + 82 + exports[`Note > can render string message 1`] = ` 83 + [ 84 + "│ 85 + ◇  ────╮ 86 + │ │ 87 + │ foo │ 88 + │ │ 89 + ├───────╯ 90 + ", 91 + ] 92 + `;
+78
packages/jsx/test/components/__snapshots__/password.test.tsx.snap
··· 1 + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 + 3 + exports[`Password > can set custom mask 1`] = ` 4 + [ 5 + "<cursor.hide>", 6 + "│ 7 + ◆ foo 8 + │ _ 9 + └ 10 + ", 11 + "<cursor.backward count=999><cursor.up count=4>", 12 + "<cursor.down count=2>", 13 + "<erase.line><cursor.left count=1>", 14 + "│ !_", 15 + "<cursor.down count=2>", 16 + "<cursor.backward count=999><cursor.up count=4>", 17 + "<cursor.down count=2>", 18 + "<erase.line><cursor.left count=1>", 19 + "│ !!_", 20 + "<cursor.down count=2>", 21 + "<cursor.backward count=999><cursor.up count=4>", 22 + "<cursor.down count=1>", 23 + "<erase.down>", 24 + "◇ foo 25 + │ !!", 26 + " 27 + ", 28 + "<cursor.show>", 29 + ] 30 + `; 31 + 32 + exports[`Password > renders password input 1`] = ` 33 + [ 34 + "<cursor.hide>", 35 + "│ 36 + ◆ foo 37 + │ _ 38 + └ 39 + ", 40 + "<cursor.backward count=999><cursor.up count=4>", 41 + "<cursor.down count=1>", 42 + "<erase.down>", 43 + "◇ foo 44 + │", 45 + " 46 + ", 47 + "<cursor.show>", 48 + ] 49 + `; 50 + 51 + exports[`Password > renders user input 1`] = ` 52 + [ 53 + "<cursor.hide>", 54 + "│ 55 + ◆ foo 56 + │ _ 57 + └ 58 + ", 59 + "<cursor.backward count=999><cursor.up count=4>", 60 + "<cursor.down count=2>", 61 + "<erase.line><cursor.left count=1>", 62 + "│ ▪_", 63 + "<cursor.down count=2>", 64 + "<cursor.backward count=999><cursor.up count=4>", 65 + "<cursor.down count=2>", 66 + "<erase.line><cursor.left count=1>", 67 + "│ ▪▪_", 68 + "<cursor.down count=2>", 69 + "<cursor.backward count=999><cursor.up count=4>", 70 + "<cursor.down count=1>", 71 + "<erase.down>", 72 + "◇ foo 73 + │ ▪▪", 74 + " 75 + ", 76 + "<cursor.show>", 77 + ] 78 + `;
+61
packages/jsx/test/components/__snapshots__/select.test.tsx.snap
··· 1 + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 + 3 + exports[`Select > renders options 1`] = ` 4 + [ 5 + "<cursor.hide>", 6 + "│ 7 + ◆ foo 8 + │ ● opt0 9 + │ ○ opt1 10 + └ 11 + ", 12 + "<cursor.backward count=999><cursor.up count=5>", 13 + "<cursor.down count=1>", 14 + "<erase.down>", 15 + "◇ foo 16 + │ opt0", 17 + " 18 + ", 19 + "<cursor.show>", 20 + ] 21 + `; 22 + 23 + exports[`Select > renders options with hints 1`] = ` 24 + [ 25 + "<cursor.hide>", 26 + "│ 27 + ◆ foo 28 + │ ● Three o three (hint one) 29 + │ ○ Eight o eight 30 + └ 31 + ", 32 + "<cursor.backward count=999><cursor.up count=5>", 33 + "<cursor.down count=1>", 34 + "<erase.down>", 35 + "◇ foo 36 + │ Three o three", 37 + " 38 + ", 39 + "<cursor.show>", 40 + ] 41 + `; 42 + 43 + exports[`Select > renders options with labels 1`] = ` 44 + [ 45 + "<cursor.hide>", 46 + "│ 47 + ◆ foo 48 + │ ● Three o three 49 + │ ○ Eight o eight 50 + └ 51 + ", 52 + "<cursor.backward count=999><cursor.up count=5>", 53 + "<cursor.down count=1>", 54 + "<erase.down>", 55 + "◇ foo 56 + │ Three o three", 57 + " 58 + ", 59 + "<cursor.show>", 60 + ] 61 + `;
+77
packages/jsx/test/components/__snapshots__/text.test.tsx.snap
··· 1 + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 + 3 + exports[`Text > can set default value 1`] = ` 4 + [ 5 + "<cursor.hide>", 6 + "│ 7 + ◆ foo 8 + │ _ 9 + └ 10 + ", 11 + "<cursor.backward count=999><cursor.up count=4>", 12 + "<cursor.down count=1>", 13 + "<erase.down>", 14 + "◇ foo 15 + │ bar", 16 + " 17 + ", 18 + "<cursor.show>", 19 + ] 20 + `; 21 + 22 + exports[`Text > can set initial value 1`] = ` 23 + [ 24 + "<cursor.hide>", 25 + "│ 26 + ◆ foo 27 + │ bar█ 28 + └ 29 + ", 30 + "<cursor.backward count=999><cursor.up count=4>", 31 + "<cursor.down count=1>", 32 + "<erase.down>", 33 + "◇ foo 34 + │ bar", 35 + " 36 + ", 37 + "<cursor.show>", 38 + ] 39 + `; 40 + 41 + exports[`Text > can set placeholder 1`] = ` 42 + [ 43 + "<cursor.hide>", 44 + "│ 45 + ◆ foo 46 + │ bar 47 + └ 48 + ", 49 + "<cursor.backward count=999><cursor.up count=4>", 50 + "<cursor.down count=1>", 51 + "<erase.down>", 52 + "◇ foo 53 + │", 54 + " 55 + ", 56 + "<cursor.show>", 57 + ] 58 + `; 59 + 60 + exports[`Text > renders text input 1`] = ` 61 + [ 62 + "<cursor.hide>", 63 + "│ 64 + ◆ foo 65 + │ _ 66 + └ 67 + ", 68 + "<cursor.backward count=999><cursor.up count=4>", 69 + "<cursor.down count=1>", 70 + "<erase.down>", 71 + "◇ foo 72 + │", 73 + " 74 + ", 75 + "<cursor.show>", 76 + ] 77 + `;
+37
packages/jsx/test/components/confirm.test.tsx
··· 1 + import { MockReadable, MockWritable } from '@clack/test-utils'; 2 + import { beforeEach, describe, expect, test } from 'vitest'; 3 + import { Confirm } from '../../src/index.js'; 4 + 5 + describe('Confirm', () => { 6 + let input: MockReadable; 7 + let output: MockWritable; 8 + 9 + beforeEach(() => { 10 + input = new MockReadable(); 11 + output = new MockWritable(); 12 + }); 13 + 14 + test('can set message', async () => { 15 + const task = <Confirm message="foo?" input={input} output={output} />; 16 + input.emit('keypress', '', { name: 'return' }); 17 + const result = await task; 18 + expect(result).to.equal(true); 19 + expect(output.buffer).toMatchSnapshot(); 20 + }); 21 + 22 + test('can set active text', async () => { 23 + const task = <Confirm message="foo?" active="DO IT" input={input} output={output} />; 24 + input.emit('keypress', '', { name: 'return' }); 25 + const result = await task; 26 + expect(result).to.equal(true); 27 + expect(output.buffer).toMatchSnapshot(); 28 + }); 29 + 30 + test('can set inactive text', async () => { 31 + const task = <Confirm message="foo?" inactive="DONT DO IT" input={input} output={output} />; 32 + input.emit('keypress', '', { name: 'return' }); 33 + const result = await task; 34 + expect(result).to.equal(true); 35 + expect(output.buffer).toMatchSnapshot(); 36 + }); 37 + });
+53
packages/jsx/test/components/note.test.tsx
··· 1 + import { MockReadable, MockWritable } from '@clack/test-utils'; 2 + import { beforeEach, describe, expect, test } from 'vitest'; 3 + import { Confirm, Note } from '../../src/index.js'; 4 + 5 + describe('Note', () => { 6 + let input: MockReadable; 7 + let output: MockWritable; 8 + 9 + beforeEach(() => { 10 + input = new MockReadable(); 11 + output = new MockWritable(); 12 + }); 13 + 14 + test('can render string message', async () => { 15 + const task = <Note message="foo" output={output} />; 16 + await task; 17 + 18 + expect(output.buffer).toMatchSnapshot(); 19 + }); 20 + 21 + test('can render children as message', async () => { 22 + const task = <Note output={output}>a message</Note>; 23 + await task; 24 + 25 + expect(output.buffer).toMatchSnapshot(); 26 + }); 27 + 28 + test('can render complex results as message', async () => { 29 + const task = ( 30 + <Note output={output}> 31 + <Confirm message="say yes" input={input} output={output} /> 32 + </Note> 33 + ); 34 + input.emit('keypress', '', { name: 'return' }); 35 + await task; 36 + 37 + expect(output.buffer).toMatchSnapshot(); 38 + }); 39 + 40 + test('can render multiple children as message', async () => { 41 + const task = ( 42 + <Note output={output}> 43 + <Confirm message="say yes" input={input} output={output} /> 44 + <Confirm message="say yes again" input={input} output={output} /> 45 + </Note> 46 + ); 47 + input.emit('keypress', '', { name: 'return' }); 48 + input.emit('keypress', '', { name: 'return' }); 49 + await task; 50 + 51 + expect(output.buffer).toMatchSnapshot(); 52 + }); 53 + });
+50
packages/jsx/test/components/password.test.tsx
··· 1 + import { MockReadable, MockWritable } from '@clack/test-utils'; 2 + import { beforeEach, describe, expect, test } from 'vitest'; 3 + import { Password } from '../../src/index.js'; 4 + 5 + describe('Password', () => { 6 + let input: MockReadable; 7 + let output: MockWritable; 8 + 9 + beforeEach(() => { 10 + input = new MockReadable(); 11 + output = new MockWritable(); 12 + }); 13 + 14 + test('renders password input', async () => { 15 + const task = <Password message="foo" output={output} input={input} />; 16 + 17 + input.emit('keypress', '', { name: 'return' }); 18 + 19 + const result = await task; 20 + 21 + expect(result).to.equal(undefined); 22 + expect(output.buffer).toMatchSnapshot(); 23 + }); 24 + 25 + test('renders user input', async () => { 26 + const task = <Password message="foo" output={output} input={input} />; 27 + 28 + input.emit('keypress', 'a', { name: 'a' }); 29 + input.emit('keypress', 'b', { name: 'b' }); 30 + input.emit('keypress', '', { name: 'return' }); 31 + 32 + const result = await task; 33 + 34 + expect(result).to.equal('ab'); 35 + expect(output.buffer).toMatchSnapshot(); 36 + }); 37 + 38 + test('can set custom mask', async () => { 39 + const task = <Password message="foo" mask="!" output={output} input={input} />; 40 + 41 + input.emit('keypress', 'a', { name: 'a' }); 42 + input.emit('keypress', 'b', { name: 'b' }); 43 + input.emit('keypress', '', { name: 'return' }); 44 + 45 + const result = await task; 46 + 47 + expect(result).to.equal('ab'); 48 + expect(output.buffer).toMatchSnapshot(); 49 + }); 50 + });
+74
packages/jsx/test/components/select.test.tsx
··· 1 + import { MockReadable, MockWritable } from '@clack/test-utils'; 2 + import { beforeEach, describe, expect, test } from 'vitest'; 3 + import { Option, Select } from '../../src/index.js'; 4 + 5 + describe('Select', () => { 6 + let input: MockReadable; 7 + let output: MockWritable; 8 + 9 + beforeEach(() => { 10 + input = new MockReadable(); 11 + output = new MockWritable(); 12 + }); 13 + 14 + test('renders options', async () => { 15 + const task = ( 16 + <Select message="foo" input={input} output={output}> 17 + <Option value={'opt0'} /> 18 + <Option value={'opt1'} /> 19 + </Select> 20 + ); 21 + 22 + // wait a tick... sad times 23 + await new Promise((res) => setTimeout(res, 0)); 24 + 25 + input.emit('keypress', '', { name: 'return' }); 26 + 27 + const result = await task; 28 + 29 + expect(result).to.equal('opt0'); 30 + expect(output.buffer).toMatchSnapshot(); 31 + }); 32 + 33 + test('renders options with labels', async () => { 34 + const task = ( 35 + <Select message="foo" input={input} output={output}> 36 + <Option value={303}>Three o three</Option> 37 + <Option value={808}>Eight o eight</Option> 38 + </Select> 39 + ); 40 + 41 + // wait a tick... sad times 42 + await new Promise((res) => setTimeout(res, 0)); 43 + 44 + input.emit('keypress', '', { name: 'return' }); 45 + 46 + const result = await task; 47 + 48 + expect(result).to.equal(303); 49 + expect(output.buffer).toMatchSnapshot(); 50 + }); 51 + 52 + test('renders options with hints', async () => { 53 + const task = ( 54 + <Select message="foo" input={input} output={output}> 55 + <Option value={303} hint="hint one"> 56 + Three o three 57 + </Option> 58 + <Option value={808} hint="hint two"> 59 + Eight o eight 60 + </Option> 61 + </Select> 62 + ); 63 + 64 + // wait a tick... sad times 65 + await new Promise((res) => setTimeout(res, 0)); 66 + 67 + input.emit('keypress', '', { name: 'return' }); 68 + 69 + const result = await task; 70 + 71 + expect(result).to.equal(303); 72 + expect(output.buffer).toMatchSnapshot(); 73 + }); 74 + });
+57
packages/jsx/test/components/text.test.tsx
··· 1 + import { MockReadable, MockWritable } from '@clack/test-utils'; 2 + import { beforeEach, describe, expect, test } from 'vitest'; 3 + import { Text } from '../../src/index.js'; 4 + 5 + describe('Text', () => { 6 + let input: MockReadable; 7 + let output: MockWritable; 8 + 9 + beforeEach(() => { 10 + input = new MockReadable(); 11 + output = new MockWritable(); 12 + }); 13 + 14 + test('renders text input', async () => { 15 + const task = <Text message="foo" output={output} input={input} />; 16 + 17 + input.emit('keypress', '', { name: 'return' }); 18 + 19 + const result = await task; 20 + 21 + expect(result).to.equal(''); 22 + expect(output.buffer).toMatchSnapshot(); 23 + }); 24 + 25 + test('can set placeholder', async () => { 26 + const task = <Text message="foo" placeholder="bar" output={output} input={input} />; 27 + 28 + input.emit('keypress', '', { name: 'return' }); 29 + 30 + const result = await task; 31 + 32 + expect(result).to.equal(''); 33 + expect(output.buffer).toMatchSnapshot(); 34 + }); 35 + 36 + test('can set default value', async () => { 37 + const task = <Text message="foo" defaultValue="bar" output={output} input={input} />; 38 + 39 + input.emit('keypress', '', { name: 'return' }); 40 + 41 + const result = await task; 42 + 43 + expect(result).to.equal('bar'); 44 + expect(output.buffer).toMatchSnapshot(); 45 + }); 46 + 47 + test('can set initial value', async () => { 48 + const task = <Text message="foo" initialValue="bar" output={output} input={input} />; 49 + 50 + input.emit('keypress', '', { name: 'return' }); 51 + 52 + const result = await task; 53 + 54 + expect(result).to.equal('bar'); 55 + expect(output.buffer).toMatchSnapshot(); 56 + }); 57 + });
+1 -172
packages/jsx/test/jsx.test.tsx
··· 1 1 import { MockReadable, MockWritable } from '@clack/test-utils'; 2 2 import { beforeEach, describe, expect, test } from 'vitest'; 3 - import { Confirm, Note, Option, Password, Select, Text, jsx } from '../src/index.js'; 3 + import { Confirm, jsx } from '../src/index.js'; 4 4 5 5 describe('jsx', () => { 6 6 let input: MockReadable; ··· 33 33 const task = jsx('unknown-nonsense' as never, {} as never); 34 34 const result = await task; 35 35 expect(result).to.equal(null); 36 - }); 37 - 38 - describe('Confirm', () => { 39 - test('can set message', async () => { 40 - const task = <Confirm message="foo?" input={input} output={output} />; 41 - input.emit('keypress', '', { name: 'return' }); 42 - const result = await task; 43 - expect(result).to.equal(true); 44 - expect(output.buffer).toMatchSnapshot(); 45 - }); 46 - 47 - test('can set active text', async () => { 48 - const task = <Confirm message="foo?" active="DO IT" input={input} output={output} />; 49 - input.emit('keypress', '', { name: 'return' }); 50 - const result = await task; 51 - expect(result).to.equal(true); 52 - expect(output.buffer).toMatchSnapshot(); 53 - }); 54 - 55 - test('can set inactive text', async () => { 56 - const task = <Confirm message="foo?" inactive="DONT DO IT" input={input} output={output} />; 57 - input.emit('keypress', '', { name: 'return' }); 58 - const result = await task; 59 - expect(result).to.equal(true); 60 - expect(output.buffer).toMatchSnapshot(); 61 - }); 62 - }); 63 - 64 - describe('Note', () => { 65 - test('can render string message', async () => { 66 - const task = <Note message="foo" output={output} />; 67 - await task; 68 - 69 - expect(output.buffer).toMatchSnapshot(); 70 - }); 71 - 72 - test('can render children as message', async () => { 73 - const task = <Note output={output}>a message</Note>; 74 - await task; 75 - 76 - expect(output.buffer).toMatchSnapshot(); 77 - }); 78 - 79 - test('can render complex results as message', async () => { 80 - const task = ( 81 - <Note output={output}> 82 - <Confirm message="say yes" input={input} output={output} /> 83 - </Note> 84 - ); 85 - input.emit('keypress', '', { name: 'return' }); 86 - await task; 87 - 88 - expect(output.buffer).toMatchSnapshot(); 89 - }); 90 - 91 - test('can render multiple children as message', async () => { 92 - const task = ( 93 - <Note output={output}> 94 - <Confirm message="say yes" input={input} output={output} /> 95 - <Confirm message="say yes again" input={input} output={output} /> 96 - </Note> 97 - ); 98 - input.emit('keypress', '', { name: 'return' }); 99 - input.emit('keypress', '', { name: 'return' }); 100 - await task; 101 - 102 - expect(output.buffer).toMatchSnapshot(); 103 - }); 104 - }); 105 - 106 - describe('Text', () => { 107 - test('renders text input', async () => { 108 - const task = <Text message="foo" output={output} input={input} />; 109 - 110 - input.emit('keypress', '', { name: 'return' }); 111 - 112 - const result = await task; 113 - 114 - expect(result).to.equal(''); 115 - expect(output.buffer).toMatchSnapshot(); 116 - }); 117 - 118 - test('can set placeholder', async () => { 119 - const task = <Text message="foo" placeholder="bar" output={output} input={input} />; 120 - 121 - input.emit('keypress', '', { name: 'return' }); 122 - 123 - const result = await task; 124 - 125 - expect(result).to.equal(''); 126 - expect(output.buffer).toMatchSnapshot(); 127 - }); 128 - 129 - test('can set default value', async () => { 130 - const task = <Text message="foo" defaultValue="bar" output={output} input={input} />; 131 - 132 - input.emit('keypress', '', { name: 'return' }); 133 - 134 - const result = await task; 135 - 136 - expect(result).to.equal('bar'); 137 - expect(output.buffer).toMatchSnapshot(); 138 - }); 139 - 140 - test('can set initial value', async () => { 141 - const task = <Text message="foo" initialValue="bar" output={output} input={input} />; 142 - 143 - input.emit('keypress', '', { name: 'return' }); 144 - 145 - const result = await task; 146 - 147 - expect(result).to.equal('bar'); 148 - expect(output.buffer).toMatchSnapshot(); 149 - }); 150 - }); 151 - 152 - describe('Password', () => { 153 - test('renders password input', async () => { 154 - const task = <Password message="foo" output={output} input={input} />; 155 - 156 - input.emit('keypress', '', { name: 'return' }); 157 - 158 - const result = await task; 159 - 160 - expect(result).to.equal(undefined); 161 - expect(output.buffer).toMatchSnapshot(); 162 - }); 163 - 164 - test('renders user input', async () => { 165 - const task = <Password message="foo" output={output} input={input} />; 166 - 167 - input.emit('keypress', 'a', { name: 'a' }); 168 - input.emit('keypress', 'b', { name: 'b' }); 169 - input.emit('keypress', '', { name: 'return' }); 170 - 171 - const result = await task; 172 - 173 - expect(result).to.equal('ab'); 174 - expect(output.buffer).toMatchSnapshot(); 175 - }); 176 - 177 - test('can set custom mask', async () => { 178 - const task = <Password message="foo" mask="!" output={output} input={input} />; 179 - 180 - input.emit('keypress', 'a', { name: 'a' }); 181 - input.emit('keypress', 'b', { name: 'b' }); 182 - input.emit('keypress', '', { name: 'return' }); 183 - 184 - const result = await task; 185 - 186 - expect(result).to.equal('ab'); 187 - expect(output.buffer).toMatchSnapshot(); 188 - }); 189 - }); 190 - 191 - describe('Select', () => { 192 - test('renders options', async () => { 193 - const task = ( 194 - <Select message="pick one" input={input} output={output}> 195 - <Option value={303}>Option One</Option> 196 - <Option value={808}>Option Two</Option> 197 - </Select> 198 - ); 199 - 200 - input.emit('keypress', '', { name: 'return' }); 201 - 202 - const result = await task; 203 - 204 - expect(result).to.equal(303); 205 - expect(output.buffer).toMatchSnapshot(); 206 - }); 207 36 }); 208 37 });