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,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