diff --git a/.changeset/mean-mice-train.md b/.changeset/mean-mice-train.md new file mode 100644 index 00000000..ca71562d --- /dev/null +++ b/.changeset/mean-mice-train.md @@ -0,0 +1,5 @@ +--- +"@clack/core": patch +--- + +Validates initial values immediately when using text prompts with initialValue and validate props. diff --git a/examples/basic/text-validation.ts b/examples/basic/text-validation.ts new file mode 100644 index 00000000..cc13199b --- /dev/null +++ b/examples/basic/text-validation.ts @@ -0,0 +1,41 @@ +import { text, note, isCancel } from '@clack/prompts'; +import { setTimeout } from 'node:timers/promises'; + +async function main() { + console.clear(); + + // Example demonstrating the issue with initial value validation + const name = await text({ + message: 'Enter your name (letters and spaces only)', + initialValue: 'John123', // Invalid initial value with numbers + validate: (value) => { + if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces'; + return undefined; + }, + }); + + if (!isCancel(name)) { + note(`Valid name: ${name}`, 'Success'); + } + + await setTimeout(1000); + + // Example with a valid initial value for comparison + const validName = await text({ + message: 'Enter another name (letters and spaces only)', + initialValue: 'John Doe', // Valid initial value + validate: (value) => { + if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name can only contain letters and spaces'; + return undefined; + }, + }); + + if (!isCancel(validName)) { + note(`Valid name: ${validName}`, 'Success'); + } + + await setTimeout(1000); + +} + +main().catch(console.error); \ No newline at end of file diff --git a/packages/core/src/prompts/prompt.ts b/packages/core/src/prompts/prompt.ts index 7e58b866..11ca3d25 100644 --- a/packages/core/src/prompts/prompt.ts +++ b/packages/core/src/prompts/prompt.ts @@ -147,6 +147,15 @@ export default class Prompt { this.rl.write(this.opts.initialValue); } this._setValue(this.opts.initialValue); + + // Validate initial value if validator exists + if (this.opts.validate) { + const problem = this.opts.validate(this.opts.initialValue); + if (problem) { + this.error = problem instanceof Error ? problem.message : problem; + this.state = 'error'; + } + } } this.input.on('keypress', this.onKeypress); diff --git a/packages/core/test/prompts/prompt.test.ts b/packages/core/test/prompts/prompt.test.ts index 5c2a0488..5be8bb99 100644 --- a/packages/core/test/prompts/prompt.test.ts +++ b/packages/core/test/prompts/prompt.test.ts @@ -263,4 +263,74 @@ describe('Prompt', () => { expect(instance.state).to.equal('cancel'); }); + + test('validates initial value on prompt start', () => { + const instance = new Prompt({ + input, + output, + render: () => 'foo', + initialValue: 'invalid', + validate: (value) => value === 'valid' ? undefined : 'must be valid', + }); + instance.prompt(); + + expect(instance.state).to.equal('error'); + expect(instance.error).to.equal('must be valid'); + }); + + test('accepts valid initial value', () => { + const instance = new Prompt({ + input, + output, + render: () => 'foo', + initialValue: 'valid', + validate: (value) => value === 'valid' ? undefined : 'must be valid', + }); + instance.prompt(); + + expect(instance.state).to.equal('active'); + expect(instance.error).to.equal(''); + }); + + test('validates initial value with Error object', () => { + const instance = new Prompt({ + input, + output, + render: () => 'foo', + initialValue: 'invalid', + validate: (value) => value === 'valid' ? undefined : new Error('must be valid'), + }); + instance.prompt(); + + expect(instance.state).to.equal('error'); + expect(instance.error).to.equal('must be valid'); + }); + + test('validates initial value with regex validation', () => { + const instance = new Prompt({ + input, + output, + render: () => 'foo', + initialValue: 'Invalid Value $$$', + validate: (value) => /^[A-Z]+$/.test(value) ? undefined : 'Invalid value', + }); + instance.prompt(); + + expect(instance.state).to.equal('error'); + expect(instance.error).to.equal('Invalid value'); + }); + + test('accepts valid initial value with regex validation', () => { + const instance = new Prompt({ + input, + output, + render: () => 'foo', + initialValue: 'VALID', + validate: (value) => /^[A-Z]+$/.test(value) ? undefined : 'Invalid value', + }); + instance.prompt(); + + expect(instance.state).to.equal('active'); + expect(instance.error).to.equal(''); + }); });