tty-prompt 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +174 -63
  4. data/Rakefile +2 -2
  5. data/examples/ask_blank.rb +9 -0
  6. data/examples/enum_select_disabled.rb +1 -1
  7. data/examples/enum_select_paged.rb +1 -1
  8. data/examples/expand_auto.rb +29 -0
  9. data/examples/mask.rb +1 -1
  10. data/examples/multi_select.rb +1 -1
  11. data/examples/multi_select_disabled_paged.rb +22 -0
  12. data/examples/multi_select_paged.rb +1 -1
  13. data/examples/select_disabled_paged.rb +22 -0
  14. data/examples/select_paginated.rb +1 -1
  15. data/lib/tty/prompt.rb +46 -10
  16. data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +19 -18
  17. data/lib/tty/prompt/choice.rb +1 -3
  18. data/lib/tty/prompt/enum_list.rb +31 -9
  19. data/lib/tty/prompt/expander.rb +19 -1
  20. data/lib/tty/prompt/keypress.rb +30 -35
  21. data/lib/tty/prompt/list.rb +112 -40
  22. data/lib/tty/prompt/mask_question.rb +2 -3
  23. data/lib/tty/prompt/multi_list.rb +36 -12
  24. data/lib/tty/prompt/paginator.rb +37 -25
  25. data/lib/tty/prompt/question.rb +29 -5
  26. data/lib/tty/prompt/slider.rb +16 -8
  27. data/lib/tty/prompt/symbols.rb +30 -6
  28. data/lib/tty/prompt/timer.rb +75 -0
  29. data/lib/tty/prompt/version.rb +1 -1
  30. data/spec/spec_helper.rb +18 -2
  31. data/spec/unit/ask_spec.rb +45 -4
  32. data/spec/unit/{enum_paginator_spec.rb → block_paginator_spec.rb} +14 -5
  33. data/spec/unit/choice/from_spec.rb +16 -0
  34. data/spec/unit/enum_select_spec.rb +104 -32
  35. data/spec/unit/expand_spec.rb +104 -12
  36. data/spec/unit/keypress_spec.rb +2 -8
  37. data/spec/unit/mask_spec.rb +9 -1
  38. data/spec/unit/multi_select_spec.rb +348 -118
  39. data/spec/unit/paginator_spec.rb +29 -10
  40. data/spec/unit/select_spec.rb +390 -108
  41. data/spec/unit/slider_spec.rb +48 -6
  42. data/spec/unit/timer_spec.rb +29 -0
  43. data/tty-prompt.gemspec +4 -6
  44. metadata +17 -46
  45. data/lib/tty/prompt/timeout.rb +0 -78
@@ -59,14 +59,8 @@ RSpec.describe TTY::Prompt::Question, '#keypress' do
59
59
  it "timeouts when no key provided" do
60
60
  prompt = TTY::TestPrompt.new(interrupt: :exit)
61
61
 
62
- prompt.keypress("Press any key or continue in :countdown", timeout: 0.1)
62
+ prompt.keypress("Press any key or continue in :countdown", timeout: 0.01)
63
63
 
64
- expect(prompt.output.string).to eq([
65
- "Press any key or continue in 0.1 ",
66
- "\e[2K\e[1G",
67
- "Press any key or continue in 0 ",
68
- "\e[2K\e[1G",
69
- "Press any key or continue in 0 \n"
70
- ].join)
64
+ expect(prompt.output.string).to include("Press any key or continue in 0.00")
71
65
  end
72
66
  end
@@ -9,7 +9,9 @@ RSpec.describe TTY::Prompt, '#mask' do
9
9
  it "masks output by default" do
10
10
  prompt.input << "pass\r"
11
11
  prompt.input.rewind
12
+
12
13
  answer = prompt.mask("What is your password?")
14
+
13
15
  expect(answer).to eql("pass")
