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,96 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, '#collect' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ def collect(&block)
8
+ prompt = subject
9
+ count = 0
10
+
11
+ result = prompt.collect do
12
+ while prompt.yes?("continue?")
13
+ instance_eval(&block)
14
+ count += 1
15
+ end
16
+ end
17
+
18
+ result[:count] = count
19
+ result
20
+ end
21
+
22
+ context "when receiving multiple answers" do
23
+ let(:colors) { %w(red blue yellow) }
24
+
25
+ before do
26
+ subject.input << "y\r" + colors.join("\ry\r") + "\rn\r"
27
+ subject.input.rewind
28
+ end
29
+
30
+ it "collects as a list if values method used in chain" do
31
+ result = collect { key(:colors).values.ask("color:") }
32
+ expect(result[:count]).to eq(3)
33
+ expect(result[:colors]).to eq(colors)
34
+ end
35
+
36
+ it "collects as a list if values method used in chain with block" do
37
+ result = collect do
38
+ key(:colors).values { key(:name).ask("color:") }
39
+ end
40
+ expect(result[:count]).to eq(3)
41
+ expect(result[:colors]).to eq(colors.map { |c| { name: c } })
42
+ end
43
+
44
+ context "with multiple keys" do
45
+ let(:colors) { ["red\rblue", "yellow\rgreen"] }
46
+ let(:expected_pairs) do
47
+ colors.map { |s| Hash[%i(hot cold).zip(s.split("\r"))] }
48
+ end
49
+
50
+ it "collects into the appropriate keys" do
51
+ result = collect do
52
+ key(:pairs).values do
53
+ key(:hot).ask("color:")
54
+ key(:cold).ask("color:")
55
+ end
56
+ end
57
+
58
+ expect(result[:count]).to eq(2)
59
+ expect(result[:pairs]).to eq(expected_pairs)
60
+ end
61
+ end
62
+
63
+ it "overrides a non-array key on multiple answers" do
64
+ result = collect { key(:colors).ask("color:") }
65
+ expect(result[:colors]).to eq(colors.last)
66
+ expect(result[:count]).to eq(3)
67
+ end
68
+ end
69
+
70
+ it "collects more than one answer" do
71
+ prompt.input << "Piotr\r30\rStreet\rCity\r123\r"
72
+ prompt.input.rewind
73
+
74
+ result = prompt.collect do
75
+ key(:name).ask('Name?')
76
+
77
+ key(:age).ask('Age?', convert: :int)
78
+
79
+ key(:address) do
80
+ key(:street).ask('Street?', required: true)
81
+ key(:city).ask('City?')
82
+ key(:zip).ask('Zip?', validate: /\A\d{3}\Z/)
83
+ end
84
+ end
85
+
86
+ expect(result).to include({
87
+ name: 'Piotr',
88
+ age: 30,
89
+ address: {
90
+ street: 'Street',
91
+ city: 'City',
92
+ zip: '123'
93
+ }
94
+ })
95
+ end
96
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert bool' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new}
6
+
7
+ it 'fails to convert boolean' do
8
+ prompt.input << 'invalid'
9
+ prompt.input.rewind
10
+ expect {
11
+ prompt.ask("Do you read books?", convert: :bool)
12
+ }.to raise_error(TTY::Prompt::ConversionError)
13
+ end
14
+
15
+ it "handles default values" do
16
+ prompt.input << "\n"
17
+ prompt.input.rewind
18
+ response = prompt.ask('Do you read books?', convert: :bool, default: true)
19
+ expect(response).to eql(true)
20
+ expect(prompt.output.string).to eq([
21
+ "Do you read books? \e[90m(true)\e[0m ",
22
+ "\e[2K\e[1GDo you read books? \e[90m(true)\e[0m \n",
23
+ "\e[1A\e[2K\e[1G",
24
+ "Do you read books? \e[32mtrue\e[0m\n"
25
+ ].join)
26
+ end
27
+
28
+ it "handles default values" do
29
+ prompt.input << "\n"
30
+ prompt.input.rewind
31
+ response = prompt.ask("Do you read books?") { |q|
32
+ q.default true
33
+ q.convert :bool
34
+ }
35
+ expect(response).to eq(true)
36
+ end
37
+
38
+ it 'converts negative boolean' do
39
+ prompt.input << 'No'
40
+ prompt.input.rewind
41
+ response = prompt.ask('Do you read books?', convert: :bool)
42
+ expect(response).to eq(false)
43
+ end
44
+
45
+ it 'converts positive boolean' do
46
+ prompt.input << 'Yes'
47
+ prompt.input.rewind
48
+ response = prompt.ask("Do you read books?", convert: :bool)
49
+ expect(response).to eq(true)
50
+ end
51
+
52
+ it 'converts single positive boolean' do
53
+ prompt.input << 'y'
54
+ prompt.input.rewind
55
+ response = prompt.ask('Do you read books?', convert: :bool)
56
+ expect(response).to eq(true)
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert char' do
4
+ it 'reads single character' do
5
+ prompt = TTY::TestPrompt.new
6
+ prompt.input << "abcde"
7
+ prompt.input.rewind
8
+ response = prompt.ask("What is your favourite letter?", convert: :char)
9
+ expect(response).to eq('a')
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert custom' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it 'converts response with custom conversion' do
8
+ prompt.input << "one,two,three\n"
9
+ prompt.input.rewind
10
+ conversion = proc { |input| input.split(/,\s*/) }
11
+ answer = prompt.ask('Ingredients? (comma sep list)', convert: conversion)
12
+ expect(answer).to eq(['one','two','three'])
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert date' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new}
6
+
7
+ it 'fails to convert date' do
8
+ prompt.input << 'invalid'
9
+ prompt.input.rewind
10
+ expect {
11
+ prompt.ask("When were you born?", convert: :date)
12
+ }.to raise_error(TTY::Prompt::ConversionError)
13
+ end
14
+
15
+ it 'converts date' do
16
+ prompt.input << "20th April 1887"
17
+ prompt.input.rewind
18
+ response = prompt.ask("When were your born?", convert: :date)
19
+ expect(response).to be_kind_of(Date)
20
+ expect(response.day).to eq(20)
21
+ expect(response.month).to eq(4)
22
+ expect(response.year).to eq(1887)
23
+ end
24
+
25
+ it "converts datetime" do
26
+ prompt.input << "20th April 1887"
27
+ prompt.input.rewind
28
+ response = prompt.ask("When were your born?", convert: :datetime)
29
+ expect(response).to be_kind_of(DateTime)
30
+ expect(response.day).to eq(20)
31
+ expect(response.month).to eq(4)
32
+ expect(response.year).to eq(1887)
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert file' do
4
+ it "converts to file" do
5
+ ::File.write('test.txt', 'foobar')
6
+
7
+ prompt = TTY::TestPrompt.new
8
+ prompt.input << "test.txt"
9
+ prompt.input.rewind
10
+
11
+ answer = prompt.ask("Which file to open?", convert: :file)
12
+
13
+ expect(::File.basename(answer)).to eq('test.txt')
14
+ expect(::File.read(answer)).to eq('foobar')
15
+
16
+ ::File.unlink('test.txt') unless Gem.win_platform?
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert numbers' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it 'fails to convert integer' do
8
+ prompt.input << 'invalid'
9
+ prompt.input.rewind
10
+ expect {
11
+ prompt.ask("What temparture?", convert: :int)
12
+ }.to raise_error(TTY::Prompt::ConversionError)
13
+ end
14
+
15
+ it 'converts integer' do
16
+ prompt.input << 35
17
+ prompt.input.rewind
18
+ answer = prompt.ask("What temperature?", convert: :int)
19
+ expect(answer).to be_a(Integer)
20
+ expect(answer).to eq(35)
21
+ end
22
+
23
+ it 'fails to convert float' do
24
+ prompt.input << 'invalid'
25
+ prompt.input.rewind
26
+ expect {
27
+ prompt.ask("How tall are you?", convert: :float)
28
+ }.to raise_error(TTY::Prompt::ConversionError)
29
+ end
30
+
31
+ it 'converts float' do
32
+ number = 6.666
33
+ prompt.input << number
34
+ prompt.input.rewind
35
+ answer = prompt.ask('How tall are you?', convert: :float)
36
+ expect(answer).to be_a(Float)
37
+ expect(answer).to eq(number)
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert path' do
4
+ subject(:prompt) { TTY::TestPrompt.new }
5
+
6
+ it "converts pathname" do
7
+ path = Pathname.new(::File.join(Dir.pwd, 'spec/unit'))
8
+ prompt.input << "spec/unit"
9
+ prompt.input.rewind
10
+
11
+ answer = prompt.ask('File location?', convert: :path)
12
+
13
+ expect(answer).to eql(path)
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert range' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new}
6
+
7
+ it 'converts with valid range' do
8
+ prompt.input << "20-30"
9
+ prompt.input.rewind
10
+ answer = prompt.ask("Which age group?", convert: :range)
11
+ expect(answer).to be_a(Range)
12
+ expect(answer).to eq(20..30)
13
+ end
14
+
15
+ it "fails to convert to range" do
16
+ prompt.input << "abcd"
17
+ prompt.input.rewind
18
+ expect {
19
+ prompt.ask('Which age group?', convert: :range)
20
+ }.to raise_error(TTY::Prompt::ConversionError)
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert regexp' do
4
+ it "converts regex" do
5
+ prompt = TTY::TestPrompt.new
6
+ prompt.input << "[a-z]*"
7
+ prompt.input.rewind
8
+ answer = prompt.ask("Regex?", convert: :regexp)
9
+ expect(answer).to be_a(Regexp)
10
+ expect(answer).to eq(/[a-z]*/)
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Question, 'convert string' do
4
+ it 'converts string' do
5
+ prompt = TTY::TestPrompt.new
6
+ prompt.input << 'Piotr'
7
+ prompt.input.rewind
8
+ answer = prompt.ask("What is your name?", convert: :string)
9
+ expect(answer).to be_a(String)
10
+ expect(answer).to eq('Piotr')
11
+ end
12
+
13
+ it "converts symbol" do
14
+ prompt = TTY::TestPrompt.new
15
+ prompt.input << 'Piotr'
16
+ prompt.input.rewind
17
+ answer = prompt.ask("What is your name?", convert: :symbol)
18
+ expect(answer).to be_a(Symbol)
19
+ expect(answer).to eq(:Piotr)
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Converters do
4
+ it "enforces block argument" do
5
+ expect {
6
+ TTY::Prompt::Converters.on_error
7
+ }.to raise_error(ArgumentError, 'You need to provide a block argument.')
8
+ end
9
+ end
@@ -0,0 +1,73 @@
1
+ # coding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::Distance, '.distance' do
4
+ let(:object) { described_class.new }
5
+
6
+ subject(:distance) { object.distance(*strings) }
7
+
8
+ context 'when nil' do
9
+ let(:strings) { [nil, nil] }
10
+
11
+ it { is_expected.to eql(0) }
12
+ end
13
+
14
+ context 'when empty' do
15
+ let(:strings) { ['', ''] }
16
+
17
+ it { is_expected.to eql(0) }
18
+ end
19
+
20
+ context 'with one non empty' do
21
+ let(:strings) { ['abc', ''] }
22
+
23
+ it { is_expected.to eql(3) }
24
+ end
25
+
26
+ context 'when single char' do
27
+ let(:strings) { ['a', 'abc'] }
28
+
29
+ it { is_expected.to eql(2) }
30
+ end
31
+
32
+ context 'when similar' do
33
+ let(:strings) { ['abc', 'abc'] }
34
+
35
+ it { is_expected.to eql(0) }
36
+ end
37
+
38
+ context 'when similar' do
39
+ let(:strings) { ['abc', 'acb'] }
40
+
41
+ it { is_expected.to eql(1) }
42
+ end
43
+
44
+ context 'when end similar' do
45
+ let(:strings) { ['saturday', 'sunday'] }
46
+
47
+ it { is_expected.to eql(3) }
48
+ end
49
+
50
+ context 'when contain similar' do
51
+ let(:strings) { ['which', 'witch'] }
52
+
53
+ it { is_expected.to eql(2) }
54
+ end
55
+
56
+ context 'when prefix' do
57
+ let(:strings) { ['sta', 'status'] }
58
+
59
+ it { is_expected.to eql(3) }
60
+ end
61
+
62
+ context 'when similar' do
63
+ let(:strings) { ['smellyfish','jellyfish'] }
64
+
65
+ it { is_expected.to eql(2) }
66
+ end
67
+
68
+ context 'when unicode' do
69
+ let(:strings) { ['マラソン五輪代表', 'ララソン五輪代表'] }
70
+
71
+ it { is_expected.to eql(1) }
72
+ end
73
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::EnumPaginator, '#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([['d',3], ['e',4], ['f',5]])
28
+ expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
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([['d',3], ['e',4], ['f',5]])
41
+ expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
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([['g',6]])
44
+ expect(paginator.paginate(list, 8).to_a).to eq([['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(5)
53
+ paginator.paginate(list, 5)
54
+ expect(paginator.max_index).to eq(5)
55
+ paginator.paginate(list, 7)
56
+ expect(paginator.max_index).to eq(8)
57
+ end
58
+
59
+ it "starts with default selection" do
60
+ list = %w(a b c d e f g)
61
+ paginator = described_class.new({per_page: 3, default: 3})
62
+
63
+ expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
64
+ end
65
+
66
+ it "doesn't accept invalid pagination" do
67
+ list = %w(a b c d e f g)
68
+
69
+ paginator = described_class.new({per_page: 0})
70
+
71
+ expect {
72
+ paginator.paginate(list, 4)
73
+ }.to raise_error(TTY::Prompt::InvalidArgument, /per_page must be > 0/)
74
+ end
75
+ end