tty-prompt 0.16.1 → 0.17.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 (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