rfix 2.0.4 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/rfix +11 -90
- data/lib/rfix.rb +10 -9
- data/lib/rfix/branch/reference.rb +2 -2
- data/lib/rfix/branch/upstream.rb +2 -4
- data/lib/rfix/cli/command.rb +14 -1
- data/lib/rfix/cli/command/all.rb +21 -0
- data/lib/rfix/cli/command/base.rb +30 -19
- data/lib/rfix/cli/command/branch.rb +2 -0
- data/lib/rfix/cli/command/help.rb +2 -0
- data/lib/rfix/cli/command/info.rb +6 -1
- data/lib/rfix/cli/command/local.rb +2 -0
- data/lib/rfix/cli/command/origin.rb +2 -0
- data/lib/rfix/cli/command/setup.rb +2 -0
- data/lib/rfix/cli/command/status.rb +39 -0
- data/lib/rfix/collector.rb +69 -0
- data/lib/rfix/diff.rb +69 -0
- data/lib/rfix/extension/comment_config.rb +15 -0
- data/lib/rfix/extension/offense.rb +17 -14
- data/lib/rfix/extension/pastel.rb +7 -4
- data/lib/rfix/extension/progresbar.rb +15 -0
- data/lib/rfix/extension/strings.rb +10 -2
- data/lib/rfix/file.rb +5 -3
- data/lib/rfix/file/base.rb +21 -14
- data/lib/rfix/file/deleted.rb +2 -0
- data/lib/rfix/file/ignored.rb +2 -0
- data/lib/rfix/file/null.rb +17 -0
- data/lib/rfix/file/tracked.rb +39 -23
- data/lib/rfix/file/undefined.rb +17 -0
- data/lib/rfix/file/untracked.rb +3 -1
- data/lib/rfix/formatter.rb +67 -71
- data/lib/rfix/highlighter.rb +1 -3
- data/lib/rfix/rake/gemfile.rb +26 -23
- data/lib/rfix/repository.rb +59 -96
- data/lib/rfix/types.rb +24 -14
- data/lib/rfix/version.rb +1 -1
- data/rfix.gemspec +11 -3
- data/vendor/cli-ui/Gemfile +17 -0
- data/vendor/cli-ui/Gemfile.lock +60 -0
- data/vendor/cli-ui/LICENSE.txt +21 -0
- data/vendor/cli-ui/README.md +224 -0
- data/vendor/cli-ui/Rakefile +20 -0
- data/vendor/cli-ui/bin/console +14 -0
- data/vendor/cli-ui/cli-ui.gemspec +25 -0
- data/vendor/cli-ui/dev.yml +14 -0
- data/vendor/cli-ui/lib/cli/ui.rb +233 -0
- data/vendor/cli-ui/lib/cli/ui/ansi.rb +157 -0
- data/vendor/cli-ui/lib/cli/ui/color.rb +84 -0
- data/vendor/cli-ui/lib/cli/ui/formatter.rb +192 -0
- data/vendor/cli-ui/lib/cli/ui/frame.rb +269 -0
- data/vendor/cli-ui/lib/cli/ui/frame/frame_stack.rb +98 -0
- data/vendor/cli-ui/lib/cli/ui/frame/frame_style.rb +120 -0
- data/vendor/cli-ui/lib/cli/ui/frame/frame_style/box.rb +166 -0
- data/vendor/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +139 -0
- data/vendor/cli-ui/lib/cli/ui/glyph.rb +84 -0
- data/vendor/cli-ui/lib/cli/ui/os.rb +67 -0
- data/vendor/cli-ui/lib/cli/ui/printer.rb +59 -0
- data/vendor/cli-ui/lib/cli/ui/progress.rb +90 -0
- data/vendor/cli-ui/lib/cli/ui/prompt.rb +297 -0
- data/vendor/cli-ui/lib/cli/ui/prompt/interactive_options.rb +484 -0
- data/vendor/cli-ui/lib/cli/ui/prompt/options_handler.rb +29 -0
- data/vendor/cli-ui/lib/cli/ui/spinner.rb +66 -0
- data/vendor/cli-ui/lib/cli/ui/spinner/async.rb +40 -0
- data/vendor/cli-ui/lib/cli/ui/spinner/spin_group.rb +263 -0
- data/vendor/cli-ui/lib/cli/ui/stdout_router.rb +232 -0
- data/vendor/cli-ui/lib/cli/ui/terminal.rb +46 -0
- data/vendor/cli-ui/lib/cli/ui/truncater.rb +102 -0
- data/vendor/cli-ui/lib/cli/ui/version.rb +5 -0
- data/vendor/cli-ui/lib/cli/ui/widgets.rb +77 -0
- data/vendor/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
- data/vendor/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
- data/vendor/cli-ui/lib/cli/ui/wrap.rb +56 -0
- data/vendor/cli-ui/test/cli/ui/ansi_test.rb +32 -0
- data/vendor/cli-ui/test/cli/ui/cli_ui_test.rb +23 -0
- data/vendor/cli-ui/test/cli/ui/color_test.rb +40 -0
- data/vendor/cli-ui/test/cli/ui/formatter_test.rb +79 -0
- data/vendor/cli-ui/test/cli/ui/glyph_test.rb +68 -0
- data/vendor/cli-ui/test/cli/ui/printer_test.rb +103 -0
- data/vendor/cli-ui/test/cli/ui/progress_test.rb +46 -0
- data/vendor/cli-ui/test/cli/ui/prompt/options_handler_test.rb +39 -0
- data/vendor/cli-ui/test/cli/ui/prompt_test.rb +348 -0
- data/vendor/cli-ui/test/cli/ui/spinner/spin_group_test.rb +39 -0
- data/vendor/cli-ui/test/cli/ui/spinner_test.rb +141 -0
- data/vendor/cli-ui/test/cli/ui/stdout_router_test.rb +32 -0
- data/vendor/cli-ui/test/cli/ui/terminal_test.rb +26 -0
- data/vendor/cli-ui/test/cli/ui/truncater_test.rb +31 -0
- data/vendor/cli-ui/test/cli/ui/widgets/status_test.rb +49 -0
- data/vendor/cli-ui/test/cli/ui/widgets_test.rb +15 -0
- data/vendor/cli-ui/test/test_helper.rb +53 -0
- data/vendor/cli-ui/tmp/cache/bootsnap/compile-cache/d9/c036af0f3dc494 +0 -0
- data/vendor/cli-ui/tmp/cache/bootsnap/load-path-cache +0 -0
- data/vendor/dry-cli/lib/dry/cli/command.rb +2 -1
- data/vendor/dry-cli/tmp/cache/bootsnap/compile-cache/ff/a22a5daafbd74c +0 -0
- data/vendor/dry-cli/tmp/cache/bootsnap/load-path-cache +0 -0
- data/vendor/strings-ansi/tmp/cache/bootsnap/compile-cache/79/49cf49407b370e +0 -0
- data/vendor/strings-ansi/tmp/cache/bootsnap/load-path-cache +0 -0
- metadata +170 -9
- data/lib/rfix/extension/string.rb +0 -12
- data/lib/rfix/indicator.rb +0 -19
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
class FormatterTest < MiniTest::Test
|
6
|
+
def test_format
|
7
|
+
input = 'a{{blue:b {{*}}{{bold:c {{red:d}}}}{{bold: e}}}} f'
|
8
|
+
expected = "\e[0ma\e[0;94mb \e[0;33m⭑\e[0;94;1mc \e[0;94;1;31md\e[0;94;1m e\e[0m f"
|
9
|
+
actual = CLI::UI::Formatter.new(input).format
|
10
|
+
assert_equal(expected, actual)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_format_widget
|
14
|
+
input = 'a{{@widget/status:0:0:0:0}}b'
|
15
|
+
expected = "a\e[0m\e[1m∅\e[0mb"
|
16
|
+
actual = CLI::UI::Formatter.new(input).format(enable_color: false)
|
17
|
+
assert_equal(expected, actual)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_format_no_color
|
21
|
+
input = 'a{{blue:b {{*}}{{bold:c {{red:d}}}}{{bold: e}}}} f {{bold:'
|
22
|
+
expected = 'ab ⭑c d e f '
|
23
|
+
actual = CLI::UI::Formatter.new(input).format(enable_color: false)
|
24
|
+
assert_equal(expected, actual)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_format_trailing
|
28
|
+
input = 'a{{bold:a {{blue:'
|
29
|
+
expected = "\e[0ma\e[0;1ma \e[0;1;94m"
|
30
|
+
actual = CLI::UI::Formatter.new(input).format
|
31
|
+
assert_equal(expected, actual)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_invalid_funcname
|
35
|
+
input = '{{nope:text}}'
|
36
|
+
ex = assert_raises(CLI::UI::Formatter::FormatError) do
|
37
|
+
CLI::UI::Formatter.new(input).format
|
38
|
+
end
|
39
|
+
expected = 'invalid format specifier: nope'
|
40
|
+
assert_equal(input, ex.input)
|
41
|
+
assert_equal(-1, ex.index)
|
42
|
+
assert_equal(expected, ex.message)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_invalid_glyph
|
46
|
+
input = '{{&}}'
|
47
|
+
ex = assert_raises(CLI::UI::Formatter::FormatError) do
|
48
|
+
CLI::UI::Formatter.new(input).format
|
49
|
+
end
|
50
|
+
expected = "invalid glyph handle at index 3: '&'"
|
51
|
+
assert_equal(input, ex.input)
|
52
|
+
assert_equal(3, ex.index)
|
53
|
+
assert_equal(expected, ex.message)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_mixed_non_syntax
|
57
|
+
input = '{{bold:{{foo {{green:bar}} }}}}'
|
58
|
+
expected = "\e[0;1m{{foo \e[0;1;32mbar\e[0;1m }}\e[0m"
|
59
|
+
actual = CLI::UI::Formatter.new(input).format
|
60
|
+
assert_equal(expected, actual)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_incomplete_non_syntax
|
64
|
+
input = '{{foo'
|
65
|
+
expected = "\e[0m{{foo"
|
66
|
+
actual = CLI::UI::Formatter.new(input).format
|
67
|
+
assert_equal(expected, actual)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_reset_after_glyph
|
71
|
+
input = '{{*}} foobar'
|
72
|
+
expected = "\e[0;33m⭑\e[0m foobar"
|
73
|
+
|
74
|
+
actual = CLI::UI::Formatter.new(input).format
|
75
|
+
assert_equal(expected, actual)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
class GlyphTest < MiniTest::Test
|
6
|
+
def test_glyphs
|
7
|
+
assert_equal("\x1b[33m⭑\x1b[0m", Glyph::STAR.to_s)
|
8
|
+
assert_equal("\x1b[94m𝒾\x1b[0m", Glyph::INFO.to_s)
|
9
|
+
assert_equal("\x1b[94m?\x1b[0m", Glyph::QUESTION.to_s)
|
10
|
+
assert_equal("\x1b[32m✓\x1b[0m", Glyph::CHECK.to_s)
|
11
|
+
assert_equal("\x1b[31m✗\x1b[0m", Glyph::X.to_s)
|
12
|
+
assert_equal("\x1b[97m🐛\x1b[0m", Glyph::BUG.to_s)
|
13
|
+
assert_equal("\x1b[33m»\x1b[0m", Glyph::CHEVRON.to_s)
|
14
|
+
|
15
|
+
assert_equal(Glyph::STAR, Glyph.lookup('*'))
|
16
|
+
assert_equal(Glyph::INFO, Glyph.lookup('i'))
|
17
|
+
assert_equal(Glyph::QUESTION, Glyph.lookup('?'))
|
18
|
+
assert_equal(Glyph::CHECK, Glyph.lookup('v'))
|
19
|
+
assert_equal(Glyph::X, Glyph.lookup('x'))
|
20
|
+
assert_equal(Glyph::BUG, Glyph.lookup('b'))
|
21
|
+
assert_equal(Glyph::CHEVRON, Glyph.lookup('>'))
|
22
|
+
|
23
|
+
assert_raises(Glyph::InvalidGlyphHandle) do
|
24
|
+
Glyph.lookup('$')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_plain_glyphs
|
29
|
+
with_os_mock_and_reload(
|
30
|
+
CLI::UI::OS::Windows,
|
31
|
+
:Glyph,
|
32
|
+
File.join(File.dirname(__FILE__), '../../../lib/cli/ui/glyph.rb')
|
33
|
+
) do
|
34
|
+
assert_equal("\x1b[33m*\x1b[0m", Glyph::STAR.to_s)
|
35
|
+
assert_equal("\x1b[94mi\x1b[0m", Glyph::INFO.to_s)
|
36
|
+
assert_equal("\x1b[94m?\x1b[0m", Glyph::QUESTION.to_s)
|
37
|
+
assert_equal("\x1b[32m√\x1b[0m", Glyph::CHECK.to_s)
|
38
|
+
assert_equal("\x1b[31mX\x1b[0m", Glyph::X.to_s)
|
39
|
+
assert_equal("\x1b[97m!\x1b[0m", Glyph::BUG.to_s)
|
40
|
+
assert_equal("\x1b[33m»\x1b[0m", Glyph::CHEVRON.to_s)
|
41
|
+
|
42
|
+
assert_equal(Glyph::STAR, Glyph.lookup('*'))
|
43
|
+
assert_equal(Glyph::INFO, Glyph.lookup('i'))
|
44
|
+
assert_equal(Glyph::QUESTION, Glyph.lookup('?'))
|
45
|
+
assert_equal(Glyph::CHECK, Glyph.lookup('v'))
|
46
|
+
assert_equal(Glyph::X, Glyph.lookup('x'))
|
47
|
+
assert_equal(Glyph::BUG, Glyph.lookup('b'))
|
48
|
+
assert_equal(Glyph::CHEVRON, Glyph.lookup('>'))
|
49
|
+
|
50
|
+
assert_raises(Glyph::InvalidGlyphHandle) do
|
51
|
+
Glyph.lookup('$')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_useful_exception
|
57
|
+
e = begin
|
58
|
+
Glyph.lookup('$')
|
59
|
+
rescue => e
|
60
|
+
e
|
61
|
+
end
|
62
|
+
assert_match(/invalid glyph handle: \$/, e.message) # error
|
63
|
+
assert_match(/Glyph\.available/, e.message) # where to find colors
|
64
|
+
assert_match(/\*/, e.message) # list of valid colors
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
class PrinterTest < MiniTest::Test
|
6
|
+
def test_puts_color
|
7
|
+
out, _ = capture_io do
|
8
|
+
CLI::UI::StdoutRouter.ensure_activated
|
9
|
+
assert(Printer.puts('foo', frame_color: :red))
|
10
|
+
end
|
11
|
+
|
12
|
+
assert_equal("\e[0mfoo\n", out)
|
13
|
+
end
|
14
|
+
|
15
|
+
# NOTE: The spacing in the assertion of this test is important for
|
16
|
+
# downstream projects and should be maintained.
|
17
|
+
def test_puts_color_frame
|
18
|
+
Frame.open('test') do
|
19
|
+
out, _ = capture_io do
|
20
|
+
CLI::UI::StdoutRouter.ensure_activated
|
21
|
+
assert(Printer.puts('foo', frame_color: :red))
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_equal("\e[31m┃ \e[0m\e[0mfoo\n", out)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_frame_with_long_texts
|
29
|
+
overlong_preamble = 'This is a long preamble! '
|
30
|
+
overlong_preamble *= (CLI::UI::Terminal.width / overlong_preamble.length).floor + 1
|
31
|
+
|
32
|
+
overlong_suffix = 'This overlaps the suffix! '
|
33
|
+
overlong_suffix *= (CLI::UI::Terminal.width / overlong_suffix.length).floor + 1
|
34
|
+
|
35
|
+
Frame.open(overlong_preamble, success_text: overlong_suffix) do
|
36
|
+
out, _ = capture_io do
|
37
|
+
CLI::UI::StdoutRouter.ensure_activated
|
38
|
+
assert(Printer.puts('foo', frame_color: :red))
|
39
|
+
end
|
40
|
+
|
41
|
+
assert_equal("\e[31m┃ \e[0m\e[0mfoo\n", out)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_puts_stream
|
46
|
+
_, err = capture_io do
|
47
|
+
assert(Printer.puts('foo', to: $stderr, format: false))
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_equal("foo\n", err)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_puts_format
|
54
|
+
out, _ = capture_io do
|
55
|
+
assert(Printer.puts('{{x}} foo'))
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_equal("\e[0;31m✗\e[0m foo\n", out)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_puts_pipe
|
62
|
+
IO.pipe do |r, w|
|
63
|
+
assert(Printer.puts('foo', to: w, format: false))
|
64
|
+
assert_equal("foo\n", r.gets)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_puts_pipe_closed
|
69
|
+
IO.pipe do |_r, w|
|
70
|
+
w.close
|
71
|
+
assert_raises(IOError) do
|
72
|
+
Printer.puts('foo', to: w, graceful: false)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_puts_graceful
|
78
|
+
IO.pipe do |r, w|
|
79
|
+
w.close
|
80
|
+
refute(Printer.puts('foo', to: w, graceful: true))
|
81
|
+
assert_nil(r.gets)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_encoding
|
86
|
+
msg = 'é'.force_encoding(Encoding::ISO_8859_1)
|
87
|
+
out, _ = capture_io do
|
88
|
+
assert(Printer.puts(msg, encoding: nil, format: false))
|
89
|
+
end
|
90
|
+
refute_equal(msg + "\n", out) # It doesn't work
|
91
|
+
assert_equal(msg.encode(Encoding::UTF_8) + "\n", out)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_encoding_ut8
|
95
|
+
msg = 'é'.force_encoding(Encoding::ISO_8859_1)
|
96
|
+
out, _ = capture_io do
|
97
|
+
assert(Printer.puts(msg, format: false))
|
98
|
+
end
|
99
|
+
assert_equal(msg + "\n", out)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
class ProgressTest < MiniTest::Test
|
6
|
+
def test_tick_with_percent
|
7
|
+
assert_bar(set_percent: 0.1, expected_filled: 1, expected_unfilled: 9, suffix: ' 10% ')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_tick_with_set_percent
|
11
|
+
assert_bar(set_percent: 0.9, expected_filled: 9, expected_unfilled: 1, suffix: ' 90% ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_tick_with_set_percent_above_100_percent_is_set_to_100_percent
|
15
|
+
assert_bar(set_percent: 2.0, expected_filled: 10, suffix: ' 100%')
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_tick_with_percent_change_to_above_100_percent_is_set_to_100_percent
|
19
|
+
assert_bar(percent: 2.0, expected_filled: 10, suffix: ' 100%')
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_tick_with_set_percent_and_percent_raises
|
23
|
+
assert_raises(ArgumentError) do
|
24
|
+
bar = Progress.new(width: 10)
|
25
|
+
bar.tick(percent: 0.5, set_percent: 0.9)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_bar(percent: nil, set_percent: nil, expected_filled: 0, expected_unfilled: 0, suffix: '')
|
30
|
+
expected_bar = "\e[0m\e[46m#{" " * expected_filled}\e[1;47m#{" " * expected_unfilled}\e[0m#{suffix}"
|
31
|
+
|
32
|
+
params = {}
|
33
|
+
params[:percent] = percent if percent
|
34
|
+
params[:set_percent] = set_percent if set_percent
|
35
|
+
|
36
|
+
out, = capture_io do
|
37
|
+
bar = Progress.new(width: 10 + suffix.size) # each 10% is one box with this width
|
38
|
+
bar.tick(**params)
|
39
|
+
assert_equal(expected_bar, bar.to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_equal(expected_bar + "\e[1A\e[1G\n", out)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'cli/ui/prompt/options_handler'
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
module Prompt
|
7
|
+
class OptionsHandlerTest < MiniTest::Test
|
8
|
+
def test_initialize
|
9
|
+
handler = OptionsHandler.new
|
10
|
+
|
11
|
+
assert_empty(handler.options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_option
|
15
|
+
handler = OptionsHandler.new
|
16
|
+
|
17
|
+
handler.option('a') {}
|
18
|
+
handler.option('b') {}
|
19
|
+
handler.option('c') {}
|
20
|
+
|
21
|
+
assert_equal(['a', 'b', 'c'], handler.options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_call
|
25
|
+
handler = OptionsHandler.new
|
26
|
+
procedure_called = false
|
27
|
+
procedure = proc do |selection|
|
28
|
+
procedure_called = true
|
29
|
+
selection
|
30
|
+
end
|
31
|
+
|
32
|
+
handler.option('a', &procedure)
|
33
|
+
assert_equal('a', handler.call('a'))
|
34
|
+
assert(procedure_called)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,348 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
require 'readline'
|
4
|
+
require 'timeout'
|
5
|
+
require 'open3'
|
6
|
+
|
7
|
+
module CLI
|
8
|
+
module UI
|
9
|
+
class PromptTest < MiniTest::Test
|
10
|
+
# ^C is not handled; raises Interrupt, which may be handled by caller.
|
11
|
+
def test_confirm_sigint
|
12
|
+
jruby_skip('SIGINT shuts down the JVM instead of raising Interrupt')
|
13
|
+
|
14
|
+
run_in_process(<<~RUBY)
|
15
|
+
begin
|
16
|
+
CLI::UI::Prompt.confirm('question')
|
17
|
+
rescue Interrupt
|
18
|
+
puts 'sentinel'
|
19
|
+
end
|
20
|
+
RUBY
|
21
|
+
|
22
|
+
wait_for_output_to_include('question')
|
23
|
+
kill_process
|
24
|
+
|
25
|
+
assert_output_includes('sentinel')
|
26
|
+
end
|
27
|
+
|
28
|
+
# ^C is not handled; raises Interrupt, which may be handled by caller.
|
29
|
+
def test_ask_free_form_sigint
|
30
|
+
jruby_skip('SIGINT shuts down the JVM instead of raising Interrupt')
|
31
|
+
|
32
|
+
run_in_process(<<~RUBY)
|
33
|
+
begin
|
34
|
+
CLI::UI::Prompt.ask('question')
|
35
|
+
rescue Interrupt
|
36
|
+
puts 'sentinel'
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
wait_for_output_to_include('question')
|
41
|
+
kill_process
|
42
|
+
|
43
|
+
assert_output_includes('sentinel')
|
44
|
+
end
|
45
|
+
|
46
|
+
# ^C is not handled; raises Interrupt, which may be handled by caller.
|
47
|
+
def test_ask_interactive_sigint
|
48
|
+
jruby_skip('SIGINT shuts down the JVM instead of raising Interrupt')
|
49
|
+
|
50
|
+
run_in_process(<<~RUBY)
|
51
|
+
begin
|
52
|
+
CLI::UI::Prompt.ask('question', options: %w(a b))
|
53
|
+
rescue Interrupt
|
54
|
+
puts 'sentinel'
|
55
|
+
end
|
56
|
+
RUBY
|
57
|
+
|
58
|
+
wait_for_output_to_include('question')
|
59
|
+
kill_process
|
60
|
+
|
61
|
+
assert_output_includes('sentinel')
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_confirm_happy_path
|
65
|
+
run_in_process('puts CLI::UI::Prompt.confirm("q")')
|
66
|
+
write('y')
|
67
|
+
assert_output_includes('true')
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_confirm_default_no
|
71
|
+
run_in_process('puts CLI::UI::Prompt.confirm("q", default: false)')
|
72
|
+
write("\n")
|
73
|
+
assert_output_includes('false')
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_confirm_invalid
|
77
|
+
run_in_process('puts CLI::UI::Prompt.confirm("q")')
|
78
|
+
write('ryn')
|
79
|
+
assert_output_includes('true')
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_confirm_no_match_internal
|
83
|
+
run_in_process('puts CLI::UI::Prompt.confirm("q", default: false)')
|
84
|
+
write('xn')
|
85
|
+
assert_output_includes('false')
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_output_includes_instructions
|
89
|
+
run_in_process('CLI::UI::Prompt.confirm("q")')
|
90
|
+
write('y')
|
91
|
+
assert_output_includes('(Choose with ↑ ↓ ⏎)')
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_windows_instructions
|
95
|
+
# Windows doesn't detect presses on the arrow keys when picking an option, so we don't show the instruction text
|
96
|
+
# for them.
|
97
|
+
run_in_process(<<~RUBY)
|
98
|
+
CLI::UI::OS # Force the file to load before redefining ::current
|
99
|
+
module CLI
|
100
|
+
module UI
|
101
|
+
module OS
|
102
|
+
def self.current
|
103
|
+
CLI::UI::OS::Windows
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
CLI::UI::Prompt.confirm("q")
|
109
|
+
RUBY
|
110
|
+
write('y')
|
111
|
+
assert_output_includes("(Navigate up with 'k' and down with 'j', press Enter to select)")
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_ask_free_form_happy_path
|
115
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q")}--"')
|
116
|
+
write("asdf\n")
|
117
|
+
assert_output_includes('--asdf--')
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_ask_free_form_empty_answer_allowed
|
121
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q")}--"')
|
122
|
+
write("\n")
|
123
|
+
assert_output_includes('----')
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_ask_free_form_empty_answer_rejected
|
127
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", allow_empty: false)}--"')
|
128
|
+
write("\nasdf\n")
|
129
|
+
assert_output_includes('--asdf--')
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_ask_free_form_no_filename_completion
|
133
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q")}--"')
|
134
|
+
write("/dev/nul\t\n")
|
135
|
+
assert_output_includes('--/dev/nul--')
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_ask_free_form_filename_completion
|
139
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", is_file: true)}--"')
|
140
|
+
write("/dev/nul\t\n")
|
141
|
+
assert_output_includes('--/dev/null--')
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_ask_free_form_default
|
145
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", default: "asdf")}--"')
|
146
|
+
write("\n")
|
147
|
+
assert_output_includes('--asdf--')
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_ask_free_form_default_nondefault
|
151
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", default: "asdf")}--"')
|
152
|
+
write("zxcv\n")
|
153
|
+
assert_output_includes('--zxcv--')
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_ask_invalid_kwargs
|
157
|
+
kwargsets = [
|
158
|
+
{ options: ['a'], default: 'a' },
|
159
|
+
{ options: ['a'], is_file: true },
|
160
|
+
]
|
161
|
+
|
162
|
+
kwargsets.each do |kwargs|
|
163
|
+
error = assert_raises(ArgumentError) { Prompt.ask('q', **kwargs) }
|
164
|
+
assert_equal('conflicting arguments: options provided with default or is_file', error.message)
|
165
|
+
end
|
166
|
+
|
167
|
+
error = assert_raises(ArgumentError) do
|
168
|
+
Prompt.ask('q', default: 'a', allow_empty: false)
|
169
|
+
end
|
170
|
+
assert_equal('conflicting arguments: default enabled but allow_empty is false', error.message)
|
171
|
+
|
172
|
+
error = assert_raises(ArgumentError) do
|
173
|
+
Prompt.ask('q', default: 'b') {}
|
174
|
+
end
|
175
|
+
assert_equal('conflicting arguments: options provided with default or is_file', error.message)
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_ask_interactive_conflicting_arguments
|
179
|
+
error = assert_raises(ArgumentError) do
|
180
|
+
Prompt.ask('q', options: %w(a b)) { |h| h.option('a') }
|
181
|
+
end
|
182
|
+
assert_equal('conflicting arguments: options and block given', error.message)
|
183
|
+
|
184
|
+
error = assert_raises(ArgumentError) do
|
185
|
+
Prompt.ask('q', options: %w(a b), multiple: true, default: %w(b c)) { |h| h.option('a') }
|
186
|
+
end
|
187
|
+
assert_equal('conflicting arguments: default should only include elements present in options', error.message)
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_ask_interactive_insufficient_options
|
191
|
+
exception = assert_raises(ArgumentError) do
|
192
|
+
Prompt.ask('q', options: %w())
|
193
|
+
end
|
194
|
+
assert_equal('insufficient options', exception.message)
|
195
|
+
|
196
|
+
exception = assert_raises(ArgumentError) do
|
197
|
+
Prompt.ask('q') { |_h| {} }
|
198
|
+
end
|
199
|
+
assert_equal('insufficient options', exception.message)
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_ask_interactive_with_block
|
203
|
+
run_in_process(<<~RUBY)
|
204
|
+
puts(CLI::UI::Prompt.ask('q') do |h|
|
205
|
+
h.option('a') { |_a| 'a was selected' }
|
206
|
+
h.option('b') { |_a| 'b was selected' }
|
207
|
+
end)
|
208
|
+
RUBY
|
209
|
+
write('1')
|
210
|
+
|
211
|
+
assert_output_includes('a was selected')
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_ask_interactive_with_vim_bound_arrows
|
215
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", options: %w(a b))}--"')
|
216
|
+
write('j ')
|
217
|
+
assert_output_includes('--b--')
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_ask_interactive_escape
|
221
|
+
run_in_process(<<~RUBY)
|
222
|
+
begin
|
223
|
+
CLI::UI::Prompt.ask("q", options: %w(a b))
|
224
|
+
rescue Interrupt # jruby can rescue this one since we raise it rather than receiving it as a signal
|
225
|
+
puts 'sentinel'
|
226
|
+
end
|
227
|
+
RUBY
|
228
|
+
|
229
|
+
write("\e;")
|
230
|
+
assert_output_includes('sentinel')
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_ask_interactive_invalid_input
|
234
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", options: %w(a b))}--"')
|
235
|
+
write('3nan2')
|
236
|
+
assert_output_includes('--b--')
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_ask_interactive_with_blank_option
|
240
|
+
run_in_process(<<~RUBY)
|
241
|
+
puts(CLI::UI::Prompt.ask('q') do |h|
|
242
|
+
h.option('a') { |_a| 'a was selected' }
|
243
|
+
h.option('') { |_a| 'b was selected' }
|
244
|
+
end)
|
245
|
+
RUBY
|
246
|
+
write('jj ')
|
247
|
+
assert_output_includes('a was selected')
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_ask_interactive_filter_options
|
251
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", options: %w(abcd xyz))}--"')
|
252
|
+
write("fz\n")
|
253
|
+
assert_output_includes('--xyz--')
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_ask_interactive_line_selection
|
257
|
+
run_in_process('puts "--#{CLI::UI::Prompt.ask("q", options: (1..15).map(&:to_s))}--"')
|
258
|
+
write("e10\n")
|
259
|
+
assert_output_includes('--10--')
|
260
|
+
end
|
261
|
+
|
262
|
+
def test_ask_multiple
|
263
|
+
run_in_process('puts CLI::UI::Prompt.ask("q", options: (1..15).map(&:to_s), multiple: true).inspect')
|
264
|
+
write('1350')
|
265
|
+
assert_output_includes(['1', '3', '5'].inspect)
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_ask_multiple_with_handler
|
269
|
+
run_in_process(<<~RUBY)
|
270
|
+
puts(CLI::UI::Prompt.ask('q', multiple: true) do |handler|
|
271
|
+
('1'..'10').each do |i|
|
272
|
+
handler.option(i) { i }
|
273
|
+
end
|
274
|
+
end.inspect)
|
275
|
+
RUBY
|
276
|
+
write('1350')
|
277
|
+
assert_output_includes(['1', '3', '5'].inspect)
|
278
|
+
end
|
279
|
+
|
280
|
+
def test_ask_multiple_with_default_values
|
281
|
+
run_in_process(
|
282
|
+
'puts CLI::UI::Prompt.ask("q", options: (1..15).map(&:to_s), multiple: true, default: %w(2 3)).inspect'
|
283
|
+
)
|
284
|
+
write('120')
|
285
|
+
assert_output_includes(['1', '3'].inspect)
|
286
|
+
end
|
287
|
+
|
288
|
+
private
|
289
|
+
|
290
|
+
def run_in_process(code)
|
291
|
+
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(
|
292
|
+
'ruby',
|
293
|
+
'-r',
|
294
|
+
'bundler/setup',
|
295
|
+
'-r',
|
296
|
+
'cli/ui',
|
297
|
+
'-e',
|
298
|
+
"$stdout.sync = true; $stderr.sync = true; #{code}"
|
299
|
+
)
|
300
|
+
end
|
301
|
+
|
302
|
+
def wait_for_output_to_include(text)
|
303
|
+
@output = ''
|
304
|
+
until @output.include?(text)
|
305
|
+
begin
|
306
|
+
@output += @stdout.read_nonblock(100)
|
307
|
+
rescue IO::WaitReadable
|
308
|
+
IO.select([@stdout])
|
309
|
+
retry
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def write(text)
|
315
|
+
@stdin.write(text)
|
316
|
+
end
|
317
|
+
|
318
|
+
def kill_process
|
319
|
+
Process.kill('INT', @wait_thr[:pid])
|
320
|
+
end
|
321
|
+
|
322
|
+
def clean_up
|
323
|
+
@wait_thr.value
|
324
|
+
yield if block_given?
|
325
|
+
ensure
|
326
|
+
@stdin.close
|
327
|
+
@stderr.close
|
328
|
+
@stdout.close
|
329
|
+
end
|
330
|
+
|
331
|
+
def assert_output_includes(text)
|
332
|
+
clean_up do
|
333
|
+
assert_includes(@stdout.read, text)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def assert_error_includes(text)
|
338
|
+
clean_up do
|
339
|
+
assert_includes(@stderr.read, text)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def jruby_skip(message)
|
344
|
+
skip(message) if RUBY_ENGINE.include?('jruby')
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|