Skip to content

Commit

Permalink
Merge pull request #23 from sasjs/handle-cancel
Browse files Browse the repository at this point in the history
fix(handle-cancel): exit gracefully when user cancels input
  • Loading branch information
krishna-acondy authored Feb 7, 2021
2 parents ec9246f + 818b4da commit a4a8cd2
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 57 deletions.
4 changes: 1 addition & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"homepage": "https://github.com/sasjs/utils#readme",
"devDependencies": {
"@types/jest": "^26.0.15",
"@types/prompts": "^2.0.9",
"@types/valid-url": "^1.0.3",
"jest": "^26.6.3",
"rimraf": "^3.0.2",
Expand All @@ -42,6 +41,7 @@
"dependencies": {
"consola": "^2.15.0",
"prompts": "^2.4.0",
"@types/prompts": "^2.0.9",
"valid-url": "^1.0.9"
}
}
104 changes: 61 additions & 43 deletions src/input/readAndValidateInput.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getString,
getUrl,
InputType,
onCancel,
readAndValidateInput
} from './readAndValidateInput'
import prompts from 'prompts'
Expand All @@ -28,14 +29,16 @@ describe('getString', () => {
await getString(message, validator)

expect(prompts.prompt).toHaveBeenCalledTimes(1)
expect(prompts.prompt).toHaveBeenCalledWith({
type: 'text',
name: 'text',
initial: '',
message,
validate: validator,
choices: []
})
expect(prompts.prompt).toHaveBeenCalledWith(
{
type: 'text',
name: 'text',
initial: '',
message,
validate: validator
},
{ onCancel }
)
done()
})

Expand Down Expand Up @@ -82,14 +85,16 @@ describe('getNumber', () => {
await getNumber(message, validator, defaultValue)

expect(prompts.prompt).toHaveBeenCalledTimes(1)
expect(prompts.prompt).toHaveBeenCalledWith({
type: 'number',
name: 'number',
initial: defaultValue,
message,
validate: validator,
choices: []
})
expect(prompts.prompt).toHaveBeenCalledWith(
{
type: 'number',
name: 'number',
initial: defaultValue,
message,
validate: validator
},
{ onCancel }
)
done()
})

Expand Down Expand Up @@ -151,14 +156,16 @@ describe('getUrl', () => {
await getUrl(message, errorMessage)

expect(prompts.prompt).toHaveBeenCalledTimes(1)
expect(prompts.prompt).toHaveBeenCalledWith({
type: 'text',
name: 'url',
initial: '',
message,
validate: expect.anything(),
choices: []
})
expect(prompts.prompt).toHaveBeenCalledWith(
{
type: 'text',
name: 'url',
initial: '',
message,
validate: expect.anything()
},
{ onCancel }
)
done()
})

Expand Down Expand Up @@ -203,14 +210,16 @@ describe('getConfirmation', () => {
await getConfirmation(message, false)

expect(prompts.prompt).toHaveBeenCalledTimes(1)
expect(prompts.prompt).toHaveBeenCalledWith({
type: 'confirm',
name: 'confirm',
initial: false,
message,
validate: confirmationValidator,
choices: []
})
expect(prompts.prompt).toHaveBeenCalledWith(
{
type: 'confirm',
name: 'confirm',
initial: false,
message,
validate: confirmationValidator
},
{ onCancel }
)
done()
})

Expand All @@ -235,29 +244,38 @@ describe('getChoice', () => {
it('should call prompts to get a selection', async (done) => {
const message = 'Choose: '
const errorMessage = 'Invalid choice.'
const choices = [{ title: 'Option 1' }, { title: 'Option 2' }]
const choices = [
{ title: 'Option 1', value: 1 },
{ title: 'Option 2', value: 2 }
]
jest
.spyOn(prompts, 'prompt')
.mockImplementation(() => Promise.resolve({ choice: 1 }))

await getChoice(message, errorMessage, choices)

expect(prompts.prompt).toHaveBeenCalledTimes(1)
expect(prompts.prompt).toHaveBeenCalledWith({
type: 'select',
name: 'choice',
initial: 0,
message,
validate: expect.anything(),
choices
})
expect(prompts.prompt).toHaveBeenCalledWith(
{
type: 'select',
name: 'choice',
initial: 0,
message,
validate: expect.anything(),
choices
},
{ onCancel }
)
done()
})

it('should throw an error with a non-number choice', async (done) => {
const message = 'Choose: '
const errorMessage = 'Invalid choice.'
const choices = [{ title: 'Option 1' }, { title: 'Option 2' }]
const choices = [
{ title: 'Option 1', value: 1 },
{ title: 'Option 2', value: 2 }
]
jest
.spyOn(prompts, 'prompt')
.mockImplementation(() => Promise.resolve({ choice: 'abc' }))
Expand Down Expand Up @@ -304,7 +322,7 @@ describe('readAndValidateInput', () => {

await expect(
readAndValidateInput('number', 'choice', 'Choose', (v) => !!v, 1, [
{ title: 'Test' }
{ title: 'Test', value: 1 }
])
).rejects.toThrowError(
'Choices are only supported with the `select` input type.'
Expand Down
45 changes: 35 additions & 10 deletions src/input/readAndValidateInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type InputValidator = (
export interface Choice {
title: string
description?: string
value?: string | number
value: string | number
}

const defaultErrorMessage = 'Invalid input.'
Expand All @@ -24,6 +24,11 @@ const isValidInputType = (type: any): boolean =>
type === 'confirm' ||
type === 'url'

export const onCancel = () => {
console.error('Input cancelled. Exiting...')
process.exit(1)
}

export const getNumber = async (
message: string,
validator: InputValidator,
Expand Down Expand Up @@ -108,7 +113,7 @@ export const getChoice = async (
choice !== null &&
choice !== undefined
) {
return choice + 1
return choice
}

throw new Error(errorMessage)
Expand Down Expand Up @@ -157,12 +162,32 @@ export const readAndValidateInput = async (
)
}

return await prompts.prompt({
type,
name: fieldName,
initial: defaultValue,
message,
choices,
validate: validator
})
if (type === 'select') {
return await prompts(
{
type,
name: fieldName,
initial: defaultValue,
message,
choices,
validate: validator
},
{
onCancel
}
)
}

return await prompts(
{
type,
name: fieldName,
initial: defaultValue,
message,
validate: validator
},
{
onCancel
}
)
}

0 comments on commit a4a8cd2

Please sign in to comment.