From 2667bb4c454be14c4be00a3bc610b679818dd54d Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Mon, 12 May 2025 23:37:11 -0600 Subject: [PATCH 1/2] fix: validate initial values --- examples/basic/text-validation.ts | 41 +++++++++++++ packages/core/src/prompts/prompt.ts | 9 +++ packages/core/test/prompts/prompt.test.ts | 70 +++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 examples/basic/text-validation.ts 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(''); + }); }); From 5bbdb569903eb578a9ecf4b311e4a841573acf56 Mon Sep 17 00:00:00 2001 From: Paul Valladares <85648028+dreyfus92@users.noreply.github.com> Date: Mon, 12 May 2025 23:43:28 -0600 Subject: [PATCH 2/2] add: changeset --- .changeset/mean-mice-train.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mean-mice-train.md 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.