infopark-user_io 1.3.0 → 1.4.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.
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