diff --git a/packages/prompts/src/__snapshots__/index.test.ts.snap b/packages/prompts/src/__snapshots__/index.test.ts.snap index e692dba5..2702508c 100644 --- a/packages/prompts/src/__snapshots__/index.test.ts.snap +++ b/packages/prompts/src/__snapshots__/index.test.ts.snap @@ -149,343 +149,440 @@ exports[`prompts (isCI = false) > confirm > right arrow moves to next choice 1`] ] `; -exports[`prompts (isCI = false) > multiselect > can cancel 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > can deselect an option 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ +", + "", + "", + "", + "│ │ ◼ group1value0", + "", + "", + "", + "", + "│ │ ◼ group1value0 +│ └ ◻ group1value1 +└ +", + "", + "", + "", + "│ ◼ group1 +│ │ ◼ group1value0 +│ └ ◼ group1value1 +└ +", + "", + "", + "", + "│ ◻ group1 +│ │ ◼ group1value0 +│ └ ◻ group1value1 +└ +", + "", "", "", - "■ foo -│", + "◇ foo +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > can render option hints 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > can select a group 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 (Hint 0) -│ ◻ opt1 +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", "", - "", - "│ ◼ opt0 (Hint 0)", - "", - "", + "", + "│ ◼ group1 +│ │ ◼ group1value0 +│ └ ◼ group1value1 +└ +", + "", "", "", "◇ foo -│ opt0", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > can set cursorAt to preselect an option 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > can select a group by selecting all members 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ +", + "", "", "", - "│ ◼ opt1", + "│ │ ◼ group1value0", + "", + "", + "", + "", + "│ │ ◼ group1value0 +│ └ ◻ group1value1 +└ +", + "", "", - "", + "", + "│ ◼ group1 +│ │ ◼ group1value0 +│ └ ◼ group1value1 +└ +", + "", "", "", "◇ foo -│ opt1", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > can set custom labels 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > can select multiple options 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ Option 0 -│ ◻ Option 1 +│ ◻ group1 +│ │ ◻ group1value0 +│ │ ◻ group1value1 +│ └ ◻ group1value2 └ ", - "", + "", "", + "", + "│ ◻ group1 +│ │ ◻ group1value0 +│ │ ◻ group1value1 +│ └ ◻ group1value2 +└ +", + "", + "", "", - "│ ◼ Option 0", + "│ │ ◼ group1value0", + "", + "", "", - "", + "", + "│ │ ◼ group1value0 +│ │ ◻ group1value1 +│ └ ◻ group1value2 +└ +", + "", + "", + "", + "│ │ ◼ group1value1", + "", + "", "", "", "◇ foo -│ Option 0", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > can set initial values 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > can submit empty selection when require = false 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◼ opt1 +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", "", "", "◇ foo -│ opt1", +│", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > can submit without selection when required = false 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > cursorAt sets initial selection 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ └ ◼ group1value1", + "", + "", "", "", "◇ foo -│ none", +│ group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > maxItems renders a sliding window 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > initial values can be set 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 -│ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ... +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◼ group1value1 └ ", - "", - "", + "", + "", "", - "│ ◻ opt0 -│ ◻ opt1 -│ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ... -└ + "◇ foo +│ group1value1", + " ", - "", - "", - "", - "│ ◻ opt1 -│ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ... + "[?25h", +] +`; + +exports[`prompts (isCI = false) > groupMultiselect > renders error when nothing selected 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", - "", + "", + "", "", - "│ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ... -└ + "▲ foo +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ Please select at least one option. +Press  space  to select,  enter  to submit ", - "", - "", + "", + "", "", - "│ ... -│ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ◻ opt5 -│ ... + "◆ foo +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ │ ◼ group1value0", "", + "", + "", "", - "│ ◻ opt3 -│ ◻ opt4 -│ ◻ opt5 -│ ◻ opt6 -│ ... + "◇ foo +│ group1value0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > groupMultiselect > renders message with options 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +│ ◻ group2 +│ └ ◻ group2value0 └ ", - "", - "", + "", + "", "", - "│ ◻ opt4 -│ ◻ opt5 -│ ◻ opt6 -│ ◻ opt7 -│ ... + "│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +│ ◻ group2 +│ └ ◻ group2value0 └ ", - "", - "", + "", + "", "", - "│ ◼ opt6", - "", - "", + "│ │ ◼ group1value0", + "", + "", "", "", "◇ foo -│ opt6", +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > renders message 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > selectableGroups = false > cannot select groups 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 +│  group1 +│  ◻ group1value0 +│  ◻ group1value1 └ ", - "", - "", + "", + "", "", - "│ ◼ opt0", + "│  ◼ group1value0", "", - "", + "", "", "", "◇ foo -│ opt0", +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > renders multiple cancelled values 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > selectableGroups = false > selecting all members of group does not select group 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 -│ ◻ opt2 +│  group1 +│  ◻ group1value0 +│  ◻ group1value1 └ ", "", - "", + "", "", - "│ ◼ opt0", - "", + "│  ◼ group1value0", + "", "", - "", + "", "", - "│ ◼ opt0 -│ ◻ opt1 -│ ◻ opt2 + "│  ◼ group1value0 +│  ◻ group1value1 └ ", "", - "", + "", "", - "│ ◼ opt1", - "", + "│  ◼ group1value1", + "", "", "", "", - "■ foo -│ opt0, opt1 -│", + "◇ foo +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > renders multiple selected options 1`] = ` +exports[`prompts (isCI = false) > groupMultiselect > values can be non-primitive 1`] = ` [ "[?25l", "│ ◆ foo -│ ◻ opt0 -│ ◻ opt1 -│ ◻ opt2 +│ ◻ group1 +│ │ ◻ value0 +│ └ ◻ value1 └ ", - "", - "", - "", - "│ ◼ opt0", - "", "", "", "", - "│ ◼ opt0 -│ ◻ opt1 -│ ◻ opt2 + "│ ◻ group1 +│ │ ◻ value0 +│ └ ◻ value1 └ ", "", "", "", - "│ ◼ opt1", + "│ │ ◼ value0", "", - "", - "", - "", - "│ ◼ opt1 -│ ◻ opt2 -└ -", "", "", "", "◇ foo -│ opt0, opt1", +│ value0", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > renders validation errors 1`] = ` +exports[`prompts (isCI = false) > multiselect > can cancel 1`] = ` [ "[?25l", "│ @@ -497,17 +594,115 @@ exports[`prompts (isCI = false) > multiselect > renders validation errors 1`] = "", "", "", - "▲ foo -│ ◻ opt0 -│ ◻ opt1 -└ Please select at least one option. -Press  space  to select,  enter  to submit + "■ foo +│", + " ", - "", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > can render option hints 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 (Hint 0) +│ ◻ opt1 +└ +", + "", + "", + "", + "│ ◼ opt0 (Hint 0)", + "", + "", "", "", - "◆ foo -│ ◼ opt0 + "◇ foo +│ opt0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > can set cursorAt to preselect an option 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 +│ ◻ opt1 +└ +", + "", + "", + "", + "│ ◼ opt1", + "", + "", + "", + "", + "◇ foo +│ opt1", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > can set custom labels 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ Option 0 +│ ◻ Option 1 +└ +", + "", + "", + "", + "│ ◼ Option 0", + "", + "", + "", + "", + "◇ foo +│ Option 0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > can set initial values 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 +│ ◼ opt1 +└ +", + "", + "", + "", + "◇ foo +│ opt1", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > can submit without selection when required = false 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 │ ◻ opt1 └ ", @@ -515,14 +710,14 @@ exports[`prompts (isCI = false) > multiselect > renders validation errors 1`] = "", "", "◇ foo -│ opt0", +│ none", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > multiselect > sliding window loops downwards 1`] = ` +exports[`prompts (isCI = false) > multiselect > maxItems renders a sliding window 1`] = ` [ "[?25l", "│ @@ -595,69 +790,38 @@ exports[`prompts (isCI = false) > multiselect > sliding window loops downwards 1 │ ◻ opt7 │ ... └ -", - "", - "", - "", - "│ ◻ opt5 -│ ◻ opt6 -│ ◻ opt7 -│ ◻ opt8 -│ ... -└ -", - "", - "", - "", - "│ ◻ opt6 -│ ◻ opt7 -│ ◻ opt8 -│ ◻ opt9 -│ ... -└ -", - "", - "", - "", - "│ ◻ opt7 -│ ◻ opt8 -│ ◻ opt9 -│ ◻ opt10 -│ ◻ opt11 -└ ", "", "", - "", - "│ ◻ opt9 -│ ◻ opt10 -│ ◻ opt11 -└ -", + "", + "│ ◼ opt6", + "", "", - "", + "", "", - "│ ◻ opt10 -│ ◻ opt11 -└ + "◇ foo +│ opt6", + " ", - "", - "", - "", - "│ ◻ opt0 + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > renders message 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 │ ◻ opt1 -│ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ... └ ", - "", + "", "", "", "│ ◼ opt0", - "", - "", + "", + "", "", "", "◇ foo @@ -668,7 +832,7 @@ exports[`prompts (isCI = false) > multiselect > sliding window loops downwards 1 ] `; -exports[`prompts (isCI = false) > multiselect > sliding window loops upwards 1`] = ` +exports[`prompts (isCI = false) > multiselect > renders multiple cancelled values 1`] = ` [ "[?25l", "│ @@ -676,77 +840,694 @@ exports[`prompts (isCI = false) > multiselect > sliding window loops upwards 1`] │ ◻ opt0 │ ◻ opt1 │ ◻ opt2 -│ ◻ opt3 -│ ◻ opt4 -│ ... └ ", - "", + "", + "", + "", + "│ ◼ opt0", + "", + "", "", "", - "│ ... -│ ◻ opt7 -│ ◻ opt8 -│ ◻ opt9 -│ ◻ opt10 -│ ◻ opt11 + "│ ◼ opt0 +│ ◻ opt1 +│ ◻ opt2 └ ", - "", - "", + "", + "", "", - "│ ◼ opt11", - "", - "", + "│ ◼ opt1", + "", + "", "", "", - "◇ foo -│ opt11", + "■ foo +│ opt0, opt1 +│", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > password > renders and clears validation errors 1`] = ` +exports[`prompts (isCI = false) > multiselect > renders multiple selected options 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ◻ opt0 +│ ◻ opt1 +│ ◻ opt2 └ ", - "", + "", "", "", - "│ ▪_", + "│ ◼ opt0", + "", + "", "", - "", - "", - "", - "▲ foo -│ ▪ -└ Password must be at least 2 characters -", - "", - "", "", - "◆ foo -│ ▪▪_ + "│ ◼ opt0 +│ ◻ opt1 +│ ◻ opt2 └ ", - "", - "", - "", + "", + "", + "", + "│ ◼ opt1", + "", + "", + "", + "", + "│ ◼ opt1 +│ ◻ opt2 +└ +", + "", + "", + "", + "◇ foo +│ opt0, opt1", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > renders validation errors 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 +│ ◻ opt1 +└ +", + "", + "", + "", + "▲ foo +│ ◻ opt0 +│ ◻ opt1 +└ Please select at least one option. +Press  space  to select,  enter  to submit +", + "", + "", + "", + "◆ foo +│ ◼ opt0 +│ ◻ opt1 +└ +", + "", + "", + "", + "◇ foo +│ opt0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > sliding window loops downwards 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 +│ ◻ opt1 +│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt0 +│ ◻ opt1 +│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt1 +│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ... +└ +", + "", + "", + "", + "│ ... +│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ◻ opt5 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt3 +│ ◻ opt4 +│ ◻ opt5 +│ ◻ opt6 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt4 +│ ◻ opt5 +│ ◻ opt6 +│ ◻ opt7 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt5 +│ ◻ opt6 +│ ◻ opt7 +│ ◻ opt8 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt6 +│ ◻ opt7 +│ ◻ opt8 +│ ◻ opt9 +│ ... +└ +", + "", + "", + "", + "│ ◻ opt7 +│ ◻ opt8 +│ ◻ opt9 +│ ◻ opt10 +│ ◻ opt11 +└ +", + "", + "", + "", + "│ ◻ opt9 +│ ◻ opt10 +│ ◻ opt11 +└ +", + "", + "", + "", + "│ ◻ opt10 +│ ◻ opt11 +└ +", + "", + "", + "", + "│ ◻ opt0 +│ ◻ opt1 +│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ... +└ +", + "", + "", + "", + "│ ◼ opt0", + "", + "", + "", + "", + "◇ foo +│ opt0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > multiselect > sliding window loops upwards 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ◻ opt0 +│ ◻ opt1 +│ ◻ opt2 +│ ◻ opt3 +│ ◻ opt4 +│ ... +└ +", + "", + "", + "", + "│ ... +│ ◻ opt7 +│ ◻ opt8 +│ ◻ opt9 +│ ◻ opt10 +│ ◻ opt11 +└ +", + "", + "", + "", + "│ ◼ opt11", + "", + "", + "", + "", + "◇ foo +│ opt11", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > password > renders and clears validation errors 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "▲ foo +│ ▪ +└ Password must be at least 2 characters +", + "", + "", + "", + "◆ foo +│ ▪▪_ +└ +", + "", + "", + "", + "◇ foo +│ ▪▪", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > password > renders cancelled value 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "■ foo +│ ▪ +│", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > password > renders custom mask 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ *_", + "", + "", + "", + "", + "│ **_", + "", + "", + "", + "", + "◇ foo +│ **", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > password > renders masked value 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", + "│ ▪_", + "", + "", + "", + "", + "│ ▪▪_", + "", + "", + "", + "", + "◇ foo +│ ▪▪", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > password > renders message 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ _ +└ +", + "", + "", + "", "◇ foo -│ ▪▪", +│", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > password > renders cancelled value 1`] = ` +exports[`prompts (isCI = false) > select > can cancel 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ● opt0 +│ ○ opt1 +└ +", + "", + "", + "", + "■ foo +│ opt0 +│", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > select > down arrow selects next option 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ● opt0 +│ ○ opt1 +└ +", + "", + "", + "", + "│ ○ opt0 +│ ● opt1 +└ +", + "", + "", + "", + "◇ foo +│ opt1", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > select > renders option hints 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ● opt0 (Hint 0) +│ ○ opt1 +└ +", + "", + "", + "", + "◇ foo +│ opt0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > select > renders option labels 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ● Option 0 +│ ○ Option 1 +└ +", + "", + "", + "", + "◇ foo +│ Option 0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > select > renders options and message 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ● opt0 +│ ○ opt1 +└ +", + "", + "", + "", + "◇ foo +│ opt0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > select > up arrow selects previous option 1`] = ` +[ + "[?25l", + "│ +◆ foo +│ ● opt0 +│ ○ opt1 +└ +", + "", + "", + "", + "│ ○ opt0 +│ ● opt1 +└ +", + "", + "", + "", + "│ ● opt0 +│ ○ opt1 +└ +", + "", + "", + "", + "◇ foo +│ opt0", + " +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > spinner > message > sets message for next frame 1`] = ` +[ + "[?25l", + "│ +", + "◒ ", + "", + "", + "◐ foo", +] +`; + +exports[`prompts (isCI = false) > spinner > start > renders frames at interval 1`] = ` +[ + "[?25l", + "│ +", + "◒ ", + "", + "", + "◐ ", + "", + "", + "◓ ", + "", + "", + "◑ ", +] +`; + +exports[`prompts (isCI = false) > spinner > start > renders message 1`] = ` +[ + "[?25l", + "│ +", + "◒ foo", +] +`; + +exports[`prompts (isCI = false) > spinner > start > renders timer when indicator is "timer" 1`] = ` +[ + "[?25l", + "│ +", + "◒ [0s]", +] +`; + +exports[`prompts (isCI = false) > spinner > stop > renders cancel symbol if code = 1 1`] = ` +[ + "[?25l", + "│ +", + "◒ ", + "", + "", + "■ +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > spinner > stop > renders error symbol if code > 1 1`] = ` +[ + "[?25l", + "│ +", + "◒ ", + "", + "", + "▲ +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > spinner > stop > renders message 1`] = ` +[ + "[?25l", + "│ +", + "◒ ", + "", + "", + "◇ foo +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > spinner > stop > renders submit symbol and stops spinner 1`] = ` +[ + "[?25l", + "│ +", + "◒ ", + "", + "", + "◇ +", + "[?25h", +] +`; + +exports[`prompts (isCI = false) > text > can cancel 1`] = ` [ "[?25l", "│ @@ -754,24 +1535,18 @@ exports[`prompts (isCI = false) > password > renders cancelled value 1`] = ` │ _ └ ", - "", - "", - "", - "│ ▪_", - "", "", "", "", "■ foo -│ ▪ -│", +│", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > password > renders custom mask 1`] = ` +exports[`prompts (isCI = false) > text > defaultValue sets the value but does not render 1`] = ` [ "[?25l", "│ @@ -779,28 +1554,18 @@ exports[`prompts (isCI = false) > password > renders custom mask 1`] = ` │ _ └ ", - "", - "", - "", - "│ *_", - "", - "", - "", - "", - "│ **_", - "", "", "", "", "◇ foo -│ **", +│ bar", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > password > renders masked value 1`] = ` +exports[`prompts (isCI = false) > text > renders cancelled value if one set 1`] = ` [ "[?25l", "│ @@ -811,25 +1576,26 @@ exports[`prompts (isCI = false) > password > renders masked value 1`] = ` "", "", "", - "│ ▪_", + "│ x█", "", "", "", "", - "│ ▪▪_", + "│ xy█", "", "", "", "", - "◇ foo -│ ▪▪", + "■ foo +│ xy +│", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > password > renders message 1`] = ` +exports[`prompts (isCI = false) > text > renders message 1`] = ` [ "[?25l", "│ @@ -841,613 +1607,713 @@ exports[`prompts (isCI = false) > password > renders message 1`] = ` "", "", "◇ foo -│", +│ undefined", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > select > can cancel 1`] = ` +exports[`prompts (isCI = false) > text > renders placeholder if set 1`] = ` [ "[?25l", "│ ◆ foo -│ ● opt0 -│ ○ opt1 +│ bar └ ", - "", + "", "", "", - "■ foo -│ opt0 -│", + "◇ foo +│ bar", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > select > down arrow selects next option 1`] = ` +exports[`prompts (isCI = false) > text > renders submitted value 1`] = ` [ "[?25l", "│ ◆ foo -│ ● opt0 -│ ○ opt1 +│ _ └ ", - "", + "", "", - "", - "│ ○ opt0 -│ ● opt1 -└ -", - "", + "", + "│ x█", + "", + "", + "", + "", + "│ xy█", + "", + "", "", "", "◇ foo -│ opt1", +│ xy", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > select > renders option hints 1`] = ` +exports[`prompts (isCI = false) > text > validation errors render and clear (using Error) 1`] = ` [ "[?25l", "│ ◆ foo -│ ● opt0 (Hint 0) -│ ○ opt1 +│ _ └ ", - "", + "", + "", + "", + "│ x█", + "", + "", "", "", - "◇ foo -│ opt0", - " -", - "[?25h", -] -`; - -exports[`prompts (isCI = false) > select > renders option labels 1`] = ` -[ - "[?25l", - "│ -◆ foo -│ ● Option 0 -│ ○ Option 1 -└ + "▲ foo +│ x█ +└ should be xy ", - "", + "", "", "", - "◇ foo -│ Option 0", - " -", - "[?25h", -] -`; - -exports[`prompts (isCI = false) > select > renders options and message 1`] = ` -[ - "[?25l", - "│ -◆ foo -│ ● opt0 -│ ○ opt1 + "◆ foo +│ xy█ └ ", - "", + "", "", "", "◇ foo -│ opt0", +│ xy", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > select > up arrow selects previous option 1`] = ` +exports[`prompts (isCI = false) > text > validation errors render and clear 1`] = ` [ "[?25l", "│ ◆ foo -│ ● opt0 -│ ○ opt1 +│ _ └ ", - "", + "", + "", + "", + "│ x█", "", + "", + "", "", - "│ ○ opt0 -│ ● opt1 -└ + "▲ foo +│ x█ +└ should be xy ", - "", - "", + "", + "", "", - "│ ● opt0 -│ ○ opt1 + "◆ foo +│ xy█ └ ", - "", + "", "", "", "◇ foo -│ opt0", +│ xy", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > spinner > message > sets message for next frame 1`] = ` -[ - "[?25l", - "│ -", - "◒ ", - "", - "", - "◐ foo", -] -`; - -exports[`prompts (isCI = false) > spinner > start > renders frames at interval 1`] = ` -[ - "[?25l", - "│ -", - "◒ ", - "", - "", - "◐ ", - "", - "", - "◓ ", - "", - "", - "◑ ", -] -`; - -exports[`prompts (isCI = false) > spinner > start > renders message 1`] = ` -[ - "[?25l", - "│ -", - "◒ foo", -] -`; - -exports[`prompts (isCI = false) > spinner > start > renders timer when indicator is "timer" 1`] = ` -[ - "[?25l", - "│ -", - "◒ [0s]", -] -`; - -exports[`prompts (isCI = false) > spinner > stop > renders cancel symbol if code = 1 1`] = ` +exports[`prompts (isCI = true) > confirm > can cancel 1`] = ` [ "[?25l", "│ +◆ foo +│ ● Yes / ○ No +└ ", - "◒ ", - "", + "", + "", "", - "■ + "■ foo +│ No +│", + " ", "[?25h", ] `; -exports[`prompts (isCI = false) > spinner > stop > renders error symbol if code > 1 1`] = ` +exports[`prompts (isCI = true) > confirm > can set initialValue 1`] = ` [ "[?25l", "│ +◆ foo +│ ○ Yes / ● No +└ ", - "◒ ", - "", + "", + "", "", - "▲ + "◇ foo +│ No", + " ", "[?25h", ] `; -exports[`prompts (isCI = false) > spinner > stop > renders message 1`] = ` +exports[`prompts (isCI = true) > confirm > left arrow moves to previous choice 1`] = ` [ "[?25l", "│ +◆ foo +│ ● Yes / ○ No +└ ", - "◒ ", - "", + "", + "", + "", + "│ ○ Yes / ● No", + "", + "", + "", + "", + "│ ● Yes / ○ No", + "", + "", + "", "", "◇ foo +│ Yes", + " ", "[?25h", ] `; -exports[`prompts (isCI = false) > spinner > stop > renders submit symbol and stops spinner 1`] = ` +exports[`prompts (isCI = true) > confirm > renders custom active choice 1`] = ` [ "[?25l", "│ +◆ foo +│ ● bleep / ○ No +└ ", - "◒ ", - "", + "", + "", "", - "◇ + "◇ foo +│ bleep", + " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > can cancel 1`] = ` +exports[`prompts (isCI = true) > confirm > renders custom inactive choice 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ● Yes / ○ bleep └ ", "", "", "", - "■ foo -│", + "◇ foo +│ Yes", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > defaultValue sets the value but does not render 1`] = ` +exports[`prompts (isCI = true) > confirm > renders message with choices 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ● Yes / ○ No └ ", "", "", "", "◇ foo -│ bar", +│ Yes", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > renders cancelled value if one set 1`] = ` +exports[`prompts (isCI = true) > confirm > right arrow moves to next choice 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ● Yes / ○ No └ ", "", "", "", - "│ x█", - "", - "", - "", - "", - "│ xy█", + "│ ○ Yes / ● No", "", "", "", "", - "■ foo -│ xy -│", + "◇ foo +│ No", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > renders message 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > can deselect an option 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ +", + "", + "", + "", + "│ │ ◼ group1value0", + "", + "", + "", + "", + "│ │ ◼ group1value0 +│ └ ◻ group1value1 +└ +", + "", + "", + "", + "│ ◼ group1 +│ │ ◼ group1value0 +│ └ ◼ group1value1 +└ +", + "", + "", + "", + "│ ◻ group1 +│ │ ◼ group1value0 +│ └ ◻ group1value1 +└ +", + "", "", "", "◇ foo -│ undefined", +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > renders placeholder if set 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > can select a group 1`] = ` [ "[?25l", "│ ◆ foo -│ bar +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ ◼ group1 +│ │ ◼ group1value0 +│ └ ◼ group1value1 +└ +", + "", "", "", "◇ foo -│ bar", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > renders submitted value 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > can select a group by selecting all members 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", - "", - "", - "│ x█", - "", - "", + "", "", + "", + "│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ +", + "", + "", "", - "│ xy█", + "│ │ ◼ group1value0", + "", + "", + "", + "", + "│ │ ◼ group1value0 +│ └ ◻ group1value1 +└ +", + "", "", - "", + "", + "│ ◼ group1 +│ │ ◼ group1value0 +│ └ ◼ group1value1 +└ +", + "", "", "", "◇ foo -│ xy", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > validation errors render and clear (using Error) 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > can select multiple options 1`] = ` [ "[?25l", "│ ◆ foo -│ _ +│ ◻ group1 +│ │ ◻ group1value0 +│ │ ◻ group1value1 +│ └ ◻ group1value2 └ ", - "", - "", - "", - "│ x█", + "", "", - "", - "", "", - "▲ foo -│ x█ -└ should be xy + "│ ◻ group1 +│ │ ◻ group1value0 +│ │ ◻ group1value1 +│ └ ◻ group1value2 +└ ", - "", - "", + "", + "", + "", + "│ │ ◼ group1value0", + "", + "", + "", "", - "◆ foo -│ xy█ + "│ │ ◼ group1value0 +│ │ ◻ group1value1 +│ └ ◻ group1value2 └ ", - "", + "", + "", + "", + "│ │ ◼ group1value1", + "", + "", "", "", "◇ foo -│ xy", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = false) > text > validation errors render and clear 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > can submit empty selection when require = false 1`] = ` [ "[?25l", "│ ◆ foo -│ _ -└ -", - "", - "", - "", - "│ x█", - "", - "", - "", - "", - "▲ foo -│ x█ -└ should be xy -", - "", - "", - "", - "◆ foo -│ xy█ +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", "", "", "◇ foo -│ xy", +│", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > can cancel 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > cursorAt sets initial selection 1`] = ` [ "[?25l", "│ ◆ foo -│ ● Yes / ○ No +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", + "", + "", + "", + "│ └ ◼ group1value1", + "", + "", "", "", - "■ foo -│ No -│", + "◇ foo +│ group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > can set initialValue 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > initial values can be set 1`] = ` [ "[?25l", "│ ◆ foo -│ ○ Yes / ● No +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◼ group1value1 └ ", - "", + "", "", "", "◇ foo -│ No", +│ group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > left arrow moves to previous choice 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > renders error when nothing selected 1`] = ` [ "[?25l", "│ ◆ foo -│ ● Yes / ○ No +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 └ ", - "", - "", - "", - "│ ○ Yes / ● No", - "", - "", - "", + "", + "", + "", + "▲ foo +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ Please select at least one option. +Press  space  to select,  enter  to submit +", + "", + "", + "", + "◆ foo +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +└ +", + "", + "", "", - "│ ● Yes / ○ No", - "", - "", + "│ │ ◼ group1value0", + "", + "", "", "", "◇ foo -│ Yes", +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > renders custom active choice 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > renders message with options 1`] = ` [ "[?25l", "│ ◆ foo -│ ● bleep / ○ No +│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +│ ◻ group2 +│ └ ◻ group2value0 └ ", - "", + "", + "", + "", + "│ ◻ group1 +│ │ ◻ group1value0 +│ └ ◻ group1value1 +│ ◻ group2 +│ └ ◻ group2value0 +└ +", + "", + "", + "", + "│ │ ◼ group1value0", + "", + "", "", "", "◇ foo -│ bleep", +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > renders custom inactive choice 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > selectableGroups = false > cannot select groups 1`] = ` [ "[?25l", "│ ◆ foo -│ ● Yes / ○ bleep +│  group1 +│  ◻ group1value0 +│  ◻ group1value1 └ ", - "", + "", + "", + "", + "│  ◼ group1value0", + "", + "", "", "", "◇ foo -│ Yes", +│ group1value0", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > renders message with choices 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > selectableGroups = false > selecting all members of group does not select group 1`] = ` [ "[?25l", "│ ◆ foo -│ ● Yes / ○ No +│  group1 +│  ◻ group1value0 +│  ◻ group1value1 └ ", - "", + "", + "", + "", + "│  ◼ group1value0", + "", + "", + "", + "", + "│  ◼ group1value0 +│  ◻ group1value1 +└ +", + "", + "", + "", + "│  ◼ group1value1", + "", + "", "", "", "◇ foo -│ Yes", +│ group1value0, group1value1", " ", "[?25h", ] `; -exports[`prompts (isCI = true) > confirm > right arrow moves to next choice 1`] = ` +exports[`prompts (isCI = true) > groupMultiselect > values can be non-primitive 1`] = ` [ "[?25l", "│ ◆ foo -│ ● Yes / ○ No +│ ◻ group1 +│ │ ◻ value0 +│ └ ◻ value1 └ ", - "", + "", "", + "", + "│ ◻ group1 +│ │ ◻ value0 +│ └ ◻ value1 +└ +", + "", + "", "", - "│ ○ Yes / ● No", - "", - "", + "│ │ ◼ value0", + "", + "", "", "", "◇ foo -│ No", +│ value0", " ", "[?25h", diff --git a/packages/prompts/src/index.test.ts b/packages/prompts/src/index.test.ts index 6af64e00..aa2fab52 100644 --- a/packages/prompts/src/index.test.ts +++ b/packages/prompts/src/index.test.ts @@ -899,4 +899,286 @@ describe.each(['true', 'false'])('prompts (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); }); + + describe('groupMultiselect', () => { + test('renders message with options', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + group2: [{ value: 'group2value0' }], + }, + }); + + // Select the first non-group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + + // submit + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('can select multiple options', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }, { value: 'group1value2' }], + }, + }); + + // Select the first non-group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + // Select the second non-group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + + // submit + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0', 'group1value1']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('can select a group', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + }); + + // Select the group as a whole + input.emit('keypress', '', { name: 'space' }); + + // submit + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0', 'group1value1']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('can select a group by selecting all members', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + }); + + // Select the first group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + // Select the second group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + + // submit + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0', 'group1value1']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('can deselect an option', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + }); + + // Select the first group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + // Select the second group option + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + // Deselect it + input.emit('keypress', '', { name: 'space' }); + + // submit + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('renders error when nothing selected', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + }); + + // try submit + input.emit('keypress', '', { name: 'return' }); + // now select something and submit + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0']); + expect(output.buffer).toMatchSnapshot(); + }); + + describe('selectableGroups = false', () => { + test('cannot select groups', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + selectableGroups: false, + }); + + // first selectable item should be group's child + input.emit('keypress', '', { name: 'space' }); + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('selecting all members of group does not select group', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + selectableGroups: false, + }); + + // first selectable item should be group's child + input.emit('keypress', '', { name: 'space' }); + // select second item + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + // submit + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value0', 'group1value1']); + expect(output.buffer).toMatchSnapshot(); + }); + }); + + test('can submit empty selection when require = false', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + required: false, + }); + + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual([]); + expect(output.buffer).toMatchSnapshot(); + }); + + test('cursorAt sets initial selection', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + cursorAt: 'group1value1', + }); + + input.emit('keypress', '', { name: 'space' }); + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value1']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('initial values can be set', async () => { + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], + }, + initialValues: ['group1value1'], + }); + + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual(['group1value1']); + expect(output.buffer).toMatchSnapshot(); + }); + + test('values can be non-primitive', async () => { + const value0 = Symbol(); + const value1 = Symbol(); + const result = prompts.groupMultiselect({ + message: 'foo', + input, + output, + options: { + group1: [ + { value: value0, label: 'value0' }, + { value: value1, label: 'value1' }, + ], + }, + }); + + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + input.emit('keypress', '', { name: 'return' }); + + const value = await result; + + expect(value).toEqual([value0]); + expect(output.buffer).toMatchSnapshot(); + }); + }); });