Skip to content

Commit 34f52fe

Browse files
authored
fix: validate initial values (#316)
1 parent 78be1ea commit 34f52fe

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

.changeset/mean-mice-train.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clack/core": patch
3+
---
4+
5+
Validates initial values immediately when using text prompts with initialValue and validate props.

examples/basic/text-validation.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { text, note, isCancel } from '@clack/prompts';
2+
import { setTimeout } from 'node:timers/promises';
3+
4+
async function main() {
5+
console.clear();
6+
7+
// Example demonstrating the issue with initial value validation
8+
const name = await text({
9+
message: 'Enter your name (letters and spaces only)',
10+
initialValue: 'John123', // Invalid initial value with numbers
11+
validate: (value) => {
12+
if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces';
13+
return undefined;
14+
},
15+
});
16+
17+
if (!isCancel(name)) {
18+
note(`Valid name: ${name}`, 'Success');
19+
}
20+
21+
await setTimeout(1000);
22+
23+
// Example with a valid initial value for comparison
24+
const validName = await text({
25+
message: 'Enter another name (letters and spaces only)',
26+
initialValue: 'John Doe', // Valid initial value
27+
validate: (value) => {
28+
if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces';
29+
return undefined;
30+
},
31+
});
32+
33+
if (!isCancel(validName)) {
34+
note(`Valid name: ${validName}`, 'Success');
35+
}
36+
37+
await setTimeout(1000);
38+
39+
}
40+
41+
main().catch(console.error);

packages/core/src/prompts/prompt.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ export default class Prompt {
147147
this.rl.write(this.opts.initialValue);
148148
}
149149
this._setValue(this.opts.initialValue);
150+
151+
// Validate initial value if validator exists
152+
if (this.opts.validate) {
153+
const problem = this.opts.validate(this.opts.initialValue);
154+
if (problem) {
155+
this.error = problem instanceof Error ? problem.message : problem;
156+
this.state = 'error';
157+
}
158+
}
150159
}
151160

152161
this.input.on('keypress', this.onKeypress);

packages/core/test/prompts/prompt.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,74 @@ describe('Prompt', () => {
263263

264264
expect(instance.state).to.equal('cancel');
265265
});
266+
267+
test('validates initial value on prompt start', () => {
268+
const instance = new Prompt({
269+
input,
270+
output,
271+
render: () => 'foo',
272+
initialValue: 'invalid',
273+
validate: (value) => value === 'valid' ? undefined : 'must be valid',
274+
});
275+
instance.prompt();
276+
277+
expect(instance.state).to.equal('error');
278+
expect(instance.error).to.equal('must be valid');
279+
});
280+
281+
test('accepts valid initial value', () => {
282+
const instance = new Prompt({
283+
input,
284+
output,
285+
render: () => 'foo',
286+
initialValue: 'valid',
287+
validate: (value) => value === 'valid' ? undefined : 'must be valid',
288+
});
289+
instance.prompt();
290+
291+
expect(instance.state).to.equal('active');
292+
expect(instance.error).to.equal('');
293+
});
294+
295+
test('validates initial value with Error object', () => {
296+
const instance = new Prompt({
297+
input,
298+
output,
299+
render: () => 'foo',
300+
initialValue: 'invalid',
301+
validate: (value) => value === 'valid' ? undefined : new Error('must be valid'),
302+
});
303+
instance.prompt();
304+
305+
expect(instance.state).to.equal('error');
306+
expect(instance.error).to.equal('must be valid');
307+
});
308+
309+
test('validates initial value with regex validation', () => {
310+
const instance = new Prompt({
311+
input,
312+
output,
313+
render: () => 'foo',
314+
initialValue: 'Invalid Value $$$',
315+
validate: (value) => /^[A-Z]+$/.test(value) ? undefined : 'Invalid value',
316+
});
317+
instance.prompt();
318+
319+
expect(instance.state).to.equal('error');
320+
expect(instance.error).to.equal('Invalid value');
321+
});
322+
323+
test('accepts valid initial value with regex validation', () => {
324+
const instance = new Prompt({
325+
input,
326+
output,
327+
render: () => 'foo',
328+
initialValue: 'VALID',
329+
validate: (value) => /^[A-Z]+$/.test(value) ? undefined : 'Invalid value',
330+
});
331+
instance.prompt();
332+
333+
expect(instance.state).to.equal('active');
334+
expect(instance.error).to.equal('');
335+
});
266336
});

0 commit comments

Comments
 (0)