infopark-user_io 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0c95a85359de4b5840ee1053f7e9abc83d8b91988ac1f7d9611c9f9f8d92f7d
4
- data.tar.gz: 68444632af92c73fbd16368b1a4f58142dcaf4c1e5d348e7b52d75c657d286f5
3
+ metadata.gz: b20bdfdfb2bd29180da2445be83e82295a71b4a30276450351c08ef114ed9da5
4
+ data.tar.gz: cfe242fc1e3a48b1c0e2b7d0ab40d8663178b6425f9e8b4215c13a67bef85b29
5
5
  SHA512:
6
- metadata.gz: cf5deca658e0d11c2d6c00b1dba29f87a2515749c6e01c2da1c5a39033a1d35a7c847d2375dd23f6f971c1a8359233f1d3ff339819216ae369a89e89e5b70694
7
- data.tar.gz: 0e1f7369c00f4b06d66c9e0f396b1966e474a964d8a3a7d8dce05856da575ea31c37c3653217048949e163e0ad203f2db643f124e4e531b269c678799a8efe2e
6
+ metadata.gz: 4d75731717e150e66512d1aecab2193938aa159cb276eaa9cae1abbae05753d88559e01b57216f210dfca8f3163a5cb5bee699593e1d5f740e51e7e95c6c3037
7
+ data.tar.gz: cb512e8b47045e191bcda99ce905b0c5e8a8dffefbd9f489ee0b543841bd75cef8e473e31caa9eddb5caebd97a67c7b5b85a9e91b1ef032894fa443e1ecd9bc2
@@ -3,7 +3,7 @@
3
3
  require_relative "../user_io"
4
4
 
5
5
  module Infopark
6
- module UserIO
6
+ class UserIO
7
7
  module Global
8
8
  def user_io
9
9
  Infopark::UserIO.global
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Infopark
4
4
  class UserIO
5
- VERSION = "1.3.0"
5
+ VERSION = "1.4.0"
6
6
  end
7
7
  end
@@ -76,88 +76,39 @@ module Infopark
76
76
  @line_pending = {}
77
77
  end
78
78
 
79
- def tell(*texts, newline: true, **line_options)
80
- lines = texts.flatten.map {|text| text.to_s.split("\n", -1) }.flatten
81
-
82
- lines[0...-1].each {|line| tell_line(line, **line_options) }
83
- tell_line(lines.last, newline:, **line_options)
84
- end
85
-
86
- def tell_pty_stream(stream, **color_options)
87
- color_prefix, color_postfix = compute_color(**color_options)
88
- write_raw(output_prefix) unless line_pending?
89
- write_raw(color_prefix)
90
- nl_pending = false
91
- uncolored_prefix = "#{color_postfix}#{output_prefix}#{color_prefix}"
92
- until stream.eof?
93
- chunk = stream.read_nonblock(100)
94
- next if chunk.empty?
95
-
96
- write_raw("\n#{uncolored_prefix}") if nl_pending
97
- chunk.chop! if (nl_pending = chunk.end_with?("\n"))
98
- chunk.gsub!(/([\r\n])/, "\\1#{uncolored_prefix}")
99
- write_raw(chunk)
100
- end
101
- write_raw("\n") if nl_pending
102
- write_raw(color_postfix)
103
- line_pending!(false)
104
- end
105
-
106
- def warn(*text)
107
- tell(*text, color: :yellow, bright: true)
108
- end
109
-
110
- def tell_error(e, **options)
111
- tell(e, **options, color: :red, bright: true)
112
- tell(e.backtrace, **options, color: :red) if Exception === e
79
+ def <<(msg)
80
+ tell(msg.chomp, newline: msg.end_with?("\n"))
113
81
  end
114
82
 
115
- def acknowledge(*text)
116
- tell("-" * 80)
117
- tell(*text, color: :cyan, bright: true)
118
- tell("-" * 80)
119
- tell("Please press ENTER to continue.")
83
+ def acknowledge(*texts, **options)
84
+ tell("-" * 80, **options)
85
+ tell(*texts, **options, color: :cyan, bright: true)
86
+ tell("-" * 80, **options)
87
+ tell("Please press ENTER to continue.", **options)
120
88
  read_line
89
+ nil
121
90
  end
122
91
 
123
- def ask(*text, default: nil, expected: "yes")
92
+ def ask(*texts, default: nil, expected: "yes", **tell_options)
124
93
  # TODO
