tty-prompt 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
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