tty-prompt 0.16.1 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/tty/prompt/messages.rb +49 -0
  4. data/lib/tty/prompt/version.rb +1 -3
  5. data/spec/spec_helper.rb +45 -0
  6. data/spec/unit/ask_spec.rb +132 -0
  7. data/spec/unit/choice/eql_spec.rb +22 -0
  8. data/spec/unit/choice/from_spec.rb +96 -0
  9. data/spec/unit/choices/add_spec.rb +12 -0
  10. data/spec/unit/choices/each_spec.rb +13 -0
  11. data/spec/unit/choices/find_by_spec.rb +10 -0
  12. data/spec/unit/choices/new_spec.rb +10 -0
  13. data/spec/unit/choices/pluck_spec.rb +9 -0
  14. data/spec/unit/collect_spec.rb +96 -0
  15. data/spec/unit/converters/convert_bool_spec.rb +58 -0
  16. data/spec/unit/converters/convert_char_spec.rb +11 -0
  17. data/spec/unit/converters/convert_custom_spec.rb +14 -0
  18. data/spec/unit/converters/convert_date_spec.rb +34 -0
  19. data/spec/unit/converters/convert_file_spec.rb +18 -0
  20. data/spec/unit/converters/convert_number_spec.rb +39 -0
  21. data/spec/unit/converters/convert_path_spec.rb +15 -0
  22. data/spec/unit/converters/convert_range_spec.rb +22 -0
  23. data/spec/unit/converters/convert_regex_spec.rb +12 -0
  24. data/spec/unit/converters/convert_string_spec.rb +21 -0
  25. data/spec/unit/converters/on_error_spec.rb +9 -0
  26. data/spec/unit/distance/distance_spec.rb +73 -0
  27. data/spec/unit/enum_paginator_spec.rb +75 -0
  28. data/spec/unit/enum_select_spec.rb +446 -0
  29. data/spec/unit/error_spec.rb +20 -0
  30. data/spec/unit/evaluator_spec.rb +67 -0
  31. data/spec/unit/expand_spec.rb +198 -0
  32. data/spec/unit/keypress_spec.rb +72 -0
  33. data/spec/unit/mask_spec.rb +132 -0
  34. data/spec/unit/multi_select_spec.rb +495 -0
  35. data/spec/unit/multiline_spec.rb +77 -0
  36. data/spec/unit/new_spec.rb +20 -0
  37. data/spec/unit/ok_spec.rb +10 -0
  38. data/spec/unit/paginator_spec.rb +73 -0
  39. data/spec/unit/question/checks_spec.rb +97 -0
  40. data/spec/unit/question/default_spec.rb +31 -0
  41. data/spec/unit/question/echo_spec.rb +38 -0
  42. data/spec/unit/question/in_spec.rb +115 -0
  43. data/spec/unit/question/initialize_spec.rb +12 -0
  44. data/spec/unit/question/modifier/apply_to_spec.rb +24 -0
  45. data/spec/unit/question/modifier/letter_case_spec.rb +41 -0
  46. data/spec/unit/question/modifier/whitespace_spec.rb +51 -0
  47. data/spec/unit/question/modify_spec.rb +41 -0
  48. data/spec/unit/question/required_spec.rb +92 -0
  49. data/spec/unit/question/validate_spec.rb +115 -0
  50. data/spec/unit/question/validation/call_spec.rb +31 -0
  51. data/spec/unit/question/validation/coerce_spec.rb +30 -0
  52. data/spec/unit/result_spec.rb +40 -0
  53. data/spec/unit/say_spec.rb +67 -0
  54. data/spec/unit/select_spec.rb +624 -0
  55. data/spec/unit/slider_spec.rb +100 -0
  56. data/spec/unit/statement/initialize_spec.rb +15 -0
  57. data/spec/unit/subscribe_spec.rb +20 -0
  58. data/spec/unit/suggest_spec.rb +28 -0
  59. data/spec/unit/warn_spec.rb +21 -0
  60. data/spec/unit/yes_no_spec.rb +235 -0
  61. data/tty-prompt.gemspec +4 -6
  62. metadata +120 -15
  63. data/.gitignore +0 -16
  64. data/.rspec +0 -3
  65. data/.travis.yml +0 -27
  66. data/CHANGELOG.md +0 -289
  67. data/CODE_OF_CONDUCT.md +0 -49
  68. data/Gemfile +0 -21
  69. data/appveyor.yml +0 -22
  70. data/benchmarks/speed.rb +0 -27
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Result do
4
+ it "checks value to be invalid" do
5
+ question = double(:question)
6
+ result = TTY::Prompt::Result.new(question, nil)
7
+
8
+ answer = result.with { |quest, value|
9
+ if value.nil?
10
+ [value, ["`#{value}` provided cannot be empty"]]
11
+ else
12
+ value
13
+ end
14
+ }
15
+ expect(answer).to be_a(TTY::Prompt::Result::Failure)
16
+ expect(answer.success?).to eq(false)
17
+ expect(answer.errors).to eq(["`` provided cannot be empty"])
18
+ end
19
+
20
+ it "checks value to be valid" do
21
+ question = double(:question)
22
+ result = TTY::Prompt::Result.new(question, 'Piotr')
23
+
24
+ CheckRequired = Class.new do
25
+ def self.call(quest, value)
26
+ if value.nil?
27
+ [value, ["`#{value}` provided cannot be empty"]]
28
+ else
29
+ value
30
+ end
31
+ end
32
+ end
33
+
34
+ answer = result.with(CheckRequired)
35
+ expect(answer).to be_a(TTY::Prompt::Result::Success)
36
+ expect(answer.success?).to eq(true)
37
+ expect(answer.value).to eq('Piotr')
38
+ expect(answer.errors).to eq([])
39
+ end
40
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, '#say' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it 'prints an empty message' do
8
+ prompt.say('')
9
+ expect(prompt.output.string).to eq('')
10
+ end
11
+
12
+ context 'with new line' do
13
+ it 'prints a message with newline' do
14
+ prompt.say("Hell yeah!\n")
15
+ expect(prompt.output.string).to eq("Hell yeah!\n")
16
+ end
17
+
18
+ it 'prints a message with implicit newline' do
19
+ prompt.say("Hell yeah!\n")
20
+ expect(prompt.output.string).to eq("Hell yeah!\n")
21
+ end
22
+
23
+ it 'prints a message with newline within text' do
24
+ prompt.say("Hell\n yeah!")
25
+ expect(prompt.output.string).to eq("Hell\n yeah!\n")
26
+ end
27
+
28
+ it 'prints a message with newline within text and blank space' do
29
+ prompt.say("Hell\n yeah! ")
30
+ expect(prompt.output.string).to eq("Hell\n yeah! ")
31
+ end
32
+
33
+ it 'prints a message without newline' do
34
+ prompt.say("Hell yeah!", newline: false)
35
+ expect(prompt.output.string).to eq("Hell yeah!")
36
+ end
37
+ end
38
+
39
+ context 'with tab or space' do
40
+ it 'prints ' do
41
+ prompt.say("Hell yeah!\t")
42
+ expect(prompt.output.string).to eq("Hell yeah!\t")
43
+ end
44
+ end
45
+
46
+ context 'with color' do
47
+ it 'prints message with ansi color' do
48
+ prompt.say('Hell yeah!', color: :green)
49
+ expect(prompt.output.string).to eq("\e[32mHell yeah!\e[0m\n")
50
+ end
51
+
52
+ it 'prints message with ansi color without newline' do
53
+ prompt.say('Hell yeah! ', color: :green)
54
+ expect(prompt.output.string).to eq("\e[32mHell yeah! \e[0m")
55
+ end
56
+ end
57
+
58
+ context 'without color' do
59
+ it 'prints message without ansi' do
60
+ prompt = TTY::TestPrompt.new(enable_color: false)
61
+
62
+ prompt.say('Hell yeah!', color: :green)
63
+
64
+ expect(prompt.output.string).to eq("Hell yeah!\n")
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,624 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, '#select' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ let(:symbols) { TTY::Prompt::Symbols.symbols }
8
+
9
+ def output_helper(prompt, choices, active, options = {})
10
+ raise ":init requires :hint" if options[:init] && options[:hint].nil?
11
+ hint = options[:hint]
12
+ init = options.fetch(:init, false)
13
+ enum = options[:enum]
14
+
15
+ out = ""
16
+ out << "\e[?25l" if init
17
+ out << prompt << " "
18
+ out << "\e[90m(#{hint})\e[0m" if hint
19
+ out << "\n"
20
+ out << choices.map.with_index do |c, i|
21
+ name = c.is_a?(Hash) ? c[:name] : c
22
+ disabled = c.is_a?(Hash) ? c[:disabled] : false
23
+ num = (i + 1).to_s + enum if enum
24
+ if disabled
25
+ "\e[31m#{symbols[:cross]}\e[0m #{num}#{name} #{disabled}"
26
+ elsif name == active
27
+ "\e[32m#{symbols[:pointer]} #{num}#{name}\e[0m"
28
+ else
29
+ " #{num}#{name}"
30
+ end
31
+ end.join("\n")
32
+ out << "\e[2K\e[1G\e[1A" * choices.count
33
+ out << "\e[2K\e[1G"
34
+ out << "\e[1A\e[2K\e[1G" if choices.empty?
35
+ out
36
+ end
37
+
38
+ def exit_message(prompt, choice)
39
+ "#{prompt} \e[32m#{choice}\e[0m\n\e[?25h"
40
+ end
41
+
42
+ # Ensure a wide prompt on CI
43
+ before { allow(TTY::Screen).to receive(:width).and_return(200) }
44
+
45
+ it "selects by default first option" do
46
+ choices = %w(Large Medium Small)
47
+ prompt.input << "\r"
48
+ prompt.input.rewind
49
+ expect(prompt.select('What size?', choices)).to eq('Large')
50
+ expect(prompt.output.string).to eq([
51
+ "\e[?25lWhat size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
52
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
53
+ " Medium\n",
54
+ " Small",
55
+ "\e[2K\e[1G\e[1A" * 3,
56
+ "\e[2K\e[1G",
57
+ "What size? \e[32mLarge\e[0m\n\e[?25h"
58
+ ].join)
59
+ end
60
+
61
+ it "allows navigation using events without errors" do
62
+ choices = %w(Large Medium Small)
63
+ prompt.input << "j" << "\r"
64
+ prompt.input.rewind
65
+ prompt.on(:keypress) do |event|
66
+ prompt.trigger(:keydown) if event.value == "j"
67
+ end
68
+ expect { prompt.select('What size?', choices) }.not_to output.to_stderr
69
+ expect(prompt.output.string).to eq([
70
+ "\e[?25lWhat size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
71
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
72
+ " Medium\n",
73
+ " Small",
74
+ "\e[2K\e[1G\e[1A" * 3,
75
+ "\e[2K\e[1G",
76
+ "What size? \n",
77
+ " Large\n",
78
+ "\e[32m#{symbols[:pointer]} Medium\e[0m\n",
79
+ " Small",
80
+ "\e[2K\e[1G\e[1A" * 3,
81
+ "\e[2K\e[1G",
82
+ "What size? \e[32mMedium\e[0m\n\e[?25h"
83
+ ].join)
84
+ end
85
+
86
+ it "sets choice name and value" do
87
+ choices = {large: 1, medium: 2, small: 3}
88
+ prompt.input << " "
89
+ prompt.input.rewind
90
+ expect(prompt.select('What size?', choices, default: 1)).to eq(1)
91
+ expect(prompt.output.string).to eq([
92
+ "\e[?25lWhat size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
93
+ "\e[32m#{symbols[:pointer]} large\e[0m\n",
94
+ " medium\n",
95
+ " small",
96
+ "\e[2K\e[1G\e[1A" * 3,
97
+ "\e[2K\e[1G",
98
+ "What size? \e[32mlarge\e[0m\n\e[?25h"
99
+ ].join)
100
+ end
101
+
102
+ it "sets choice name through DSL" do
103
+ prompt.input << " "
104
+ prompt.input.rewind
105
+ value = prompt.select('What size?') do |menu|
106
+ menu.choice "Large"
107
+ menu.choice "Medium"
108
+ menu.choice "Small"
109
+ end
110
+ expect(value).to eq('Large')
111
+ expect(prompt.output.string).to eq([
112
+ "\e[?25lWhat size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
113
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
114
+ " Medium\n",
115
+ " Small",
116
+ "\e[2K\e[1G\e[1A" * 3,
117
+ "\e[2K\e[1G",
118
+ "What size? \e[32mLarge\e[0m\n\e[?25h"
119
+ ].join)
120
+ end
121
+
122
+ it "sets choice name & value through DSL" do
123
+ prompt.input << " "
124
+ prompt.input.rewind
125
+ value = prompt.select('What size?') do |menu|
126
+ menu.choice :large, 1
127
+ menu.choice :medium, 2
128
+ menu.choice :small, 3
129
+ end
130
+ expect(value).to eq(1)
131
+ expect(prompt.output.string).to eq([
132
+ "\e[?25lWhat size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
133
+ "\e[32m#{symbols[:pointer]} large\e[0m\n",
134
+ " medium\n",
135
+ " small",
136
+ "\e[2K\e[1G\e[1A" * 3,
137
+ "\e[2K\e[1G",
138
+ "What size? \e[32mlarge\e[0m\n\e[?25h"
139
+ ].join)
140
+ end
141
+
142
+ it "sets choices and single choice through DSL" do
143
+ prompt.input << " "
144
+ prompt.input.rewind
145
+ value = prompt.select('What size?') do |menu|
146
+ menu.choice 'Large'
147
+ menu.choices %w(Medium Small)
148
+ end
149
+ expect(value).to eq('Large')
150
+ expect(prompt.output.string).to eq([
151
+ "\e[?25lWhat size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
152
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
153
+ " Medium\n",
154
+ " Small",
155
+ "\e[2K\e[1G\e[1A" * 3,
156
+ "\e[2K\e[1G",
157
+ "What size? \e[32mLarge\e[0m\n\e[?25h"
158
+ ].join)
159
+ end
160
+
161
+ it "sets choice name & value through DSL" do
162
+ prompt.input << " "
163
+ prompt.input.rewind
164
+ value = prompt.select('What size?') do |menu|
165
+ menu.default 2
166
+ menu.enum '.'
167
+
168
+ menu.choice :large, 1
169
+ menu.choice :medium, 2
170
+ menu.choice :small, 3
171
+ end
172
+ expect(value).to eq(2)
173
+ expect(prompt.output.string).to eq([
174
+ "\e[?25lWhat size? \e[90m(Use arrow or number (1-3) keys, press Enter to select)\e[0m\n",
175
+ " 1. large\n",
176
+ "\e[32m#{symbols[:pointer]} 2. medium\e[0m\n",
177
+ " 3. small",
178
+ "\e[2K\e[1G\e[1A" * 3,
179
+ "\e[2K\e[1G",
180
+ "What size? \e[32mmedium\e[0m\n\e[?25h"
181
+ ].join)
182
+ end
183
+
184
+ it "sets choice value to proc and executes it" do
185
+ prompt.input << " "
186
+ prompt.input.rewind
187
+ value = prompt.select('What size?', default: 2, enum: ')') do |menu|
188
+ menu.choice :large, 1
189
+ menu.choice :medium do 'Good choice!' end
190
+ menu.choice :small, 3
191
+ end
192
+ expect(value).to eq('Good choice!')
193
+ expect(prompt.output.string).to eq([
194
+ "\e[?25lWhat size? \e[90m(Use arrow or number (1-3) keys, press Enter to select)\e[0m\n",
195
+ " 1) large\n",
196
+ "\e[32m#{symbols[:pointer]} 2) medium\e[0m\n",
197
+ " 3) small",
198
+ "\e[2K\e[1G\e[1A" * 3,
199
+ "\e[2K\e[1G",
200
+ "What size? \e[32mmedium\e[0m\n\e[?25h"
201
+ ].join)
202
+ end
203
+
204
+ it "sets default option through hash syntax" do
205
+ choices = %w(Large Medium Small)
206
+ prompt.input << " "
207
+ prompt.input.rewind
208
+ expect(prompt.select('What size?', choices, default: 2, enum: '.')).to eq('Medium')
209
+ expect(prompt.output.string).to eq([
210
+ "\e[?25lWhat size? \e[90m(Use arrow or number (1-3) keys, press Enter to select)\e[0m\n",
211
+ " 1. Large\n",
212
+ "\e[32m#{symbols[:pointer]} 2. Medium\e[0m\n",
213
+ " 3. Small",
214
+ "\e[2K\e[1G\e[1A" * 3,
215
+ "\e[2K\e[1G",
216
+ "What size? \e[32mMedium\e[0m\n\e[?25h"
217
+ ].join)
218
+ end
219
+
220
+ it "changes selected item color & marker" do
221
+ choices = %w(Large Medium Small)
222
+ prompt.input << " "
223
+ prompt.input.rewind
224
+ options = {active_color: :blue, help_color: :red, marker: '>'}
225
+ value = prompt.select('What size?', choices, options)
226
+ expect(value).to eq('Large')
227
+ expect(prompt.output.string).to eq([
228
+ "\e[?25lWhat size? \e[31m(Use arrow keys, press Enter to select)\e[0m\n",
229
+ "\e[34m> Large\e[0m\n",
230
+ " Medium\n",
231
+ " Small",
232
+ "\e[2K\e[1G\e[1A" * 3,
233
+ "\e[2K\e[1G",
234
+ "What size? \e[34mLarge\e[0m\n\e[?25h"
235
+ ].join)
236
+ end
237
+
238
+ it "changes help text" do
239
+ choices = %w(Large Medium Small)
240
+ prompt.input << " "
241
+ prompt.input.rewind
242
+ value = prompt.select('What size?', choices, help: "(Bash keyboard)")
243
+ expect(value).to eq('Large')
244
+ expect(prompt.output.string).to eq([
245
+ "\e[?25lWhat size? \e[90m(Bash keyboard)\e[0m\n",
246
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
247
+ " Medium\n",
248
+ " Small",
249
+ "\e[2K\e[1G\e[1A" * 3,
250
+ "\e[2K\e[1G",
251
+ "What size? \e[32mLarge\e[0m\n\e[?25h"
252
+ ].join)
253
+ end
254
+
255
+ it "changes help text through DSL" do
256
+ choices = %w(Large Medium Small)
257
+ prompt.input << " "
258
+ prompt.input.rewind
259
+ value = prompt.select('What size?') do |menu|
260
+ menu.help "(Bash keyboard)"
261
+ menu.choices choices
262
+ end
263
+ expect(value).to eq('Large')
264
+ expect(prompt.output.string).to eq([
265
+ "\e[?25lWhat size? \e[90m(Bash keyboard)\e[0m\n",
266
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
267
+ " Medium\n",
268
+ " Small",
269
+ "\e[2K\e[1G\e[1A" * 3,
270
+ "\e[2K\e[1G",
271
+ "What size? \e[32mLarge\e[0m\n\e[?25h"
272
+ ].join)
273
+ end
274
+
275
+ it "sets prompt prefix" do
276
+ prompt = TTY::TestPrompt.new(prefix: '[?] ')
277
+ choices = %w(Large Medium Small)
278
+ prompt.input << "\r"
279
+ prompt.input.rewind
280
+ expect(prompt.select('What size?', choices)).to eq('Large')
281
+ expect(prompt.output.string).to eq([
282
+ "\e[?25l[?] What size? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
283
+ "\e[32m#{symbols[:pointer]} Large\e[0m\n",
284
+ " Medium\n",
285
+ " Small",
286
+ "\e[2K\e[1G\e[1A" * 3,
287
+ "\e[2K\e[1G",
288
+ "[?] What size? \e[32mLarge\e[0m\n\e[?25h"
289
+ ].join)
290
+ end
291
+
292
+ it "paginates long selections" do
293
+ choices = %w(A B C D E F G H)
294
+ prompt.input << "\r"
295
+ prompt.input.rewind
296
+ value = prompt.select("What letter?", choices, per_page: 3, default: 4)
297
+ expect(value).to eq('D')
298
+ expect(prompt.output.string).to eq([
299
+ "\e[?25lWhat letter? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
300
+ "\e[32m#{symbols[:pointer]} D\e[0m\n",
301
+ " E\n",
302
+ " F\n",
303
+ "\e[90m(Move up or down to reveal more choices)\e[0m",
304
+ "\e[2K\e[1G\e[1A" * 4,
305
+ "\e[2K\e[1G",
306
+ "What letter? \e[32mD\e[0m\n\e[?25h",
307
+ ].join)
308
+ end
309
+
310
+ it "paginates choices as hash object" do
311
+ prompt = TTY::TestPrompt.new
312
+ choices = {A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8}
313
+ prompt.input << "\r"
314
+ prompt.input.rewind
315
+ value = prompt.select("What letter?", choices, per_page: 3, default: 4)
316
+ expect(value).to eq(4)
317
+ expect(prompt.output.string).to eq([
318
+ "\e[?25lWhat letter? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
319
+ "\e[32m#{symbols[:pointer]} D\e[0m\n",
320
+ " E\n",
321
+ " F\n",
322
+ "\e[90m(Move up or down to reveal more choices)\e[0m",
323
+ "\e[2K\e[1G\e[1A" * 4,
324
+ "\e[2K\e[1G",
325
+ "What letter? \e[32mD\e[0m\n\e[?25h",
326
+ ].join)
327
+ end
328
+
329
+ it "paginates long selections through DSL" do
330
+ prompt = TTY::TestPrompt.new
331
+ choices = %w(A B C D E F G H)
332
+ prompt.input << "\r"
333
+ prompt.input.rewind
334
+ value = prompt.select("What letter?") do |menu|
335
+ menu.per_page 3
336
+ menu.page_help '(Wiggle thy finger up or down to see more)'
337
+ menu.default 4
338
+
339
+ menu.choices choices
340
+ end
341
+ expect(value).to eq('D')
342
+ expect(prompt.output.string).to eq([
343
+ "\e[?25lWhat letter? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
344
+ "\e[32m#{symbols[:pointer]} D\e[0m\n",
345
+ " E\n",
346
+ " F\n",
347
+ "\e[90m(Wiggle thy finger up or down to see more)\e[0m",
348
+ "\e[2K\e[1G\e[1A" * 4,
349
+ "\e[2K\e[1G",
350
+ "What letter? \e[32mD\e[0m\n\e[?25h",
351
+ ].join)
352
+ end
353
+
354
+ context 'with :cycle option' do
355
+ it "doesn't cycle by default" do
356
+ prompt = TTY::TestPrompt.new
357
+ choices = %w(A B C)
358
+ prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
359
+ prompt.input << "j" << "j" << "j" << "\r"
360
+ prompt.input.rewind
361
+ value = prompt.select("What letter?", choices)
362
+ expect(value).to eq("C")
363
+ expect(prompt.output.string).to eq(
364
+ output_helper("What letter?", choices, "A", init: true, hint: "Use arrow keys, press Enter to select") +
365
+ output_helper("What letter?", choices, "B") +
366
+ output_helper("What letter?", choices, "C") +
367
+ output_helper("What letter?", choices, "C") +
368
+ "What letter? \e[32mC\e[0m\n\e[?25h"
369
+ )
370
+ end
371
+
372
+ it "cycles around when configured to do so" do
373
+ prompt = TTY::TestPrompt.new
374
+ choices = %w(A B C)
375
+ prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
376
+ prompt.input << "j" << "j" << "j" << "\r"
377
+ prompt.input.rewind
378
+ value = prompt.select("What letter?", choices, cycle: true)
379
+ expect(value).to eq("A")
380
+ expect(prompt.output.string).to eq(
381
+ output_helper("What letter?", choices, "A", init: true, hint: "Use arrow keys, press Enter to select") +
382
+ output_helper("What letter?", choices, "B") +
383
+ output_helper("What letter?", choices, "C") +
384
+ output_helper("What letter?", choices, "A") +
385
+ "What letter? \e[32mA\e[0m\n\e[?25h"
386
+ )
387
+ end
388
+
389
+ it "cycles around disabled items" do
390
+ prompt = TTY::TestPrompt.new
391
+ choices = [
392
+ {name: 'A', disabled: '(out)'},
393
+ {name: 'B'},
394
+ {name: 'C', disabled: '(out)'},
395
+ {name: 'D'},
396
+ {name: 'E', disabled: '(out)'},
397
+ ]
398
+ prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
399
+ prompt.input << "j" << "j" << "j" << "\r"
400
+ prompt.input.rewind
401
+ value = prompt.select("What letter?", choices, cycle: true, default: 2)
402
+ expect(value).to eq("D")
403
+
404
+ expected_output =
405
+ output_helper("What letter?", choices, "B", init: true,
406
+ hint: "Use arrow keys, press Enter to select") +
407
+ output_helper("What letter?", choices, "D") +
408
+ output_helper("What letter?", choices, "B") +
409
+ output_helper("What letter?", choices, "D") +
410
+ "What letter? \e[32mD\e[0m\n\e[?25h"
411
+
412
+ expect(prompt.output.string).to eq(expected_output)
413
+ end
414
+ end
415
+
416
+ it "verifies default index format" do
417
+ prompt = TTY::TestPrompt.new
418
+ choices = %w(Large Medium Small)
419
+ prompt.input << "\r"
420
+ prompt.input.rewind
421
+
422
+ expect {
423
+ prompt.select('What size?', choices, default: '')
424
+ }.to raise_error(TTY::Prompt::ConfigurationError, /in range \(1 - 3\)/)
425
+ end
426
+
427
+ it "doesn't paginate short selections" do
428
+ prompt = TTY::TestPrompt.new
429
+ choices = %w(A B C D)
430
+ prompt.input << "\r"
431
+ prompt.input.rewind
432
+ value = prompt.select("What letter?", choices, per_page: 4, default: 1)
433
+ expect(value).to eq('A')
434
+
435
+ expect(prompt.output.string).to eq([
436
+ "\e[?25lWhat letter? \e[90m(Use arrow keys, press Enter to select)\e[0m\n",
437
+ "\e[32m#{symbols[:pointer]} A\e[0m\n",
438
+ " B\n",
439
+ " C\n",
440
+ " D",
441
+ "\e[2K\e[1G\e[1A" * 4,
442
+ "\e[2K\e[1G",
443
+ "What letter? \e[32mA\e[0m\n\e[?25h",
444
+ ].join)
445
+ end
446
+
447
+ it "verifies default index range" do
448
+ prompt = TTY::TestPrompt.new
449
+ choices = %w(Large Medium Small)
450
+ prompt.input << "\r"
451
+ prompt.input.rewind
452
+
453
+ expect {
454
+ prompt.select("What size?", choices, default: 10)
455
+ }.to raise_error(TTY::Prompt::ConfigurationError, /`10` out of range \(1 - 3\)/)
456
+ end
457
+
458
+ context "with filter" do
459
+ it "doesn't allow mixing enumeration and filter" do
460
+ prompt = TTY::TestPrompt.new
461
+
462
+ expect {
463
+ prompt.select("What size?", [], enum: '.', filter: true)
464
+ }.to raise_error(TTY::Prompt::ConfigurationError, "Enumeration can't be used with filter")
465
+ end
466
+
467
+ it "filters and chooses a uniquely matching entry, ignoring case" do
468
+ prompt = TTY::TestPrompt.new
469
+ prompt.input << "U" << "g" << "\r"
470
+ prompt.input.rewind
471
+
472
+ answer = prompt.select("What size?", %w(Small Medium Large Huge), filter: true)
473
+ expect(answer).to eql("Huge")
474
+
475
+ actual_prompt_output = prompt.output.string
476
+
477
+ expected_prompt_output =
478
+ output_helper("What size?", %w(Small Medium Large Huge), "Small", init: true, hint: "Use arrow keys, press Enter to select, and letter keys to filter") +
479
+ output_helper("What size?", %w(Medium Huge), "Medium", hint: 'Filter: "U"') +
480
+ output_helper("What size?", %w(Huge), "Huge", hint: 'Filter: "Ug"') +
481
+ exit_message("What size?", "Huge")
482
+
483
+ expect(actual_prompt_output).to eql(expected_prompt_output)
484
+ end
485
+
486
+ it "filters and chooses the first of multiple matching entries" do
487
+ prompt = TTY::TestPrompt.new
488
+ prompt.input << "g" << "\r"
489
+ prompt.input.rewind
490
+
491
+ answer = prompt.select("What size?", %w(Small Medium Large Huge), filter: true)
492
+ expect(answer).to eql("Large")
493
+
494
+ actual_prompt_output = prompt.output.string
495
+ expected_prompt_output =
496
+ output_helper("What size?", %w(Small Medium Large Huge), "Small", init: true, hint: "Use arrow keys, press Enter to select, and letter keys to filter") +
497
+ output_helper("What size?", %w(Large Huge), "Large", hint: 'Filter: "g"') +
498
+ exit_message("What size?", "Large")
499
+
500
+ expect(actual_prompt_output).to eql(expected_prompt_output)
501
+ end
502
+
503
+ # This test can't be done in an exact way, at least, with the current framework
504
+ it "doesn't exit when there are no matching entries" do
505
+ prompt = TTY::TestPrompt.new
506
+ prompt.on(:keypress) { |e| prompt.trigger(:keybackspace) if e.value == "a" }
507
+ prompt.input << "z" << "\r" # shows no entry, blocking exit
508
+ prompt.input << "a" << "\r" # triggers Backspace before `a` (see above)
509
+ prompt.input.rewind
510
+
511
+ answer = prompt.select("What size?", %w(Tiny Medium Large Huge), filter: true)
512
+ expect(answer).to eql("Large")
513
+
514
+ actual_prompt_output = prompt.output.string
515
+ expected_prompt_output =
516
+ output_helper("What size?", %w(Tiny Medium Large Huge), "Tiny", init: true, hint: "Use arrow keys, press Enter to select, and letter keys to filter") +
517
+ output_helper("What size?", %w(), "", hint: 'Filter: "z"') +
518
+ output_helper("What size?", %w(), "", hint: 'Filter: "z"') +
519
+ output_helper("What size?", %w(Large), "Large", hint: 'Filter: "a"') +
520
+ exit_message("What size?", "Large")
521
+
522
+ expect(actual_prompt_output).to eql(expected_prompt_output)
523
+ end
524
+
525
+ it "cancels a selection" do
526
+ prompt = TTY::TestPrompt.new
527
+ prompt.on(:keypress) { |e| prompt.trigger(:keydelete) if e.value == "S" }
528
+ prompt.input << "Hu"
529
+ prompt.input << "S" # triggers Canc before `S` (see above)
530
+ prompt.input << "\r"
531
+ prompt.input.rewind
532
+
533
+ answer = prompt.select("What size?", %w(Small Medium Large Huge), filter: true)
534
+ expect(answer).to eql("Small")
535
+
536
+ expected_prompt_output =
537
+ output_helper("What size?", %w(Small Medium Large Huge), "Small", init: true, hint: "Use arrow keys, press Enter to select, and letter keys to filter") +
538
+ output_helper("What size?", %w(Huge), "Huge", hint: 'Filter: "H"') +
539
+ output_helper("What size?", %w(Huge), "Huge", hint: 'Filter: "Hu"') +
540
+ output_helper("What size?", %w(Small), "Small", hint: 'Filter: "S"') +
541
+ exit_message("What size?", "Small")
542
+
543
+ expect(prompt.output.string).to eql(expected_prompt_output)
544
+ end
545
+ end
546
+
547
+ context 'with :disabled choice' do
548
+ it "omits disabled choice when navigating menu" do
549
+ choices = [ 'Small', 'Medium', {name: 'Large', disabled: '(out of stock)'}, 'Huge' ]
550
+ prompt = TTY::TestPrompt.new
551
+ prompt.input << "j" << "j" << "\r"
552
+ prompt.input.rewind
553
+ prompt.on(:keypress) { |e| prompt.trigger(:keydown) if e.value == "j" }
554
+
555
+ answer = prompt.select("What size?", choices)
556
+ expect(answer).to eq('Huge')
557
+
558
+ expected_output =
559
+ output_helper("What size?", choices, "Small", init: true,
560
+ hint: "Use arrow keys, press Enter to select") +
561
+ output_helper("What size?", choices, "Medium") +
562
+ output_helper("What size?", choices, "Huge") +
563
+ "What size? \e[32mHuge\e[0m\n\e[?25h"
564
+
565
+ expect(prompt.output.string).to eq(expected_output)
566
+ end
567
+
568
+ it "doesn't show disabled choice when filtering choices" do
569
+ choices = [ 'A', 'B', {name: 'C', disabled: '(unavailable)'}, 'D' ]
570
+ prompt = TTY::TestPrompt.new
571
+ prompt.on(:keypress) { |e| prompt.trigger(:keybackspace) if e.value == "a" }
572
+ prompt.input << "c" << "\r" # nothing matches
573
+ prompt.input << "a" << "\r" # backtracks & chooses default option
574
+ prompt.input.rewind
575
+
576
+ answer = prompt.select("What letter?", choices, filter: true)
577
+ expect(answer).to eq('A')
578
+
579
+ expected_output =
580
+ output_helper("What letter?", choices, "A", init: true,
581
+ hint: "Use arrow keys, press Enter to select, and letter keys to filter") +
582
+ output_helper("What letter?", [], "", hint: 'Filter: "c"') +
583
+ output_helper("What letter?", [], "", hint: 'Filter: "c"') +
584
+ output_helper("What letter?", ['A'], "A", hint: 'Filter: "a"') +
585
+ exit_message("What letter?", "A")
586
+
587
+ expect(prompt.output.string).to eq(expected_output)
588
+ end
589
+
590
+ it "omits disabled choice when number key is pressed" do
591
+ choices = [ 'Small', {name: 'Medium', disabled: '(out of stock)'}, 'Large' ]
592
+ prompt = TTY::TestPrompt.new
593
+ prompt.input << "2" << "\r" << "\r"
594
+ prompt.input.rewind
595
+ answer = prompt.select('What size?') do |menu|
596
+ menu.enum ')'
597
+
598
+ menu.choice 'Small', 1
599
+ menu.choice 'Medium', 2, disabled: '(out of stock)'
600
+ menu.choice 'Large', 3
601
+ end
602
+ expect(answer).to eq(1)
603
+
604
+ expected_output =
605
+ output_helper("What size?", choices, "Small", init: true, enum: ') ',
606
+ hint: "Use arrow or number (1-3) keys, press Enter to select") +
607
+ output_helper("What size?", choices, "Small", enum: ') ') +
608
+ "What size? \e[32mSmall\e[0m\n\e[?25h"
609
+
610
+ expect(prompt.output.string).to eq(expected_output)
611
+ end
612
+
613
+ it "sets default correct when disabled choice" do
614
+ choices = [ {name: 'Small', disabled: '(out of stock)'}, 'Medium', 'Large', 'Huge' ]
615
+ prompt = TTY::TestPrompt.new
616
+ prompt.input << "\r"
617
+ prompt.input.rewind
618
+
619
+ expect {
620
+ prompt.select("What size?", choices)
621
+ }.to raise_error(TTY::Prompt::ConfigurationError, /default index `1` matches disabled choice item/)
622
+ end
623
+ end
624
+ end