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,77 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, '#multiline' do
4
+ it 'reads no lines' do
5
+ prompt = TTY::TestPrompt.new
6
+ prompt.input << "\C-d"
7
+ prompt.input.rewind
8
+
9
+ answer = prompt.multiline("Description?")
10
+
11
+ expect(answer).to eq([])
12
+ expect(prompt.output.string).to eq([
13
+ "Description? \e[90m(Press CTRL-D or CTRL-Z to finish)\e[0m\n",
14
+ "\e[2K\e[1G\e[1A\e[2K\e[1G",
15
+ "Description? \n"
16
+ ].join)
17
+ end
18
+
19
+ it "uses defualt when no input" do
20
+ prompt = TTY::TestPrompt.new
21
+ prompt.input << "\C-d"
22
+ prompt.input.rewind
23
+
24
+ answer = prompt.multiline("Description?", default: 'A super sweet prompt')
25
+
26
+ expect(answer).to eq([])
27
+ expect(prompt.output.string).to eq([
28
+ "Description? \e[90m(Press CTRL-D or CTRL-Z to finish)\e[0m\n",
29
+ "\e[2K\e[1G\e[1A\e[2K\e[1G",
30
+ "Description? \e[32mA super sweet prompt\e[0m\n"
31
+ ].join)
32
+ end
33
+
34
+ it "changes help text" do
35
+ prompt = TTY::TestPrompt.new
36
+ prompt.input << "\C-d"
37
+ prompt.input.rewind
38
+
39
+ answer = prompt.multiline("Description?") do |q|
40
+ q.default 'A super sweet prompt'
41
+ q.help '(Press thy ctrl-d to end)'
42
+ end
43
+
44
+ expect(answer).to eq([])
45
+ expect(prompt.output.string).to eq([
46
+ "Description? \e[90m(Press thy ctrl-d to end)\e[0m\n",
47
+ "\e[2K\e[1G\e[1A\e[2K\e[1G",
48
+ "Description? \e[32mA super sweet prompt\e[0m\n"
49
+ ].join)
50
+ end
51
+
52
+ it 'reads multiple lines with empty lines' do
53
+ prompt = TTY::TestPrompt.new
54
+ prompt.input << "aa\n\nbb\n\n\ncc\C-d"
55
+ prompt.input.rewind
56
+
57
+ answer = prompt.multiline("Description?")
58
+ expect(answer).to eq(["aa\n", "bb\n", "cc"])
59
+ expect(prompt.output.string).to eq([
60
+ "Description? \e[90m(Press CTRL-D or CTRL-Z to finish)\e[0m\n",
61
+ "\e[2K\e[1Ga",
62
+ "\e[2K\e[1Gaa",
63
+ "\e[2K\e[1Gaa\n",
64
+ "\e[2K\e[1G\n",
65
+ "\e[2K\e[1Gb",
66
+ "\e[2K\e[1Gbb",
67
+ "\e[2K\e[1Gbb\n",
68
+ "\e[2K\e[1G\n",
69
+ "\e[2K\e[1G\n",
70
+ "\e[2K\e[1Gc",
71
+ "\e[2K\e[1Gcc",
72
+ "\e[2K\e[1G\e[1A" * 6,
73
+ "\e[2K\e[1G",
74
+ "Description? \e[32maa ...\e[0m\n"
75
+ ].join)
76
+ end
77
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, '#new' do
4
+ let(:env) { { "TTY_TEST" => true } }
5
+
6
+ it "sets prefix" do
7
+ prompt = described_class.new(prefix: "[?]", env: env)
8
+ expect(prompt.prefix).to eq("[?]")
9
+ end
10
+
11
+ it "sets input stream" do
12
+ prompt = described_class.new(input: :stream1, env: env)
13
+ expect(prompt.input).to eq(:stream1)
14
+ end
15
+
16
+ it "sets output stream" do
17
+ prompt = described_class.new(output: :stream2, env: env)
18
+ expect(prompt.output).to eq(:stream2)
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, 'ok' do
4
+ subject(:prompt) { TTY::TestPrompt.new }
5
+
6
+ it 'prints text in green' do
7
+ prompt.ok("All is fine")
8
+ expect(prompt.output.string).to eq("\e[32mAll is fine\e[0m\n")
9
+ end
10
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Paginator, '#paginate' do
4
+ it "ignores per_page when equal items " do
5
+ list = %w(a b c d)
6
+ paginator = described_class.new({per_page: 4})
7
+
8
+ expect(paginator.paginate(list, 1).to_a).to eq([
9
+ ['a',0],['b',1],['c',2],['d',3]])
10
+ end
11
+
12
+ it "ignores per_page when less items " do
13
+ list = %w(a b c d)
14
+ paginator = described_class.new({per_page: 5})
15
+
16
+ expect(paginator.paginate(list, 1).to_a).to eq([
17
+ ['a',0],['b',1],['c',2],['d',3]])
18
+ end
19
+
20
+ it "paginates items matching per_page count" do
21
+ list = %w(a b c d e f)
22
+ paginator = described_class.new({per_page: 3})
23
+
24
+ expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
25
+ expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
26
+ expect(paginator.paginate(list, 3).to_a).to eq([['a',0], ['b',1], ['c',2]])
27
+ expect(paginator.paginate(list, 4).to_a).to eq([['b',1], ['c',2], ['d',3]])
28
+ expect(paginator.paginate(list, 5).to_a).to eq([['c',2], ['d',3], ['e',4]])
29
+ expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
30
+ expect(paginator.paginate(list, 7).to_a).to eq([['d',3], ['e',4], ['f',5]])
31
+ end
32
+
33
+ it "paginates items not matching per_page count" do
34
+ list = %w(a b c d e f g)
35
+ paginator = described_class.new({per_page: 3})
36
+
37
+ expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
38
+ expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
39
+ expect(paginator.paginate(list, 3).to_a).to eq([['a',0], ['b',1], ['c',2]])
40
+ expect(paginator.paginate(list, 4).to_a).to eq([['b',1], ['c',2], ['d',3]])
41
+ expect(paginator.paginate(list, 5).to_a).to eq([['c',2], ['d',3], ['e',4]])
42
+ expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
43
+ expect(paginator.paginate(list, 7).to_a).to eq([['e',4], ['f',5], ['g',6]])
44
+ expect(paginator.paginate(list, 8).to_a).to eq([['e',4], ['f',5], ['g',6]])
45
+ end
46
+
47
+ it "finds maximum index for current selection" do
48
+ list = %w(a b c d e f g)
49
+ paginator = described_class.new({per_page: 3, default: 0})
50
+
51
+ paginator.paginate(list, 4)
52
+ expect(paginator.max_index).to eq(3)
53
+ paginator.paginate(list, 5)
54
+ expect(paginator.max_index).to eq(4)
55
+ end
56
+
57
+ it "starts with default selection" do
58
+ list = %w(a b c d e f g)
59
+ paginator = described_class.new({per_page: 3, default: 3})
60
+
61
+ expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
62
+ end
63
+
64
+ it "doesn't accept invalid pagination" do
65
+ list = %w(a b c d e f g)
66
+
67
+ paginator = described_class.new({per_page: 0})
68
+
69
+ expect {
70
+ paginator.paginate(list, 4)
71
+ }.to raise_error(TTY::Prompt::InvalidArgument, /per_page must be > 0/)
72
+ end
73
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it "passes range check" do
8
+ question = described_class.new(prompt)
9
+ question.in 1..10
10
+
11
+ result = TTY::Prompt::Question::Checks::CheckRange.call(question, 2)
12
+
13
+ expect(result).to eq([2])
14
+ end
15
+
16
+ it "fails range check" do
17
+ question = described_class.new(prompt, messages: TTY::Prompt.messages)
18
+ question.in 1..10
19
+
20
+ result = TTY::Prompt::Question::Checks::CheckRange.call(question, 11)
21
+
22
+ expect(result).to eq([11, ["Value 11 must be within the range 1..10"]])
23
+ end
24
+
25
+ it "fails range check" do
26
+ question = described_class.new(prompt)
27
+ question.in 1..10, 'Outside of range!'
28
+
29
+ result = TTY::Prompt::Question::Checks::CheckRange.call(question, 11)
30
+
31
+ expect(result).to eq([11, ['Outside of range!']])
32
+ end
33
+
34
+ it "passes validation check" do
35
+ question = described_class.new(prompt)
36
+ question.validate(/\A\d{5}\Z/)
37
+
38
+ result = TTY::Prompt::Question::Checks::CheckValidation.call(question, '12345')
39
+
40
+ expect(result).to eq(['12345'])
41
+ end
42
+
43
+ it "fails validation check" do
44
+ question = described_class.new(prompt, messages: TTY::Prompt.messages)
45
+ question.validate(/\A\d{5}\Z/)
46
+
47
+ result = TTY::Prompt::Question::Checks::CheckValidation.call(question, '123')
48
+
49
+ expect(result).to eq(['123', ['Your answer is invalid (must match /\\A\\d{5}\\Z/)']])
50
+ end
51
+
52
+ it "fails validation check with inlined custom message" do
53
+ question = described_class.new(prompt)
54
+ question.validate(/\A\w+@\w+\.\w+\Z/, 'Invalid email address')
55
+
56
+ result = TTY::Prompt::Question::Checks::CheckValidation.call(question, 'piotr@com')
57
+
58
+ expect(result).to eq(['piotr@com', ['Invalid email address']])
59
+ end
60
+
61
+ it "fails validation check with custom message" do
62
+ question = described_class.new(prompt)
63
+ question.validate(/\A\w+@\w+\.\w+\Z/)
64
+ question.messages[:valid?] = 'Invalid email address'
65
+
66
+ result = TTY::Prompt::Question::Checks::CheckValidation.call(question, 'piotr@com')
67
+
68
+ expect(result).to eq(['piotr@com', ['Invalid email address']])
69
+ end
70
+
71
+ it "passes required check" do
72
+ question = described_class.new(prompt)
73
+ question.required true
74
+
75
+ result = TTY::Prompt::Question::Checks::CheckRequired.call(question, 'Piotr')
76
+
77
+ expect(result).to eq(['Piotr'])
78
+ end
79
+
80
+ it "fails required check" do
81
+ question = described_class.new(prompt, messages: TTY::Prompt.messages)
82
+ question.required true
83
+
84
+ result = TTY::Prompt::Question::Checks::CheckRequired.call(question, nil)
85
+
86
+ expect(result).to eq([nil, ['Value must be provided']])
87
+ end
88
+
89
+ it "fails required check with custom message" do
90
+ question = described_class.new(prompt)
91
+ question.required true, 'Required input'
92
+
93
+ result = TTY::Prompt::Question::Checks::CheckRequired.call(question, nil)
94
+
95
+ expect(result).to eq([nil, ['Required input']])
96
+ end
97
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, '#default' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it 'uses default value' do
8
+ name = 'Anonymous'
9
+ prompt.input << "\n"
10
+ prompt.input.rewind
11
+ answer = prompt.ask('What is your name?', default: name)
12
+ expect(answer).to eq(name)
13
+ expect(prompt.output.string).to eq([
14
+ "What is your name? \e[90m(Anonymous)\e[0m ",
15
+ "\e[2K\e[1GWhat is your name? \e[90m(Anonymous)\e[0m \n",
16
+ "\e[1A\e[2K\e[1G",
17
+ "What is your name? \e[32mAnonymous\e[0m\n"
18
+ ].join)
19
+ end
20
+
21
+ it 'uses default value in block' do
22
+ name = 'Anonymous'
23
+ answer = prompt.ask('What is your name?') { |q| q.default(name) }
24
+ expect(answer).to eq(name)
25
+ expect(prompt.output.string).to eq([
26
+ "What is your name? \e[90m(Anonymous)\e[0m ",
27
+ "\e[1A\e[2K\e[1G",
28
+ "What is your name? \e[32mAnonymous\e[0m\n"
29
+ ].join)
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, '#echo' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it 'asks with echo on' do
8
+ prompt.input << "password"
9
+ prompt.input.rewind
10
+ answer = prompt.ask("What is your password?") { |q| q.echo(true) }
11
+ expect(answer).to eql("password")
12
+ expect(prompt.output.string).to eq([
13
+ "What is your password? ",
14
+ "\e[2K\e[1GWhat is your password? p",
15
+ "\e[2K\e[1GWhat is your password? pa",
16
+ "\e[2K\e[1GWhat is your password? pas",
17
+ "\e[2K\e[1GWhat is your password? pass",
18
+ "\e[2K\e[1GWhat is your password? passw",
19
+ "\e[2K\e[1GWhat is your password? passwo",
20
+ "\e[2K\e[1GWhat is your password? passwor",
21
+ "\e[2K\e[1GWhat is your password? password",
22
+ "\e[1A\e[2K\e[1G",
23
+ "What is your password? \e[32mpassword\e[0m\n"
24
+ ].join)
25
+ end
26
+
27
+ it 'asks with echo off' do
28
+ prompt.input << "password"
29
+ prompt.input.rewind
30
+ answer = prompt.ask("What is your password?", echo: false)
31
+ expect(answer).to eql("password")
32
+ expect(prompt.output.string).to eq([
33
+ "What is your password? ",
34
+ "\e[1A\e[2K\e[1G",
35
+ "What is your password? \n"
36
+ ].join)
37
+ end
38
+ end
@@ -0,0 +1,115 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, '#in' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it "reads range from option" do
8
+ prompt.input << '8'
9
+ prompt.input.rewind
10
+
11
+ answer = prompt.ask("How do you like it on scale 1-10?", in: '1-10')
12
+
13
+ expect(answer).to eq('8')
14
+ end
15
+
16
+ it 'reads number within string range' do
17
+ prompt.input << '8'
18
+ prompt.input.rewind
19
+
20
+ answer = prompt.ask("How do you like it on scale 1-10?") do |q|
21
+ q.in('1-10')
22
+ end
23
+
24
+ expect(answer).to eq('8')
25
+ expect(prompt.output.string).to eq([
26
+ "How do you like it on scale 1-10? ",
27
+ "\e[2K\e[1GHow do you like it on scale 1-10? 8",
28
+ "\e[1A\e[2K\e[1G",
29
+ "How do you like it on scale 1-10? \e[32m8\e[0m\n",
30
+ ].join)
31
+ end
32
+
33
+ it 'reads number within digit range' do
34
+ prompt.input << '8.1'
35
+ prompt.input.rewind
36
+
37
+ answer = prompt.ask("How do you like it on scale 1-10?") do |q|
38
+ q.in(1.0..11.5)
39
+ end
40
+
41
+ expect(answer).to eq('8.1')
42
+ expect(prompt.output.string).to eq([
43
+ "How do you like it on scale 1-10? ",
44
+ "\e[2K\e[1GHow do you like it on scale 1-10? 8",
45
+ "\e[2K\e[1GHow do you like it on scale 1-10? 8.",
46
+ "\e[2K\e[1GHow do you like it on scale 1-10? 8.1",
47
+ "\e[1A\e[2K\e[1G",
48
+ "How do you like it on scale 1-10? \e[32m8.1\e[0m\n",
49
+ ].join)
50
+ end
51
+
52
+ it 'reads letters within range' do
53
+ prompt.input << 'E'
54
+ prompt.input.rewind
55
+
56
+ answer = prompt.ask("Your favourite vitamin? (A-K)") do |q|
57
+ q.in('A-K')
58
+ end
59
+
60
+ expect(answer).to eq('E')
61
+ expect(prompt.output.string).to eq([
62
+ "Your favourite vitamin? (A-K) ",
63
+ "\e[2K\e[1GYour favourite vitamin? (A-K) E",
64
+ "\e[1A\e[2K\e[1G",
65
+ "Your favourite vitamin? (A-K) \e[32mE\e[0m\n"
66
+ ].join)
67
+ end
68
+
69
+ it "provides default error message when wrong input" do
70
+ prompt.input << "A\n2\n"
71
+ prompt.input.rewind
72
+
73
+ answer = prompt.ask("How spicy on scale? (1-5)", in: '1-5')
74
+
75
+ expect(answer).to eq('2')
76
+ expect(prompt.output.string).to eq([
77
+ "How spicy on scale? (1-5) ",
78
+ "\e[2K\e[1GHow spicy on scale? (1-5) A",
79
+ "\e[2K\e[1GHow spicy on scale? (1-5) A\n",
80
+ "\e[31m>>\e[0m Value A must be within the range 1..5\e[1A",
81
+ "\e[2K\e[1G",
82
+ "How spicy on scale? (1-5) ",
83
+ "\e[2K\e[1GHow spicy on scale? (1-5) 2",
84
+ "\e[2K\e[1GHow spicy on scale? (1-5) 2\n",
85
+ "\e[2K\e[1G",
86
+ "\e[1A\e[2K\e[1G",
87
+ "How spicy on scale? (1-5) \e[32m2\e[0m\n"
88
+ ].join)
89
+ end
90
+
91
+ it "overwrites default error message when wrong input" do
92
+ prompt.input << "A\n2\n"
93
+ prompt.input.rewind
94
+
95
+ answer = prompt.ask("How spicy on scale? (1-5)") do |q|
96
+ q.in '1-5'
97
+ q.messages[:range?] = 'Ohh dear what is this %{value} doing in %{in}?'
98
+ end
99
+
100
+ expect(answer).to eq('2')
101
+ expect(prompt.output.string).to eq([
102
+ "How spicy on scale? (1-5) ",
103
+ "\e[2K\e[1GHow spicy on scale? (1-5) A",
104
+ "\e[2K\e[1GHow spicy on scale? (1-5) A\n",
105
+ "\e[31m>>\e[0m Ohh dear what is this A doing in 1..5?\e[1A",
106
+ "\e[2K\e[1G",
107
+ "How spicy on scale? (1-5) ",
108
+ "\e[2K\e[1GHow spicy on scale? (1-5) 2",
109
+ "\e[2K\e[1GHow spicy on scale? (1-5) 2\n",
110
+ "\e[2K\e[1G",
111
+ "\e[1A\e[2K\e[1G",
112
+ "How spicy on scale? (1-5) \e[32m2\e[0m\n"
113
+ ].join)
114
+ end
115
+ end