125
94
  # - implementation error if default not boolean or nil
126
95
  # - implementation error if expected not "yes" or "no"
127
- tell("-" * 80)
128
- tell(*text, color: :cyan, bright: true)
129
- tell("-" * 80)
96
+ tell("-" * 80, **tell_options)
97
+ tell(*texts, **tell_options, color: :cyan, bright: true)
98
+ tell("-" * 80, **tell_options)
130
99
  default_answer = default ? "yes" : "no" unless default.nil?
131
- tell("(yes/no) #{default_answer && "[#{default_answer}] "}> ", newline: false)
132
- until %w(yes no).include?(answer = read_line.strip.downcase)
100
+ tell("(yes/no) #{default_answer && "[#{default_answer}] "}> ", **tell_options, newline: false)
101
+ until %w(yes no).include?((answer = read_line.strip.downcase))
133
102
  if answer.empty?
134
103
  answer = default_answer
135
104
  break
136
105
  end
137
- tell("I couldn't understand “#{answer}”.", newline: false, color: :red, bright: true)
138
- tell(" > ", newline: false)
106
+ tell("I couldn't understand “#{answer}”.", **tell_options, newline: false, color: :red, bright: true)
107
+ tell(" > ", **tell_options, newline: false)
139
108
  end
140
109
  answer == expected
141
110
  end
142
111
 
143
- def listen(prompt = nil, **options)
144
- prompt << " " if prompt
145
- tell("#{prompt}> ", **options, newline: false)
146
- read_line.strip
147
- end
148
-
149
- def confirm(*text)
150
- ask(*text) or raise(Aborted)
151
- end
152
-
153
- def new_progress(label)
154
- Progress.new(label, self)
155
- end
156
-
157
- def start_progress(label)
158
- new_progress(label).tap(&:start)
159
- end
160
-
161
112
  def background_other_threads
162
113
  return if @foreground_thread
163
114
 
@@ -165,22 +116,8 @@ module Infopark
165
116
  @foreground_thread = Thread.current
166
117
  end
167
118
 
168
- def foreground
169
- return unless @foreground_thread
170
-
171
- @background_data.each(&$stdout.method(:write))
172
- @foreground_thread = nil
173
- # take over line_pending from background
174
- @line_pending[false] = @line_pending[true]
175
- @line_pending[true] = false
176
- end
177
-
178
- def <<(msg)
179
- tell(msg.chomp, newline: msg.end_with?("\n"))
180
- end
181
-
182
- def tty?
183
- $stdout.tty?
119
+ def confirm(*text, **options)
120
+ ask(*text, **options) or raise(Aborted)
184
121
  end
185
122
 
186
123
  def edit_file(kind_of_data, filename = nil, template: nil)
@@ -200,6 +137,26 @@ module Infopark
200
137
  File.read(filename)
201
138
  end
202
139
 
140
+ def foreground
141
+ return unless @foreground_thread
142
+
143
+ @background_data.each(&$stdout.method(:write))
144
+ @foreground_thread = nil
145
+ # take over line_pending from background
146
+ @line_pending[false] = @line_pending[true]
147
+ @line_pending[true] = false
148
+ end
149
+
150
+ def listen(prompt = nil, **options)
151
+ prompt << " " if prompt
152
+ tell("#{prompt}> ", **options, newline: false)
153
+ read_line.strip
154
+ end
155
+
156
+ def new_progress(label)
157
+ Progress.new(label, self)
158
+ end
159
+
203
160
  def select(description, items, item_describer: :to_s, default: nil)
204
161
  return if items.empty?
205
162
 
@@ -247,50 +204,54 @@ module Infopark
247
204
  choice
248
205
  end
249
206
 
250
- private
251
-
252
- def background?
253
- !!@foreground_thread && @foreground_thread != Thread.current
207
+ def start_progress(label)
208
+ new_progress(label).tap(&:start)
254
209
  end
255
210
 
256
- def wait_for_foreground
257
- sleep(0.1) while background?
258
- end
211
+ def tell(*texts, newline: true, **line_options)
212
+ lines = texts.flatten.map {|text| text.to_s.split("\n", -1) }.flatten
259
213
 
260
- def output_prefix
261
- @output_prefix || @output_prefix_proc&.call
214
+ lines[0...-1].each {|line| tell_line(line, **line_options) }
215
+ tell_line(lines.last, newline:, **line_options)
262
216
  end
263
217
 
264
- def read_line
265
- wait_for_foreground if background?
266
- @line_pending[false] = false
267
- $stdin.gets.chomp
218
+ def tell_error(e, **options)
219
+ tell(e, **options, color: :red, bright: true)
220
+ tell(e.backtrace, **options, color: :red) if Exception === e
268
221
  end
269
222
 
270
- def tell_line(line, newline: true, prefix: true, **color_options)
271
- line_prefix, line_postfix = compute_color(**color_options)
272
- prefix = false if line_pending?
273
-
274
- out_line = "#{output_prefix if prefix}#{line_prefix}#{line}#{line_postfix}#{"\n" if newline}"
275
- write_raw(out_line)
223
+ def tell_pty_stream(stream, **color_options)
224
+ color_prefix, color_postfix = compute_color(**color_options)
225
+ write_raw(output_prefix) unless line_pending?
226
+ write_raw(color_prefix)
227
+ nl_pending = false
228
+ uncolored_prefix = "#{color_postfix}#{output_prefix}#{color_prefix}"
229
+ until stream.eof?
230
+ chunk = stream.read_nonblock(100)
231
+ next if chunk.empty?
276
232
 
277
- line_pending!(!newline)
233
+ write_raw("\n#{uncolored_prefix}") if nl_pending
234
+ chunk.chop! if (nl_pending = chunk.end_with?("\n"))
235
+ chunk.gsub!(/([\r\n])/, "\\1#{uncolored_prefix}")
236
+ write_raw(chunk)
237
+ end
238
+ write_raw("\n") if nl_pending
239
+ write_raw(color_postfix)
240
+ line_pending!(false)
278
241
  end
279
242
 
280
- def write_raw(bytes)
281
- if background?
282
- @background_data << bytes
283
- else
284
- $stdout.write(bytes)
285
- end
243
+ def tty?
244
+ $stdout.tty?
286
245
  end
287
246
 
288
- def line_pending?
289
- @line_pending[background?]
247
+ def warn(*texts, **options)
248
+ tell(*texts, **options, color: :yellow, bright: true)
290
249
  end
291
250
 
292
- def line_pending!(value)
293
- @line_pending[background?] = value
251
+ private
252
+
253
+ def background?
254
+ !!@foreground_thread && @foreground_thread != Thread.current
294
255
  end
295
256
 
296
257
  def compute_color(**options)
@@ -305,11 +266,39 @@ module Infopark
305
266
  "\033[#{parameters.join(';')}#{function}"
306
267
  end
307
268
 
269
+ def line_pending?
270
+ @line_pending[background?]
271
+ end
272
+
273
+ def line_pending!(value)
274
+ @line_pending[background?] = value
275
+ end
276
+
277
+ def output_prefix
278
+ @output_prefix || @output_prefix_proc&.call
279
+ end
280
+
281
+ def read_line
282
+ wait_for_foreground if background?
283
+ @line_pending[false] = false
284
+ $stdin.gets.chomp
285
+ end
286
+
308
287
  # SGR: Select Graphic Rendition … far too long for a function name ;)
309
288
  def sgr_sequence(*parameters)
310
289
  control_sequence(*parameters, :m)
311
290
  end
312
291
 
292
+ def tell_line(line, newline: true, prefix: true, **color_options)
293
+ line_prefix, line_postfix = compute_color(**color_options)
294
+ prefix = false if line_pending?
295
+
296
+ out_line = "#{output_prefix if prefix}#{line_prefix}#{line}#{line_postfix}#{"\n" if newline}"
297
+ write_raw(out_line)
298
+
299
+ line_pending!(!newline)
300
+ end
301
+
313
302
  def text_color(color: nil, bright: nil, faint: nil, italic: nil, underline: nil)
314
303
  return if color.nil? && bright.nil?
315
304
 
@@ -348,6 +337,18 @@ module Infopark
348
337
  end
349
338
  sgr_sequence(*sequence)
350
339
  end
340
+
341
+ def wait_for_foreground
342
+ sleep(0.1) while background?
343
+ end
344
+
345
+ def write_raw(bytes)
346
+ if background?
347
+ @background_data << bytes
348
+ else
349
+ $stdout.write(bytes)
350
+ end
351
+ end
351
352
  end
352
353
  end
353
354
 
data/spec/user_io_spec.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
4
+
3
5
  module Infopark
4
6
  RSpec.describe(UserIO) do
5
7
  let(:options) { {} }
@@ -28,19 +30,35 @@ module Infopark
28
30
  end
29
31
 
30
32
  describe "#acknowledge" do
31
- before { allow($stdin).to(receive(:gets).and_return("\n")) }
33
+ subject(:acknowledge) { user_io.acknowledge(*ack_texts, **ack_options) }
32
34
 
33
- let(:message) { "Some important statement." }
35
+ before do
36
+ allow($stdin).to(receive(:gets).and_return("\n"))
37
+ allow(user_io).to(receive(:tell))
38
+ end
34
39
 
35
- subject(:acknowledge) { user_io.acknowledge(message) }
40
+ let(:ack_options) do
41
+ [
42
+ {newline: true},
43
+ {prefix: "foo", newline: false},
44
+ {},
45
+ ].sample
46
+ end
47
+ let(:ack_texts) do
48
+ [
49
+ ["Some important statement."],
50
+ ["Some", "important", "statement!"],
51
+ [],
52
+ ].sample
53
+ end
36
54
 
37
- it "presents the message (colorized)" do
38
- expect($stdout).to(receive(:write).with("\e[1;36mSome important statement.\e[22;39m\n"))
55
+ it "tells the message (colorized)" do
56
+ expect(user_io).to(receive(:tell).with(*ack_texts, **ack_options, color: :cyan, bright: true))
39
57
  acknowledge
40
58
  end
41
59
 
42
60
  it "asks for pressing “Enter”" do
43
- expect($stdout).to(receive(:write).with("Please press ENTER to continue.\n"))
61
+ expect(user_io).to(receive(:tell).with("Please press ENTER to continue.", **ack_options))
44
62
  acknowledge
45
63
  end
46
64
 
@@ -48,21 +66,73 @@ module Infopark
48
66
  expect($stdin).to(receive(:gets).and_return("\n"))
49
67
  acknowledge
50
68
  end
69
+
70
+ it "returns nil" do
71
+ expect(acknowledge).to(be_nil)
72
+ end
51
73
  end
52
74
 
53
75
  describe "#ask" do
54
- before { allow($stdin).to(receive(:gets).and_return("yes\n")) }
76
+ subject(:ask) { user_io.ask(*ask_texts, **ask_options.merge(tell_options)) }
55
77
 
78
+ before do
79
+ allow($stdin).to(receive(:gets).and_return("#{answer}\n"))
80
+ allow(user_io).to(receive(:tell))
81
+ end
82
+
83
+ let(:answer) { "yes" }
84
+ let(:ask_texts) do
85
+ [
86
+ ["do you want to?"],
87
+ ["do", "you", "want", "to?"],
88
+ [],
89
+ ].sample
90
+ end
56
91
  let(:ask_options) { {} }
57
- let(:question) { "do you want to?" }
92
+ let(:tell_options) do
93
+ [
94
+ {italic: true},
95
+ {prefix: "foo", bold: false},
96
+ {},
97
+ ].sample
98
+ end
58
99
 
59
- subject(:ask) { user_io.ask(*Array(question), **ask_options) }
100
+ shared_examples_for "any question" do |invert_answer: false|
101
+ it "tells the message (colorized)" do
102
+ expect(user_io).to(receive(:tell).with(*ask_texts, **tell_options, color: :cyan, bright: true))
103
+ ask
104
+ end
105
+
106
+ it "requests input" do
107
+ expect($stdin).to(receive(:gets).and_return("\n"))
108
+ ask
109
+ end
110
+
111
+ context "when answer is “yes”" do
112
+ let(:answer) { "yes" }
113
+
114
+ it { is_expected.to(be(invert_answer ? false : true))}
115
+ end
116
+
117
+ context "when answer is “no”" do
118
+ let(:answer) { "no" }
119
+
120
+ it { is_expected.to(be(invert_answer ? true : false))}
121
+ end
60
122
 
61
- shared_examples_for "any question" do
62
123
  # TODO
63
124
  # it_behaves_like "handling valid answer"
64
125
  # it_behaves_like "handling invalid input"
65
- # it_behaves_like "printing prefix on every line"
126
+ end
127
+
128
+ it "asks for answer" do
129
+ expect(user_io).to(receive(:tell).with("(yes/no) > ", **tell_options, newline: false))
130
+ ask
131
+ end
132
+
133
+ it "returns “false” on empty input" do
134
+ expect($stdin).to(receive(:gets).and_return("\n"))
135
+ expect(ask).to(be(false))
66
136
  end
67
137
 
68
138
  context "with default" do
@@ -72,7 +142,7 @@ module Infopark
72
142
  let(:default_value) { true }
73
143
 
74
144
  it "presents default answer “yes”" do
75
- expect($stdout).to(receive(:write).with("(yes/no) [yes] > "))
145
+ expect(user_io).to(receive(:tell).with("(yes/no) [yes] > ", **tell_options, newline: false))
76
146
  ask
77
147
  end
78
148
 
@@ -88,7 +158,7 @@ module Infopark
88
158
  let(:default_value) { false }
89
159
 
90
160
  it "presents default answer “no”" do
91
- expect($stdout).to(receive(:write).with("(yes/no) [no] > "))
161
+ expect(user_io).to(receive(:tell).with("(yes/no) [no] > ", **tell_options, newline: false))
92
162
  ask
93
163
  end
94
164
 
@@ -127,7 +197,7 @@ module Infopark
127
197
  context "“no”" do
128
198
  let(:expected_value) { "no" }
129
199
 
130
- it_behaves_like "any question"
200
+ it_behaves_like "any question", invert_answer: true
131
201
 
132
202
  it "returns “true” when answering “no”" do
133
203
  expect($stdin).to(receive(:gets).and_return("no\n"))
@@ -146,6 +216,53 @@ module Infopark
146
216
  end
147
217
  end
148
218
 
219
+ describe "#confirm" do
220
+ subject(:confirm) { user_io.confirm(*confirm_texts, **confirm_options) }
221
+
222
+ before { allow(user_io).to(receive(:ask)).and_return(ask_result) }
223
+
224
+ let(:ask_result) { true }
225
+ let(:confirm_options) do
226
+ [
227
+ {expected: "yes"},
228
+ {default: "foo", expected: "bar"},
229
+ {},
230
+ ].sample
231
+ end
232
+ let(:confirm_texts) do
233
+ [
234
+ %w[foo bar],
235
+ %w[baz],
236
+ [],
237
+ ].sample
238
+ end
239
+
240
+ it "delegates to #ask" do
241
+ confirm
242
+ if confirm_texts.empty? && confirm_options.empty?
243
+ expect(user_io).to(have_received(:ask).with(no_args))
244
+ else
245
+ expect(user_io).to(have_received(:ask).with(*confirm_texts, **confirm_options))
246
+ end
247
+ end
248
+
249
+ context "when #ask returns truthy" do
250
+ let(:ask_result) { [SecureRandom.hex, 1, true].sample }
251
+
252
+ it "returns the result" do
253
+ expect(confirm).to(be(ask_result))
254
+ end
255
+ end
256
+
257
+ context "when #ask returns falsey" do
258
+ let(:ask_result) { [nil, false].sample }
259
+
260
+ it "aborts" do
261
+ expect { confirm }.to(raise_error(UserIO::Aborted))
262
+ end
263
+ end
264
+ end
265
+
149
266
  describe "#select" do
150
267
  before { allow($stdin).to(receive(:gets).and_return("1\n")) }
151
268
 
@@ -206,7 +323,7 @@ module Infopark
206
323
  describe "#tell_pty_stream" do
207
324
  let(:color_options) { {} }
208
325
  let(:stream) { instance_double(IO) }
209
- let(:data) { "test data" }
326
+ let(:data) { +"test data" }
210
327
 
211
328
  subject(:tell) { user_io.tell_pty_stream(stream, **color_options) }
212
329
 
@@ -221,7 +338,7 @@ module Infopark
221
338
  it "tells all data from stream in non blocking chunks" do
222
339
  expect(stream).to(receive(:eof?).and_return(false, false, false, true))
223
340
  expect(stream).to(receive(:read_nonblock).with(100)
224
- .and_return("first\nchunk", "second chunk", "\nlast chunk"))
341
+ .and_return(+"first\nchunk", +"second chunk", +"\nlast chunk"))
225
342
  expect($stdout).to(receive(:write).with("first\nchunk"))
226
343
  expect($stdout).to(receive(:write).with("second chunk"))
227
344
  expect($stdout).to(receive(:write).with("\nlast chunk"))
@@ -261,7 +378,7 @@ module Infopark
261
378
  end
262
379
 
263
380
  context "when stream contains carriage return" do
264
- let(:data) { "some\rdata\rwith\rCRs" }
381
+ let(:data) { +"some\rdata\rwith\rCRs" }
265
382
 
266
383
  it "writes the prefix right after the CR" do
267
384
  expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
@@ -289,7 +406,7 @@ module Infopark
289
406
  end
290
407
 
291
408
  context "when stream contains newline" do
292
- let(:data) { "some\ndata\nwith\nNLs" }
409
+ let(:data) { +"some\ndata\nwith\nNLs" }
293
410
 
294
411
  it "writes the prefix right after the NL" do
295
412
  expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
@@ -317,7 +434,7 @@ module Infopark
317
434
 
318
435
  context "when stream ends with newline" do
319
436
  # includes an empty chunk to verify, that they don't consume the pending NL
320
- let(:data) { ["some\n", "data\n", "with\n", "", "NLs\n", ""] }
437
+ let(:data) { [+"some\n", +"data\n", +"with\n", +"", +"NLs\n", +""] }
321
438
 
322
439
  it "does not write prefix after the last newline" do
323
440
  expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
@@ -354,7 +471,7 @@ module Infopark
354
471
  end
355
472
 
356
473
  context "when data does not end with newline" do
357
- let(:data) { "foo" }
474
+ let(:data) { +"foo" }
358
475
 
359
476
  it "writes prefix on next output nevertheless" do
360
477
  expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
@@ -388,20 +505,23 @@ module Infopark
388
505
  context "when in background" do
389
506
  let(:color_options) { {color: :yellow} }
390
507
  let(:options) { {output_prefix: "foo"} }
391
- let(:data) { ["data\n", "in\nchunks", "", "yo\n", ""] }
508
+ let(:data) { [+"data\n", +"in\nchunks", +"", +"yo\n", +""] }
392
509
 
393
- let!(:foreground_thread) do
394
- @finished = false
395
- Thread.new do
510
+ before do
511
+ @fg_in = Thread::Queue.new
512
+ @fg_out = Thread::Queue.new
513
+ @fg_thread = Thread.new do
396
514
  user_io.background_other_threads
397
- sleep(0.1) until @finished
515
+ @fg_out.push(:other_backgrounded)
516
+ @fg_in.pop
398
517
  user_io.foreground
399
518
  end
400
519
  end
401
520
 
402
- after { foreground_thread.kill.join }
521
+ after { @fg_thread.kill.join }
403
522
 
404
523
  it "holds back the output until coming back to foreground" do
524
+ @fg_out.pop(timeout: 1)
405
525
  expect($stdout).to_not(receive(:write))
406
526
  tell
407
527
  RSpec::Mocks.space.proxy_for($stdout).reset
@@ -413,10 +533,37 @@ module Infopark
413
533
  expect($stdout).to(receive(:write).with("yo").ordered)
414
534
  expect($stdout).to(receive(:write).with("\n").ordered)
415
535
  expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
416
- @finished = true
417
- foreground_thread.join
536
+ @fg_in.push(:background_done)
537
+ @fg_thread.join
418
538
  end
419
539
  end
420
540
  end
541
+
542
+ describe "#warn" do
543
+ subject(:warn) { user_io.warn(*warn_texts, **warn_options) }
544
+
545
+ before { allow(user_io).to(receive(:tell)).and_return(tell_result) }
546
+
547
+ let(:tell_result) { [SecureRandom.hex, nil].sample }
548
+ let(:warn_options) do
549
+ [
550
+ {newline: true},
551
+ {prefix: "foo", newline: false},
552
+ {},
553
+ ].sample
554
+ end
555
+ let(:warn_texts) do
556
+ [
557
+ %w[foo bar],
558
+ %w[baz],
559
+ [],
560
+ ].sample
561
+ end
562
+
563
+ it "delegates to #tell" do
564
+ warn
565
+ expect(user_io).to(have_received(:tell).with(*warn_texts, **warn_options, color: :yellow, bright: true))
566
+ end
567
+ end
421
568
  end
422
569
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: infopark-user_io
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilo Prütz