14
16
  expect(prompt.output.string).to eq([
15
17
  "What is your password? ",
@@ -31,7 +33,9 @@ RSpec.describe TTY::Prompt, '#mask' do
31
33
  it 'masks output with custom character' do
32
34
  prompt.input << "pass\r"
33
35
  prompt.input.rewind
36
+
34
37
  answer = prompt.mask("What is your password?") { |q| q.mask('*') }
38
+
35
39
  expect(answer).to eql("pass")
36
40
  expect(prompt.output.string).to eq([
37
41
  "What is your password? ",
@@ -53,7 +57,9 @@ RSpec.describe TTY::Prompt, '#mask' do
53
57
  it "masks with unicode character" do
54
58
  prompt.input << "lov\n"
55
59
  prompt.input.rewind
60
+
56
61
  answer = prompt.mask("What is your password?", mask: "\u2665")
62
+
57
63
  expect(answer).to eql("lov")
58
64
  expect(prompt.output.string).to eq([
59
65
  "What is your password? ",
@@ -73,10 +79,12 @@ RSpec.describe TTY::Prompt, '#mask' do
73
79
  it 'ignores mask if echo is off' do
74
80
  prompt.input << "pass\n"
75
81
  prompt.input.rewind
82
+
76
83
  answer = prompt.mask('What is your password?') do |q|
77
84
  q.echo false
78
85
  q.mask '*'
79
86
  end
87
+
80
88
  expect(answer).to eql("pass")
81
89
  expect(prompt.output.string).to eq([
82
90
  "What is your password? ",
@@ -96,11 +104,11 @@ RSpec.describe TTY::Prompt, '#mask' do
96
104
  end
97
105
 
98
106
  it "validates input" do
107
+ prompt = TTY::TestPrompt.new(symbols: {dot: '*'})
99
108
  prompt.input << "no\nyes\n"
100
109
  prompt.input.rewind
101
110
  answer = prompt.mask('What is your password?') do |q|
102
111
  q.echo true
103
- q.mask '*'
104
112
  q.validate(/[a-z]{3,4}/)
105
113
  q.messages[:valid?] = 'Not valid'
106
114
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  RSpec.describe TTY::Prompt do
4
4
  let(:symbols) { TTY::Prompt::Symbols.symbols }
5
+ let(:up_down) { "#{symbols[:arrow_up]}/#{symbols[:arrow_down]}" }
6
+ let(:left_right) { "#{symbols[:arrow_left]}/#{symbols[:arrow_right]}"}
5
7
 
6
8
  def output_helper(prompt, choices, active, selected, options = {})
7
9
  raise ":init requires :hint" if options[:init] && options[:hint].nil?
@@ -12,7 +14,9 @@ RSpec.describe TTY::Prompt do
12
14
  out = []
13
15
  out << "\e[?25l" if init
14
16
  out << prompt << " "
17
+ out << "(max. #{options[:max]}) " if options[:max]
15
18
  out << selected.join(', ')
19
+ out << " " if init && !selected.empty?
16
20
  out << "\e[90m" if init
17
21
  out << (init ? "(#{hint})\e[0m" : " (#{hint})") if hint
18
22
  out << "\n"
@@ -21,7 +25,7 @@ RSpec.describe TTY::Prompt do
21
25
  disabled = choice.is_a?(Hash) ? choice[:disabled] : false
22
26
  num = (i + 1).to_s + enum if enum
23
27
 
24
- prefix = name == active ? "#{symbols[:pointer]} " : " "
28
+ prefix = name == active ? "#{symbols[:marker]} " : " "
25
29
  prefix += if disabled
26
30
  "\e[31m#{symbols[:cross]}\e[0m #{num}#{name} #{disabled}"
27
31
  elsif selected.include?(name)
@@ -48,17 +52,20 @@ RSpec.describe TTY::Prompt do
48
52
  choices = %i(vodka beer wine whisky bourbon)
49
53
  prompt.input << "\r"
50
54
  prompt.input.rewind
55
+
51
56
  expect(prompt.multi_select("Select drinks?", choices)). to eq([])
52
- expect(prompt.output.string).to eq([
53
- "\e[?25lSelect drinks? \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
54
- "#{symbols[:pointer]} #{symbols[:radio_off]} vodka\n",
57
+
58
+ expected_output = [
59
+ "\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
60
+ "#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
55
61
  " #{symbols[:radio_off]} beer\n",
56
62
  " #{symbols[:radio_off]} wine\n",
57
63
  " #{symbols[:radio_off]} whisky\n",
58
64
  " #{symbols[:radio_off]} bourbon",
59
65
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
60
66
  "Select drinks? \n\e[?25h"
61
- ].join)
67
+ ].join
68
+ expect(prompt.output.string).to eq(expected_output)
62
69
  end
63
70
 
64
71
  it "selects item when space pressed" do
@@ -67,23 +74,25 @@ RSpec.describe TTY::Prompt do
67
74
  prompt.input << " \r"
68
75
  prompt.input.rewind
69
76
  expect(prompt.multi_select("Select drinks?", choices)). to eq(['vodka'])
70
- expect(prompt.output.string).to eq([
71
- "\e[?25lSelect drinks? \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
72
- "#{symbols[:pointer]} #{symbols[:radio_off]} vodka\n",
77
+
78
+ expected_output = [
79
+ "\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
80
+ "#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
73
81
  " #{symbols[:radio_off]} beer\n",
74
82
  " #{symbols[:radio_off]} wine\n",
75
83
  " #{symbols[:radio_off]} whisky\n",
76
84
  " #{symbols[:radio_off]} bourbon",
77
85
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
78
86
  "Select drinks? vodka\n",
79
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
87
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
80
88
  " #{symbols[:radio_off]} beer\n",
81
89
  " #{symbols[:radio_off]} wine\n",
82
90
  " #{symbols[:radio_off]} whisky\n",
83
91
  " #{symbols[:radio_off]} bourbon",
84
92
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
85
93
  "Select drinks? \e[32mvodka\e[0m\n\e[?25h"
86
- ].join)
94
+ ].join
95
+ expect(prompt.output.string).to eq(expected_output)
87
96
  end
88
97
 
89
98
  it "selects item when space pressed but doesn't echo item if echo: false" do
@@ -92,23 +101,25 @@ RSpec.describe TTY::Prompt do
92
101
  prompt.input << " \r"
93
102
  prompt.input.rewind
94
103
  expect(prompt.multi_select("Select drinks?", choices, echo: false)). to eq(['vodka'])
95
- expect(prompt.output.string).to eq([
96
- "\e[?25lSelect drinks? \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
97
- "#{symbols[:pointer]} #{symbols[:radio_off]} vodka\n",
104
+
105
+ expected_output = [
106
+ "\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
107
+ "#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
98
108
  " #{symbols[:radio_off]} beer\n",
99
109
  " #{symbols[:radio_off]} wine\n",
100
110
  " #{symbols[:radio_off]} whisky\n",
101
111
  " #{symbols[:radio_off]} bourbon",
102
112
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
103
113
  "Select drinks? \n",
104
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
114
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
105
115
  " #{symbols[:radio_off]} beer\n",
106
116
  " #{symbols[:radio_off]} wine\n",
107
117
  " #{symbols[:radio_off]} whisky\n",
108
118
  " #{symbols[:radio_off]} bourbon",
109
119
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
110
120
  "Select drinks? \n\e[?25h"
111
- ].join)
121
+ ].join
122
+ expect(prompt.output.string).to eq(expected_output)
112
123
  end
113
124
 
114
125
  it "sets choice custom values" do
@@ -117,23 +128,24 @@ RSpec.describe TTY::Prompt do
117
128
  prompt.input << " \r"
118
129
  prompt.input.rewind
119
130
  expect(prompt.multi_select("Select drinks?", choices)).to eq([1])
120
- expect(prompt.output.string).to eq([
121
- "\e[?25lSelect drinks? \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
122
- "#{symbols[:pointer]} #{symbols[:radio_off]} vodka\n",
131
+ expected_output = [
132
+ "\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
133
+ "#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
123
134
  " #{symbols[:radio_off]} beer\n",
124
135
  " #{symbols[:radio_off]} wine\n",
125
136
  " #{symbols[:radio_off]} whisky\n",
126
137
  " #{symbols[:radio_off]} bourbon",
127
138
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
128
139
  "Select drinks? vodka\n",
129
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
140
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m vodka\n",
130
141
  " #{symbols[:radio_off]} beer\n",
131
142
  " #{symbols[:radio_off]} wine\n",
132
143
  " #{symbols[:radio_off]} whisky\n",
133
144
  " #{symbols[:radio_off]} bourbon",
134
145
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
135
146
  "Select drinks? \e[32mvodka\e[0m\n\e[?25h"
136
- ].join)
147
+ ].join
148
+ expect(prompt.output.string).to eq(expected_output)
137
149
  end
138
150
 
139
151
  it "sets choice name and value through DSL" do
@@ -141,6 +153,7 @@ RSpec.describe TTY::Prompt do
141
153
  prompt.input << " \r"
142
154
  prompt.input.rewind
143
155
  value = prompt.multi_select("Select drinks?") do |menu|
156
+ menu.symbols marker: '>', radio_off: '-', radio_on: '='
144
157
  menu.enum ')'
145
158
 
146
159
  menu.choice :vodka, {score: 1}
@@ -150,19 +163,19 @@ RSpec.describe TTY::Prompt do
150
163
  end
151
164
  expect(value).to eq([{score: 1}])
152
165
  expect(prompt.output.string).to eq([
153
- "\e[?25lSelect drinks? \e[90m(Use arrow or number (1-5) keys, press Space to select and Enter to finish)\e[0m\n",
154
- "#{symbols[:pointer]} #{symbols[:radio_off]} 1) vodka\n",
155
- " #{symbols[:radio_off]} 2) beer\n",
156
- " #{symbols[:radio_off]} 3) wine\n",
157
- " #{symbols[:radio_off]} 4) whisky\n",
158
- " #{symbols[:radio_off]} 5) bourbon",
166
+ "\e[?25lSelect drinks? \e[90m(Use #{up_down} arrow or number (1-5) keys, press Space to select and Enter to finish)\e[0m\n",
167
+ "> - 1) vodka\n",
168
+ " - 2) beer\n",
169
+ " - 3) wine\n",
170
+ " - 4) whisky\n",
171
+ " - 5) bourbon",
159
172
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
160
173
  "Select drinks? vodka\n",
161
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m 1) vodka\n",
162
- " #{symbols[:radio_off]} 2) beer\n",
163
- " #{symbols[:radio_off]} 3) wine\n",
164
- " #{symbols[:radio_off]} 4) whisky\n",
165
- " #{symbols[:radio_off]} 5) bourbon",
174
+ "> \e[32m=\e[0m 1) vodka\n",
175
+ " - 2) beer\n",
176
+ " - 3) wine\n",
177
+ " - 4) whisky\n",
178
+ " - 5) bourbon",
166
179
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
167
180
  "Select drinks? \e[32mvodka\e[0m\n\e[?25h"
168
181
  ].join)
@@ -183,12 +196,12 @@ RSpec.describe TTY::Prompt do
183
196
  end
184
197
  expect(value).to match_array([{score: 20}, {score: 50}])
185
198
  expect(prompt.output.string).to eq([
186
- "\e[?25lSelect drinks? beer, bourbon \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
199
+ "\e[?25lSelect drinks? beer, bourbon \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
187
200
  " #{symbols[:radio_off]} vodka\n",
188
201
  " \e[32m#{symbols[:radio_on]}\e[0m beer\n",
189
202
  " #{symbols[:radio_off]} wine\n",
190
203
  " #{symbols[:radio_off]} whisky\n",
191
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m bourbon",
204
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m bourbon",
192
205
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
193
206
  "Select drinks? \e[32mbeer, bourbon\e[0m\n\e[?25h",
194
207
  ].join)
@@ -231,8 +244,8 @@ RSpec.describe TTY::Prompt do
231
244
  prompt.input.rewind
232
245
  expect(prompt.multi_select("Select drinks?", choices)). to eq([])
233
246
  expect(prompt.output.string).to eq([
234
- "\e[?25l[?] Select drinks? \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
235
- "#{symbols[:pointer]} #{symbols[:radio_off]} vodka\n",
247
+ "\e[?25l[?] Select drinks? \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
248
+ "#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
236
249
  " #{symbols[:radio_off]} beer\n",
237
250
  " #{symbols[:radio_off]} wine\n",
238
251
  " #{symbols[:radio_off]} whisky\n",
@@ -247,10 +260,10 @@ RSpec.describe TTY::Prompt do
247
260
  choices = %w(vodka beer wine whisky bourbon)
248
261
  prompt.input << "\r"
249
262
  prompt.input.rewind
250
- options = {default: [1], active_color: :blue, marker: '>'}
263
+ options = {default: [1], active_color: :blue, symbols: {marker: '>'}}
251
264
  expect(prompt.multi_select("Select drinks?", choices, options)). to eq(['vodka'])
252
265
  expect(prompt.output.string).to eq([
253
- "\e[?25lSelect drinks? vodka \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
266
+ "\e[?25lSelect drinks? vodka \e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
254
267
  "> \e[34m#{symbols[:radio_on]}\e[0m vodka\n",
255
268
  " #{symbols[:radio_off]} beer\n",
256
269
  " #{symbols[:radio_off]} wine\n",
@@ -266,95 +279,217 @@ RSpec.describe TTY::Prompt do
266
279
  choices = %w(vodka beer wine whisky bourbon)
267
280
  prompt.input << "\r"
268
281
  prompt.input.rewind
269
- expect(prompt.multi_select("Select drinks?", choices, help: '(Bash keyboard)')). to eq([])
270
- expect(prompt.output.string).to eq([
282
+
283
+ answer = prompt.multi_select("Select drinks?", choices, help: '(Bash keyboard)')
284
+
285
+ expect(answer).to eq([])
286
+ expected_output = [
271
287
  "\e[?25lSelect drinks? \e[90m(Bash keyboard)\e[0m\n",
272
- "#{symbols[:pointer]} #{symbols[:radio_off]} vodka\n",
288
+ "#{symbols[:marker]} #{symbols[:radio_off]} vodka\n",
273
289
  " #{symbols[:radio_off]} beer\n",
274
290
  " #{symbols[:radio_off]} wine\n",
275
291
  " #{symbols[:radio_off]} whisky\n",
276
292
  " #{symbols[:radio_off]} bourbon",
277
293
  "\e[2K\e[1G\e[1A" * 5, "\e[2K\e[1G",
278
294
  "Select drinks? \n\e[?25h"
279
- ].join)
295
+ ].join
296
+ expect(prompt.output.string).to eq(expected_output)
280
297
  end
281
298
 
282
- it "paginates long selections" do
283
- prompt = TTY::TestPrompt.new
284
- choices = %w(A B C D E F G H)
285
- prompt.input << "\r"
286
- prompt.input.rewind
287
- value = prompt.multi_select("What letter?", choices, per_page: 3, default: 4)
288
- expect(value).to eq(['D'])
289
- expect(prompt.output.string).to eq([
290
- "\e[?25lWhat letter? D \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
291
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
292
- " #{symbols[:radio_off]} E\n",
293
- " #{symbols[:radio_off]} F\n",
294
- "\e[90m(Move up or down to reveal more choices)\e[0m",
295
- "\e[2K\e[1G\e[1A" * 4, "\e[2K\e[1G",
296
- "What letter? \e[32mD\e[0m\n\e[?25h",
297
- ].join)
298
- end
299
+ context "when paginated" do
300
+ it "paginates long selections" do
301
+ prompt = TTY::TestPrompt.new
302
+ choices = %w(A B C D E F G H)
303
+ prompt.input << "\r"
304
+ prompt.input.rewind
299
305
 
300
- it "paginates choices as hash object" do
301
- prompt = TTY::TestPrompt.new
302
- choices = {A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8}
303
- prompt.input << "\r"
304
- prompt.input.rewind
305
- value = prompt.multi_select("What letter?", choices, default: 4, per_page: 3)
306
- expect(value).to eq([4])
307
- expect(prompt.output.string).to eq([
308
- "\e[?25lWhat letter? D \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
309
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
310
- " #{symbols[:radio_off]} E\n",
311
- " #{symbols[:radio_off]} F\n",
312
- "\e[90m(Move up or down to reveal more choices)\e[0m",
313
- "\e[2K\e[1G\e[1A" * 4, "\e[2K\e[1G",
314
- "What letter? \e[32mD\e[0m\n\e[?25h",
315
- ].join)
316
- end
306
+ answer = prompt.multi_select("What letter?", choices, per_page: 3, default: 4)
307
+
308
+ expect(answer).to eq(['D'])
309
+ expected_output = [
310
+ "\e[?25lWhat letter? D \e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
311
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
312
+ " #{symbols[:radio_off]} E\n",
313
+ " #{symbols[:radio_off]} F",
314
+ "\e[2K\e[1G\e[1A" * 3,
315
+ "\e[2K\e[1G",
316
+ "What letter? \e[32mD\e[0m\n\e[?25h",
317
+ ].join
318
+ expect(prompt.output.string).to eq(expected_output)
319
+ end
317
320
 
318
- it "paginates long selections through DSL" do
319
- prompt = TTY::TestPrompt.new
320
- choices = %w(A B C D E F G H)
321
- prompt.input << "\r"
322
- prompt.input.rewind
323
- value = prompt.multi_select("What letter?") do |menu|
324
- menu.per_page 3
325
- menu.page_help '(Wiggle thy finger up or down to see more)'
326
- menu.default 4
327
- menu.choices choices
328
- end
329
- expect(value).to eq(['D'])
330
- expect(prompt.output.string).to eq([
331
- "\e[?25lWhat letter? D \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
332
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
333
- " #{symbols[:radio_off]} E\n",
334
- " #{symbols[:radio_off]} F\n",
335
- "\e[90m(Wiggle thy finger up or down to see more)\e[0m",
336
- "\e[2K\e[1G\e[1A" * 4, "\e[2K\e[1G",
337
- "What letter? \e[32mD\e[0m\n\e[?25h",
338
- ].join)
339
- end
321
+ it "paginates choices as hash object" do
322
+ prompt = TTY::TestPrompt.new
323
+ choices = {A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8}
324
+ prompt.input << "\r"
325
+ prompt.input.rewind
340
326
 
341
- it "doesn't paginate short selections" do
342
- prompt = TTY::TestPrompt.new
343
- choices = %w(A B C D)
344
- prompt.input << "\r"
345
- prompt.input.rewind
346
- value = prompt.multi_select("What letter?", choices, per_page: 4, default: 1)
347
- expect(value).to eq(['A'])
327
+ answer = prompt.multi_select("What letter?", choices, default: 4, per_page: 3)
328
+
329
+ expect(answer).to eq([4])
330
+ expected_output = [
331
+ "\e[?25lWhat letter? D ",
332
+ "\e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
333
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
334
+ " #{symbols[:radio_off]} E\n",
335
+ " #{symbols[:radio_off]} F",
336
+ "\e[2K\e[1G\e[1A" * 3, "\e[2K\e[1G",
337
+ "What letter? \e[32mD\e[0m\n\e[?25h",
338
+ ].join
339
+ expect(prompt.output.string).to eq(expected_output)
340
+ end
348
341
 
349
- expect(prompt.output.string).to eq([
350
- "\e[?25lWhat letter? A \e[90m(Use arrow keys, press Space to select and Enter to finish)\e[0m\n",
351
- "#{symbols[:pointer]} \e[32m#{symbols[:radio_on]}\e[0m A\n",
352
- " #{symbols[:radio_off]} B\n",
353
- " #{symbols[:radio_off]} C\n",
354
- " #{symbols[:radio_off]} D",
355
- "\e[2K\e[1G\e[1A" * 4, "\e[2K\e[1G",
356
- "What letter? \e[32mA\e[0m\n\e[?25h",
357
- ].join)
342
+ it "paginates long selections through DSL" do
343
+ prompt = TTY::TestPrompt.new
344
+ choices = %w(A B C D E F G H)
345
+ prompt.input << "\r"
346
+ prompt.input.rewind
347
+
348
+ answer = prompt.multi_select("What letter?") do |menu|
349
+ menu.per_page 3
350
+ menu.default 4
351
+ menu.choices choices
352
+ end
353
+
354
+ expect(answer).to eq(['D'])
355
+ expected_output = [
356
+ "\e[?25lWhat letter? D ",
357
+ "\e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
358
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m D\n",
359
+ " #{symbols[:radio_off]} E\n",
360
+ " #{symbols[:radio_off]} F",
361
+ "\e[2K\e[1G\e[1A" * 3,
362
+ "\e[2K\e[1G",
363
+ "What letter? \e[32mD\e[0m\n\e[?25h",
364
+ ].join
365
+ expect(prompt.output.string).to eq(expected_output)
366
+ end
367
+
368
+ it "doesn't paginate short selections" do
369
+ prompt = TTY::TestPrompt.new
370
+ choices = %w(A B C D)
371
+ prompt.input << "\r"
372
+ prompt.input.rewind
373
+ value = prompt.multi_select("What letter?", choices, per_page: 4, default: 1)
374
+ expect(value).to eq(['A'])
375
+
376
+ expect(prompt.output.string).to eq([
377
+ "\e[?25lWhat letter? A ",
378
+ "\e[90m(Use #{up_down} arrow keys, press Space to select and Enter to finish)\e[0m\n",
379
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m A\n",
380
+ " #{symbols[:radio_off]} B\n",
381
+ " #{symbols[:radio_off]} C\n",
382
+ " #{symbols[:radio_off]} D",
383
+ "\e[2K\e[1G\e[1A" * 4, "\e[2K\e[1G",
384
+ "What letter? \e[32mA\e[0m\n\e[?25h",
385
+ ].join)
386
+ end
387
+
388
+ it "navigates evenly paged output with right arrow until end of selection" do
389
+ prompt = TTY::TestPrompt.new
390
+ choices = ('1'..'12').to_a
391
+ prompt.on(:keypress) { |e| prompt.trigger(:keyright) if e.value == "l" }
392
+ prompt.input << "l" << "l" << "l" << " " << "\r"
393
+ prompt.input.rewind
394
+
395
+ answer = prompt.multi_select("What number?", choices, per_page: 4)
396
+
397
+ expect(answer).to eq(["9"])
398
+
399
+ expected_output = [
400
+ output_helper("What number?", choices[0..3], "1", [], init: true,
401
+ hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
402
+ output_helper("What number?", choices[4..7], "5", []),
403
+ output_helper("What number?", choices[8..11], "9", []),
404
+ output_helper("What number?", choices[8..11], "9", []),
405
+ output_helper("What number?", choices[8..11], "9", ["9"]),
406
+ "What number? \e[32m9\e[0m\n\e[?25h",
407
+ ].join
408
+
409
+ expect(prompt.output.string).to eq(expected_output)
410
+ end
411
+
412
+ it "navigates unevenly paged output with right arrow until the end of selection" do
413
+ prompt = TTY::TestPrompt.new
414
+ choices = ('1'..'10').to_a
415
+ prompt.on(:keypress) { |e| prompt.trigger(:keyright) if e.value == "l" }
416
+ prompt.input << "l" << "l" << "l" << " " << "\r"
417
+ prompt.input.rewind
418
+
419
+ answer = prompt.multi_select("What number?", choices, default: 4, per_page: 4)
420
+
421
+ expect(answer).to eq(['4', '10'])
422
+
423
+ expected_output = [
424
+ output_helper("What number?", choices[3..6], "4", ["4"], init: true,
425
+ hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
426
+ output_helper("What number?", choices[4..7], "8", ["4"]),
427
+ output_helper("What number?", choices[8..9], "10", ["4"]),
428
+ output_helper("What number?", choices[8..9], "10", ["4"]),
429
+ output_helper("What number?", choices[8..9], "10", ["4", "10"]),
430
+ "What number? \e[32m4, 10\e[0m\n\e[?25h",
431
+ ].join
432
+
433
+ expect(prompt.output.string).to eq(expected_output)
434
+ end
435
+
436
+ it "navigates left and right" do
437
+ prompt = TTY::TestPrompt.new
438
+ choices = ('1'..'10').to_a
439
+ prompt.on(:keypress) { |e|
440
+ prompt.trigger(:keyright) if e.value == "l"
441
+ prompt.trigger(:keyleft) if e.value == "h"
442
+ }
443
+ prompt.input << "l" << "l" << "h" << " " << "\r"
444
+ prompt.input.rewind
445
+
446
+ answer = prompt.multi_select("What number?", choices, default: 2, per_page: 4)
447
+
448
+ expect(answer).to eq(['2', '6'])
449
+
450
+ expected_output = [
451
+ output_helper("What number?", choices[0..3], "2", ["2"], init: true,
452
+ hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
453
+ output_helper("What number?", choices[4..7], "6", ["2"]),
454
+ output_helper("What number?", choices[8..9], "10", ["2"]),
455
+ output_helper("What number?", choices[4..7], "6", ["2"]),
456
+ output_helper("What number?", choices[4..7], "6", ["2", "6"]),
457
+ "What number? \e[32m2, 6\e[0m\n\e[?25h",
458
+ ].join
459
+
460
+ expect(prompt.output.string).to eq(expected_output)
461
+ end
462
+
463
+ it "combines up/down navigation with left/right" do
464
+ prompt = TTY::TestPrompt.new
465
+ choices = ('1'..'11').to_a
466
+ prompt.on(:keypress) { |e|
467
+ prompt.trigger(:keyup) if e.value == "k"
468
+ prompt.trigger(:keydown) if e.value == "j"
469
+ prompt.trigger(:keyright) if e.value == "l"
470
+ prompt.trigger(:keyleft) if e.value == "h"
471
+ }
472
+ prompt.input << "j" << "l" << "k" << "k" << "h" << " " << "\r"
473
+ prompt.input.rewind
474
+
475
+ answer = prompt.multi_select("What number?", choices, default: 2, per_page: 4)
476
+
477
+ expect(answer).to eq(['2', '1'])
478
+
479
+ expected_output = [
480
+ output_helper("What number?", choices[0..3], "2", ["2"], init: true,
481
+ hint: "Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish"),
482
+ output_helper("What number?", choices[0..3], "3", ["2"]),
483
+ output_helper("What number?", choices[4..7], "7", ["2"]),
484
+ output_helper("What number?", choices[4..7], "6", ["2"]),
485
+ output_helper("What number?", choices[3..6], "5", ["2"]),
486
+ output_helper("What number?", choices[0..3], "1", ["2"]),
487
+ output_helper("What number?", choices[0..3], "1", ["2", "1"]),
488
+ "What number? \e[32m2, 1\e[0m\n\e[?25h",
489
+ ].join
490
+
491
+ expect(prompt.output.string).to eq(expected_output)
492
+ end
358
493
  end
359
494
 
360
495
  context "with :cycle" do
@@ -370,7 +505,7 @@ RSpec.describe TTY::Prompt do
370
505
 
371
506
  expect(prompt.output.string).to eq(
372
507
  output_helper("What letter?", choices, "A", [], init: true,
373
- hint: "Use arrow keys, press Space to select and Enter to finish") +
508
+ hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
374
509
  output_helper("What letter?", choices, "B", []) +
375
510
  output_helper("What letter?", choices, "C", []) +
376
511
  output_helper("What letter?", choices, "C", []) +
@@ -385,11 +520,13 @@ RSpec.describe TTY::Prompt do
385
520
  prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
386
521
  prompt.input << "j" << "j" << "j" << " " << "\r"
387
522
  prompt.input.rewind
523
+
388
524
  value = prompt.multi_select("What letter?", choices, cycle: true)
525
+
389
526
  expect(value).to eq(["A"])
390
527
  expect(prompt.output.string).to eq(
391
528
  output_helper("What letter?", choices, "A", [], init: true,
392
- hint: "Use arrow keys, press Space to select and Enter to finish") +
529
+ hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
393
530
  output_helper("What letter?", choices, "B", []) +
394
531
  output_helper("What letter?", choices, "C", []) +
395
532
  output_helper("What letter?", choices, "A", []) +
@@ -397,6 +534,64 @@ RSpec.describe TTY::Prompt do
397
534
  "What letter? \e[32mA\e[0m\n\e[?25h"
398
535
  )
399
536
  end
537
+
538
+ it "cycles choices using left/right arrows" do
539
+ prompt = TTY::TestPrompt.new
540
+ choices = ('1'..'10').to_a
541
+ prompt.on(:keypress) { |e|
542
+ prompt.trigger(:keyright) if e.value == "l"
543
+ prompt.trigger(:keyleft) if e.value == "h"
544
+ }
545
+ prompt.input << "l" << "l" << "l" << "h" << " " << "\r"
546
+ prompt.input.rewind
547
+
548
+ answer = prompt.multi_select("What number?", choices, default: 2, per_page: 4, cycle: true)
549
+
550
+ expect(answer).to eq(['2', '10'])
551
+
552
+ expected_output = [
553
+ "\e[?25lWhat number? 2 ",
554
+ "\e[90m(Use #{up_down} and #{left_right} arrow keys, press Space to select and Enter to finish)\e[0m\n",
555
+ " #{symbols[:radio_off]} 1\n",
556
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m 2\n",
557
+ " #{symbols[:radio_off]} 3\n",
558
+ " #{symbols[:radio_off]} 4",
559
+ "\e[2K\e[1G\e[1A" * 4,
560
+ "\e[2K\e[1G",
561
+ "What number? 2\n",
562
+ " #{symbols[:radio_off]} 5\n",
563
+ "#{symbols[:marker]} #{symbols[:radio_off]} 6\n",
564
+ " #{symbols[:radio_off]} 7\n",
565
+ " #{symbols[:radio_off]} 8",
566
+ "\e[2K\e[1G\e[1A" * 4,
567
+ "\e[2K\e[1G",
568
+ "What number? 2\n",
569
+ " #{symbols[:radio_off]} 9\n",
570
+ "#{symbols[:marker]} #{symbols[:radio_off]} 10",
571
+ "\e[2K\e[1G\e[1A" * 2,
572
+ "\e[2K\e[1G",
573
+ "What number? 2\n",
574
+ " #{symbols[:radio_off]} 1\n",
575
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m 2\n",
576
+ " #{symbols[:radio_off]} 3\n",
577
+ " #{symbols[:radio_off]} 4",
578
+ "\e[2K\e[1G\e[1A" * 4,
579
+ "\e[2K\e[1G",
580
+ "What number? 2\n",
581
+ " #{symbols[:radio_off]} 9\n",
582
+ "#{symbols[:marker]} #{symbols[:radio_off]} 10",
583
+ "\e[2K\e[1G\e[1A" * 2,
584
+ "\e[2K\e[1G",
585
+ "What number? 2, 10\n",
586
+ " #{symbols[:radio_off]} 9\n",
587
+ "#{symbols[:marker]} \e[32m#{symbols[:radio_on]}\e[0m 10",
588
+ "\e[2K\e[1G\e[1A" * 2,
589
+ "\e[2K\e[1G",
590
+ "What number? \e[32m2, 10\e[0m\n\e[?25h",
591
+ ].join
592
+
593
+ expect(prompt.output.string).to eq(expected_output)
594
+ end
400
595
  end
401
596
 
402
597
  context "with filter" do
@@ -413,7 +608,8 @@ RSpec.describe TTY::Prompt do
413
608
  expect(answer).to eql(%w(Tiny Large))
414
609
 
415
610
  expected_prompt_output =
416
- output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", %w(), init: true, hint: "Use arrow keys, press Space to select and Enter to finish, and letter keys to filter") +
611
+ output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", %w(), init: true,
612
+ hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish, and letter keys to filter") +
417
613
  output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", %w(Tiny)) +
418
614
  output_helper("What size?", %w(Large), "Large", %w(Tiny), hint: 'Filter: "a"') +
419
615
  output_helper("What size?", %w(Large), "Large", %w(Tiny Large), hint: 'Filter: "a"') +
@@ -467,7 +663,8 @@ RSpec.describe TTY::Prompt do
467
663
  expect(answer).to eq(%w[D E])
468
664
 
469
665
  expected_output =
470
- output_helper("What letter?", choices, "A", [], init: true, hint: "Use arrow keys, press Space to select and Enter to finish") +
666
+ output_helper("What letter?", choices, "A", [], init: true,
667
+ hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
471
668
  output_helper("What letter?", choices, "D", []) +
472
669
  output_helper("What letter?", choices, "D", %w[D]) +
473
670
  output_helper("What letter?", choices, "E", %w[D]) +
@@ -500,7 +697,8 @@ RSpec.describe TTY::Prompt do
500
697
  expect(answer).to eq([1])
501
698
 
502
699
  expected_output =
503
- output_helper("Select drinks?", choices, "vodka", [], init: true, enum: ') ', hint: "Use arrow or number (1-5) keys, press Space to select and Enter to finish") +
700
+ output_helper("Select drinks?", choices, "vodka", [], init: true, enum: ') ',
701
+ hint: "Use #{up_down} arrow or number (1-5) keys, press Space to select and Enter to finish") +
504
702
  output_helper("Select drinks?", choices, "vodka", [], enum: ') ') +
505
703
  output_helper("Select drinks?", choices, "vodka", %w[vodka], enum: ') ') +
506
704
  exit_message("Select drinks?", %w[vodka])
@@ -508,4 +706,36 @@ RSpec.describe TTY::Prompt do
508
706
  expect(prompt.output.string).to eq(expected_output)
509
707
  end
510
708
  end
709
+
710
+ context "with :max" do
711
+ it "limits number of choices" do
712
+ prompt = TTY::TestPrompt.new
713
+ choices = %w(A B C)
714
+ prompt.on(:keypress) { |e|
715
+ prompt.trigger(:keyup) if e.value == "k"
716
+ prompt.trigger(:keydown) if e.value == "j"
717
+ }
718
+ prompt.input << " " << "j" << " " << "j" << " " << "k" << " " << "j" << " " << "\r"
719
+ prompt.input.rewind
720
+
721
+ value = prompt.multi_select("What letter?", choices, max: 2, per_page: 100)
722
+ expect(value).to eq(["A", "C"])
723
+
724
+ expected_output =
725
+ output_helper("What letter?", choices, "A", [], init: true, max: 2,
726
+ hint: "Use #{up_down} arrow keys, press Space to select and Enter to finish") +
727
+ output_helper("What letter?", choices, "A", %w[A], max: 2) +
728
+ output_helper("What letter?", choices, "B", %w[A], max: 2) +
729
+ output_helper("What letter?", choices, "B", %w[A B], max: 2) +
730
+ output_helper("What letter?", choices, "C", %w[A B], max: 2) +
731
+ output_helper("What letter?", choices, "C", %w[A B], max: 2) +
732
+ output_helper("What letter?", choices, "B", %w[A B], max: 2) +
733
+ output_helper("What letter?", choices, "B", %w[A], max: 2) +
734
+ output_helper("What letter?", choices, "C", %w[A], max: 2) +
735
+ output_helper("What letter?", choices, "C", %w[A C], max: 2) +
736
+ exit_message("What letter?", %w[A C])
737
+
738
+ expect(prompt.output.string).to eq(expected_output)
739
+ end
740
+ end
511
741
  end