infopark-user_io 1.2.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/spec/user_io_spec.rb CHANGED
@@ -1,337 +1,417 @@
1
- module Infopark
2
-
3
- RSpec.describe UserIO do
4
- let(:options) { {} }
5
-
6
- subject(:user_io) { UserIO.new(**options) }
7
-
8
- before do
9
- allow($stdout).to receive(:puts)
10
- # for debugging: .and_call_original
11
- allow($stdout).to receive(:write)
12
- # for debugging: .and_call_original
13
- end
1
+ # frozen_string_literal: true
14
2
 
15
- describe ".global" do
16
- subject(:global) { UserIO.global }
3
+ require "securerandom"
17
4
 
18
- it { is_expected.to be_nil }
19
- end
5
+ module Infopark
6
+ RSpec.describe(UserIO) do
7
+ let(:options) { {} }
20
8
 
21
- describe ".global=" do
22
- subject(:assign_global) { UserIO.global = user_io }
9
+ subject(:user_io) { UserIO.new(**options) }
23
10
 
24
- it "assigns the global UserIO" do
25
- expect {
26
- assign_global
27
- }.to change {
28
- UserIO.global
29
- }.to(user_io)
11
+ before do
12
+ allow($stdout).to(receive(:puts))
13
+ # for debugging: .and_call_original
14
+ allow($stdout).to(receive(:write))
15
+ # for debugging: .and_call_original
30
16
  end
31
- end
32
17
 
33
- describe "#acknowledge" do
34
- before { allow($stdin).to receive(:gets).and_return("\n") }
18
+ describe ".global" do
19
+ subject(:global) { UserIO.global }
35
20
 
36
- let(:message) { "Some important statement." }
21
+ it { is_expected.to(be_nil) }
22
+ end
37
23
 
38
- subject(:acknowledge) { user_io.acknowledge(message) }
24
+ describe ".global=" do
25
+ subject(:assign_global) { UserIO.global = user_io }
39
26
 
40
- it "presents the message (colorized)" do
41
- expect($stdout).to receive(:write).with("\e[1;36m""Some important statement.""\e[22;39m\n")
42
- acknowledge
27
+ it "assigns the global UserIO" do
28
+ expect { assign_global }.to(change { UserIO.global }.to(user_io))
29
+ end
43
30
  end
44
31
 
45
- it "asks for pressing “Enter”" do
46
- expect($stdout).to receive(:write).with("Please press ENTER to continue.\n")
47
- acknowledge
48
- end
32
+ describe "#acknowledge" do
33
+ subject(:acknowledge) { user_io.acknowledge(*ack_texts, **ack_options) }
49
34
 
50
- it "requests input" do
51
- expect($stdin).to receive(:gets).and_return("\n")
52
- acknowledge
53
- end
54
- end
35
+ before do
36
+ allow($stdin).to(receive(:gets).and_return("\n"))
37
+ allow(user_io).to(receive(:tell))
38
+ end
39
+
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
55
54
 
56
- describe "#ask" do
57
- before { allow($stdin).to receive(:gets).and_return("yes\n") }
55
+ it "tells the message (colorized)" do
56
+ expect(user_io).to(receive(:tell).with(*ack_texts, **ack_options, color: :cyan, bright: true))
57
+ acknowledge
58
+ end
58
59
 
59
- let(:ask_options) { {} }
60
- let(:question) { "do you want to?" }
60
+ it "asks for pressing “Enter”" do
61
+ expect(user_io).to(receive(:tell).with("Please press ENTER to continue.", **ack_options))
62
+ acknowledge
63
+ end
61
64
 
62
- subject(:ask) { user_io.ask(*Array(question), **ask_options) }
65
+ it "requests input" do
66
+ expect($stdin).to(receive(:gets).and_return("\n"))
67
+ acknowledge
68
+ end
63
69
 
64
- shared_examples_for "any question" do
65
- # TODO
66
- #it_behaves_like "handling valid answer"
67
- #it_behaves_like "handling invalid input"
68
- #it_behaves_like "printing prefix on every line"
70
+ it "returns nil" do
71
+ expect(acknowledge).to(be_nil)
72
+ end
69
73
  end
70
74
 
71
- context "with default" do
72
- let(:ask_options) { {default: default_value} }
75
+ describe "#ask" do
76
+ subject(:ask) { user_io.ask(*ask_texts, **ask_options.merge(tell_options)) }
73
77
 
74
- context "“true”" do
75
- let(:default_value) { true }
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
91
+ let(:ask_options) { {} }
92
+ let(:tell_options) do
93
+ [
94
+ {italic: true},
95
+ {prefix: "foo", bold: false},
96
+ {},
97
+ ].sample
98
+ end
76
99
 
77
- it "presents default answer “yes”" do
78
- expect($stdout).to receive(:write).with("(yes/no) [yes] > ")
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))
79
103
  ask
80
104
  end
81
105
 
82
- it "returns “true” on empty input" do
83
- expect($stdin).to receive(:gets).and_return("\n")
84
- expect(ask).to be true
106
+ it "requests input" do
107
+ expect($stdin).to(receive(:gets).and_return("\n"))
108
+ ask
85
109
  end
86
110
 
87
- it_behaves_like "any question"
88
- end
89
-
90
- context "“false”" do
91
- let(:default_value) { false }
111
+ context "when answer is “yes”" do
112
+ let(:answer) { "yes" }
92
113
 
93
- it "presents default answer “no”" do
94
- expect($stdout).to receive(:write).with("(yes/no) [no] > ")
95
- ask
114
+ it { is_expected.to(be(invert_answer ? false : true))}
96
115
  end
97
116
 
98
- it "returnsfalse on empty input" do
99
- expect($stdin).to receive(:gets).and_return("\n")
100
- expect(ask).to be false
117
+ context "when answer is no”" do
118
+ let(:answer) { "no" }
119
+
120
+ it { is_expected.to(be(invert_answer ? true : false))}
101
121
  end
102
122
 
103
- it_behaves_like "any question"
123
+ # TODO
124
+ # it_behaves_like "handling valid answer"
125
+ # it_behaves_like "handling invalid input"
104
126
  end
105
127
 
106
- context "non boolean" do
107
- # TODO
128
+ it "asks for answer" do
129
+ expect(user_io).to(receive(:tell).with("(yes/no) > ", **tell_options, newline: false))
130
+ ask
108
131
  end
109
- end
110
132
 
111
- context "withexpected”" do
112
- let(:ask_options) { {expected: expected_value} }
133
+ it "returnsfalse on empty input" do
134
+ expect($stdin).to(receive(:gets).and_return("\n"))
135
+ expect(ask).to(be(false))
136
+ end
137
+
138
+ context "with default" do
139
+ let(:ask_options) { {default: default_value} }
113
140
 
114
- context "“yes”" do
115
- let(:expected_value) { "yes" }
141
+ context "“true”" do
142
+ let(:default_value) { true }
116
143
 
117
- it_behaves_like "any question"
144
+ it "presents default answer “yes”" do
145
+ expect(user_io).to(receive(:tell).with("(yes/no) [yes] > ", **tell_options, newline: false))
146
+ ask
147
+ end
148
+
149
+ it "returns “true” on empty input" do
150
+ expect($stdin).to(receive(:gets).and_return("\n"))
151
+ expect(ask).to(be(true))
152
+ end
118
153
 
119
- it "returns “true” when answering “yes”" do
120
- expect($stdin).to receive(:gets).and_return("yes\n")
121
- expect(ask).to be true
154
+ it_behaves_like "any question"
122
155
  end
123
156
 
124
- it "returns “false” when answering “no”"do
125
- expect($stdin).to receive(:gets).and_return("no\n")
126
- expect(ask).to be false
157
+ context "“false”" do
158
+ let(:default_value) { false }
159
+
160
+ it "presents default answer “no”" do
161
+ expect(user_io).to(receive(:tell).with("(yes/no) [no] > ", **tell_options, newline: false))
162
+ ask
163
+ end
164
+
165
+ it "returns “false” on empty input" do
166
+ expect($stdin).to(receive(:gets).and_return("\n"))
167
+ expect(ask).to(be(false))
168
+ end
169
+
170
+ it_behaves_like "any question"
171
+ end
172
+
173
+ context "non boolean" do
174
+ # TODO
127
175
  end
128
176
  end
129
177
 
130
- context "“no”" do
131
- let(:expected_value) { "no" }
178
+ context "with expected”" do
179
+ let(:ask_options) { {expected: expected_value} }
180
+
181
+ context "“yes”" do
182
+ let(:expected_value) { "yes" }
132
183
 
133
- it_behaves_like "any question"
184
+ it_behaves_like "any question"
134
185
 
135
- it "returns “true” when answering “no”" do
136
- expect($stdin).to receive(:gets).and_return("no\n")
137
- expect(ask).to be true
186
+ it "returns “true” when answering “yes”" do
187
+ expect($stdin).to(receive(:gets).and_return("yes\n"))
188
+ expect(ask).to(be(true))
189
+ end
190
+
191
+ it "returns “false” when answering “no”" do
192
+ expect($stdin).to(receive(:gets).and_return("no\n"))
193
+ expect(ask).to(be(false))
194
+ end
138
195
  end
139
196
 
140
- it "returns false when answering “yes”" do
141
- expect($stdin).to receive(:gets).and_return("yes\n")
142
- expect(ask).to be false
197
+ context "“no”" do
198
+ let(:expected_value) { "no" }
199
+
200
+ it_behaves_like "any question", invert_answer: true
201
+
202
+ it "returns “true” when answering “no”" do
203
+ expect($stdin).to(receive(:gets).and_return("no\n"))
204
+ expect(ask).to(be(true))
205
+ end
206
+
207
+ it "returns “false” when answering “yes”" do
208
+ expect($stdin).to(receive(:gets).and_return("yes\n"))
209
+ expect(ask).to(be(false))
210
+ end
143
211
  end
144
- end
145
212
 
146
- context "other" do
147
- # TODO
213
+ context "other" do
214
+ # TODO
215
+ end
148
216
  end
149
217
  end
150
- end
151
218
 
152
- describe "#select" do
153
- before { allow($stdin).to receive(:gets).and_return("1\n") }
219
+ describe "#confirm" do
220
+ subject(:confirm) { user_io.confirm(*confirm_texts, **confirm_options) }
154
221
 
155
- let(:description) { "a thing" }
156
- let(:items) { [:a, :b, :c] }
157
- let(:select_options) { {} }
222
+ before { allow(user_io).to(receive(:ask)).and_return(ask_result) }
158
223
 
159
- subject(:select) { user_io.select(description, items, **select_options) }
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
160
248
 
161
- context "with default" do
162
- let(:select_options) { {default: :b} }
249
+ context "when #ask returns truthy" do
250
+ let(:ask_result) { [SecureRandom.hex, 1, true].sample }
163
251
 
164
- it "presents the default's index as default answer" do
165
- expect($stdout).to receive(:write).with("Your choice [2] > ")
166
- select
252
+ it "returns the result" do
253
+ expect(confirm).to(be(ask_result))
254
+ end
167
255
  end
168
256
 
169
- it "returns the default on empty input" do
170
- expect($stdin).to receive(:gets).and_return("\n")
171
- expect(select).to eq(:b)
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
172
263
  end
173
264
  end
174
- end
175
265
 
176
- describe "#tell_error" do
177
- let(:error) { {my: :error} }
178
- let(:tell_options) { {} }
266
+ describe "#select" do
267
+ before { allow($stdin).to(receive(:gets).and_return("1\n")) }
179
268
 
180
- subject(:tell_error) { user_io.tell_error(error, **tell_options) }
269
+ let(:description) { "a thing" }
270
+ let(:items) { %i[a b c] }
271
+ let(:select_options) { {} }
181
272
 
182
- it "tells the given thing in bright red" do
183
- expect(user_io).to receive(:tell).with(error, color: :red, bright: true)
184
- tell_error
185
- end
273
+ subject(:select) { user_io.select(description, items, **select_options) }
186
274
 
187
- context "with options" do
188
- let(:tell_options) { {newline: false} }
275
+ context "with default" do
276
+ let(:select_options) { {default: :b} }
189
277
 
190
- it "delegates them to #tell" do
191
- expect(user_io).to receive(:tell).with(error, newline: false, color: :red, bright: true)
192
- tell_error
278
+ it "presents the default's index as default answer" do
279
+ expect($stdout).to(receive(:write).with("Your choice [2] > "))
280
+ select
281
+ end
282
+
283
+ it "returns the default on empty input" do
284
+ expect($stdin).to(receive(:gets).and_return("\n"))
285
+ expect(select).to(eq(:b))
286
+ end
193
287
  end
194
288
  end
195
289
 
196
- context "when error is a kind of an exception" do
197
- let(:error) { UserIO::Aborted.new }
290
+ describe "#tell_error" do
291
+ let(:error) { {my: :error} }
292
+ let(:tell_options) { {} }
198
293
 
199
- before { allow(error).to receive(:backtrace).and_return(%w(a b c)) }
294
+ subject(:tell_error) { user_io.tell_error(error, **tell_options) }
200
295
 
201
- it "tells the error and the whole backtrace" do
202
- expect(user_io).to receive(:tell).with(error, color: :red, bright: true)
203
- expect(user_io).to receive(:tell).with(%w(a b c), color: :red)
296
+ it "tells the given thing in bright red" do
297
+ expect(user_io).to(receive(:tell).with(error, color: :red, bright: true))
204
298
  tell_error
205
299
  end
206
- end
207
- end
208
-
209
- describe "#tell_pty_stream" do
210
- let(:color_options) { {} }
211
- let(:stream) { instance_double(IO) }
212
- let(:data) { "test data" }
213
300
 
214
- subject(:tell) { user_io.tell_pty_stream(stream, **color_options) }
301
+ context "with options" do
302
+ let(:tell_options) { {newline: false} }
215
303
 
216
- before do
217
- chunks = Array(data)
218
- allow(stream).to receive(:eof?).and_return(*[false] * chunks.size, true)
219
- allow(stream).to receive(:read_nonblock).and_return(*chunks)
220
- RSpec::Mocks.space.proxy_for($stdout).reset
221
- allow($stdout).to receive(:write).with(nil)
222
- end
304
+ it "delegates them to #tell" do
305
+ expect(user_io).to(receive(:tell).with(error, newline: false, color: :red, bright: true))
306
+ tell_error
307
+ end
308
+ end
223
309
 
224
- it "tells all data from stream in non blocking chunks" do
225
- expect(stream).to receive(:eof?).and_return(false, false, false, true)
226
- expect(stream).to receive(:read_nonblock).with(100).
227
- and_return("first\nchunk", "second chunk", "\nlast chunk")
228
- expect($stdout).to receive(:write).with("first\nchunk")
229
- expect($stdout).to receive(:write).with("second chunk")
230
- expect($stdout).to receive(:write).with("\nlast chunk")
231
- tell
232
- end
310
+ context "when error is a kind of an exception" do
311
+ let(:error) { UserIO::Aborted.new }
233
312
 
234
- context "with color" do
235
- let(:color_options) { {color: :yellow} }
313
+ before { allow(error).to(receive(:backtrace).and_return(%w(a b c))) }
236
314
 
237
- it "colorizes the output" do
238
- expect($stdout).to receive(:write).with("\e[33m").ordered
239
- expect($stdout).to receive(:write).with("test data").ordered
240
- expect($stdout).to receive(:write).with("\e[22;39m").ordered
241
- tell
315
+ it "tells the error and the whole backtrace" do
316
+ expect(user_io).to(receive(:tell).with(error, color: :red, bright: true))
317
+ expect(user_io).to(receive(:tell).with(%w(a b c), color: :red))
318
+ tell_error
319
+ end
242
320
  end
243
321
  end
244
322
 
245
- context "with output_prefix" do
246
- let(:options) { {output_prefix: "the prefix"} }
323
+ describe "#tell_pty_stream" do
324
+ let(:color_options) { {} }
325
+ let(:stream) { instance_double(IO) }
326
+ let(:data) { +"test data" }
327
+
328
+ subject(:tell) { user_io.tell_pty_stream(stream, **color_options) }
247
329
 
248
- it "prefixes the output" do
249
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
250
- expect($stdout).to receive(:write).with("test data").ordered
330
+ before do
331
+ chunks = Array(data)
332
+ allow(stream).to(receive(:eof?).and_return(*[false] * chunks.size, true))
333
+ allow(stream).to(receive(:read_nonblock).and_return(*chunks))
334
+ RSpec::Mocks.space.proxy_for($stdout).reset
335
+ allow($stdout).to(receive(:write).with(nil))
336
+ end
337
+
338
+ it "tells all data from stream in non blocking chunks" do
339
+ expect(stream).to(receive(:eof?).and_return(false, false, false, true))
340
+ expect(stream).to(receive(:read_nonblock).with(100)
341
+ .and_return(+"first\nchunk", +"second chunk", +"\nlast chunk"))
342
+ expect($stdout).to(receive(:write).with("first\nchunk"))
343
+ expect($stdout).to(receive(:write).with("second chunk"))
344
+ expect($stdout).to(receive(:write).with("\nlast chunk"))
251
345
  tell
252
346
  end
253
347
 
254
348
  context "with color" do
255
349
  let(:color_options) { {color: :yellow} }
256
350
 
257
- it "does not colorize the prefix" do
258
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
259
- expect($stdout).to receive(:write).with("\e[33m").ordered
260
- expect($stdout).to receive(:write).with("test data").ordered
261
- expect($stdout).to receive(:write).with("\e[22;39m").ordered
351
+ it "colorizes the output" do
352
+ expect($stdout).to(receive(:write).with("\e[33m").ordered)
353
+ expect($stdout).to(receive(:write).with("test data").ordered)
354
+ expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
262
355
  tell
263
356
  end
264
357
  end
265
358
 
266
- context "when stream contains carriage return" do
267
- let(:data) { "some\rdata\rwith\rCRs" }
359
+ context "with output_prefix" do
360
+ let(:options) { {output_prefix: "the prefix"} }
268
361
 
269
- it "writes the prefix right after the CR" do
270
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
271
- expect($stdout).to receive(:write).
272
- with("some\r[the prefix] data\r[the prefix] with\r[the prefix] CRs").ordered
362
+ it "prefixes the output" do
363
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
364
+ expect($stdout).to(receive(:write).with("test data").ordered)
273
365
  tell
274
366
  end
275
367
 
276
368
  context "with color" do
277
369
  let(:color_options) { {color: :yellow} }
278
370
 
279
- it "uncolorizes the prefix" do
280
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
281
- expect($stdout).to receive(:write).with("\e[33m").ordered
282
- expect($stdout).to receive(:write).with(
283
- "some\r"\
284
- "\e[22;39m" "[the prefix] " "\e[33m" "data\r"\
285
- "\e[22;39m" "[the prefix] " "\e[33m" "with\r"\
286
- "\e[22;39m" "[the prefix] " "\e[33m" "CRs"
287
- ).ordered
288
- expect($stdout).to receive(:write).with("\e[22;39m").ordered
371
+ it "does not colorize the prefix" do
372
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
373
+ expect($stdout).to(receive(:write).with("\e[33m").ordered)
374
+ expect($stdout).to(receive(:write).with("test data").ordered)
375
+ expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
289
376
  tell
290
377
  end
291
378
  end
292
- end
293
379
 
294
- context "when stream contains newline" do
295
- let(:data) { "some\ndata\nwith\nNLs" }
380
+ context "when stream contains carriage return" do
381
+ let(:data) { +"some\rdata\rwith\rCRs" }
296
382
 
297
- it "writes the prefix right after the NL" do
298
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
299
- expect($stdout).to receive(:write).
300
- with("some\n[the prefix] data\n[the prefix] with\n[the prefix] NLs")
301
- tell
302
- end
383
+ it "writes the prefix right after the CR" do
384
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
385
+ expect($stdout).to(receive(:write)
386
+ .with("some\r[the prefix] data\r[the prefix] with\r[the prefix] CRs").ordered)
387
+ tell
388
+ end
303
389
 
304
- context "with color" do
305
- let(:color_options) { {color: :yellow} }
390
+ context "with color" do
391
+ let(:color_options) { {color: :yellow} }
306
392
 
307
- it "uncolorizes the prefix" do
308
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
309
- expect($stdout).to receive(:write).with("\e[33m").ordered
310
- expect($stdout).to receive(:write).with(
311
- "some\n"\
312
- "\e[22;39m" "[the prefix] " "\e[33m" "data\n"\
313
- "\e[22;39m" "[the prefix] " "\e[33m" "with\n"\
314
- "\e[22;39m" "[the prefix] " "\e[33m" "NLs"
315
- ).ordered
316
- expect($stdout).to receive(:write).with("\e[22;39m").ordered
317
- tell
393
+ it "uncolorizes the prefix" do
394
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
395
+ expect($stdout).to(receive(:write).with("\e[33m").ordered)
396
+ expect($stdout).to(receive(:write).with(
397
+ "some\r" \
398
+ "\e[22;39m[the prefix] \e[33mdata\r" \
399
+ "\e[22;39m[the prefix] \e[33mwith\r" \
400
+ "\e[22;39m[the prefix] \e[33mCRs",
401
+ ).ordered)
402
+ expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
403
+ tell
404
+ end
318
405
  end
319
406
  end
320
407
 
321
- context "when stream ends with newline" do
322
- # includes an empty chunk to verify, that they don't consume the pending NL
323
- let(:data) { ["some\n", "data\n", "with\n", "", "NLs\n", ""] }
324
-
325
- it "does not write prefix after the last newline" do
326
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
327
- expect($stdout).to receive(:write).with("some").ordered
328
- expect($stdout).to receive(:write).with("\n" "[the prefix] ").ordered
329
- expect($stdout).to receive(:write).with("data").ordered
330
- expect($stdout).to receive(:write).with("\n" "[the prefix] ").ordered
331
- expect($stdout).to receive(:write).with("with").ordered
332
- expect($stdout).to receive(:write).with("\n" "[the prefix] ").ordered
333
- expect($stdout).to receive(:write).with("NLs").ordered
334
- expect($stdout).to receive(:write).with("\n").ordered
408
+ context "when stream contains newline" do
409
+ let(:data) { +"some\ndata\nwith\nNLs" }
410
+
411
+ it "writes the prefix right after the NL" do
412
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
413
+ expect($stdout).to(receive(:write)
414
+ .with("some\n[the prefix] data\n[the prefix] with\n[the prefix] NLs"))
335
415
  tell
336
416
  end
337
417
 
@@ -339,92 +419,151 @@ RSpec.describe UserIO do
339
419
  let(:color_options) { {color: :yellow} }
340
420
 
341
421
  it "uncolorizes the prefix" do
342
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
343
- expect($stdout).to receive(:write).with("\e[33m").ordered
344
- expect($stdout).to receive(:write).with("some").ordered
345
- expect($stdout).to receive(:write).
346
- with("\n" "\e[22;39m" "[the prefix] " "\e[33m").ordered
347
- expect($stdout).to receive(:write).with("data").ordered
348
- expect($stdout).to receive(:write).
349
- with("\n" "\e[22;39m" "[the prefix] " "\e[33m").ordered
350
- expect($stdout).to receive(:write).with("with").ordered
351
- expect($stdout).to receive(:write).
352
- with("\n" "\e[22;39m" "[the prefix] " "\e[33m").ordered
353
- expect($stdout).to receive(:write).with("NLs").ordered
354
- expect($stdout).to receive(:write).with("\n").ordered
355
- expect($stdout).to receive(:write).with("\e[22;39m").ordered
422
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
423
+ expect($stdout).to(receive(:write).with("\e[33m").ordered)
424
+ expect($stdout).to(receive(:write).with(
425
+ "some\n" \
426
+ "\e[22;39m[the prefix] \e[33mdata\n" \
427
+ "\e[22;39m[the prefix] \e[33mwith\n" \
428
+ "\e[22;39m[the prefix] \e[33mNLs",
429
+ ).ordered)
430
+ expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
356
431
  tell
357
432
  end
358
433
  end
434
+
435
+ context "when stream ends with newline" do
436
+ # includes an empty chunk to verify, that they don't consume the pending NL
437
+ let(:data) { [+"some\n", +"data\n", +"with\n", +"", +"NLs\n", +""] }
438
+
439
+ it "does not write prefix after the last newline" do
440
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
441
+ expect($stdout).to(receive(:write).with("some").ordered)
442
+ expect($stdout).to(receive(:write).with("\n[the prefix] ").ordered)
443
+ expect($stdout).to(receive(:write).with("data").ordered)
444
+ expect($stdout).to(receive(:write).with("\n[the prefix] ").ordered)
445
+ expect($stdout).to(receive(:write).with("with").ordered)
446
+ expect($stdout).to(receive(:write).with("\n[the prefix] ").ordered)
447
+ expect($stdout).to(receive(:write).with("NLs").ordered)
448
+ expect($stdout).to(receive(:write).with("\n").ordered)
449
+ tell
450
+ end
451
+
452
+ context "with color" do
453
+ let(:color_options) { {color: :yellow} }
454
+
455
+ it "uncolorizes the prefix" do
456
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
457
+ expect($stdout).to(receive(:write).with("\e[33m").ordered)
458
+ expect($stdout).to(receive(:write).with("some").ordered)
459
+ expect($stdout).to(receive(:write).with("\n\e[22;39m[the prefix] \e[33m").ordered)
460
+ expect($stdout).to(receive(:write).with("data").ordered)
461
+ expect($stdout).to(receive(:write).with("\n\e[22;39m[the prefix] \e[33m").ordered)
462
+ expect($stdout).to(receive(:write).with("with").ordered)
463
+ expect($stdout).to(receive(:write).with("\n\e[22;39m[the prefix] \e[33m").ordered)
464
+ expect($stdout).to(receive(:write).with("NLs").ordered)
465
+ expect($stdout).to(receive(:write).with("\n").ordered)
466
+ expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
467
+ tell
468
+ end
469
+ end
470
+ end
359
471
  end
360
- end
361
472
 
362
- context "when data does not end with newline" do
363
- let(:data) { "foo" }
473
+ context "when data does not end with newline" do
474
+ let(:data) { +"foo" }
364
475
 
365
- it "writes prefix on next output nevertheless" do
366
- expect($stdout).to receive(:write).with("[the prefix] ").ordered
367
- expect($stdout).to receive(:write).with("foo").ordered
368
- tell
369
- expect($stdout).to receive(:write).with("[the prefix] next\n")
370
- user_io.tell("next")
476
+ it "writes prefix on next output nevertheless" do
477
+ expect($stdout).to(receive(:write).with("[the prefix] ").ordered)
478
+ expect($stdout).to(receive(:write).with("foo").ordered)
479
+ tell
480
+ expect($stdout).to(receive(:write).with("[the prefix] next\n"))
481
+ user_io.tell("next")
482
+ end
483
+ end
484
+
485
+ context "when no newline was printed before" do
486
+ before do
487
+ expect($stdout).to(receive(:write).with("[the prefix] no newline").ordered)
488
+ user_io.tell("no newline", newline: false)
489
+ end
490
+
491
+ it "does not prepend prefix" do
492
+ expect($stdout).to(receive(:write).with("test data").ordered)
493
+ tell
494
+ end
495
+
496
+ it "prints prefix on following output" do
497
+ expect($stdout).to(receive(:write).with("test data").ordered)
498
+ tell
499
+ expect($stdout).to(receive(:write).with("[the prefix] next\n"))
500
+ user_io.tell("next")
501
+ end
371
502
  end
372
503
  end
373
504
 
374
- context "when no newline was printed before" do
505
+ context "when in background" do
506
+ let(:color_options) { {color: :yellow} }
507
+ let(:options) { {output_prefix: "foo"} }
508
+ let(:data) { [+"data\n", +"in\nchunks", +"", +"yo\n", +""] }
509
+
375
510
  before do
376
- expect($stdout).to receive(:write).with("[the prefix] no newline").ordered
377
- user_io.tell("no newline", newline: false)
511
+ @fg_in = Thread::Queue.new
512
+ @fg_out = Thread::Queue.new
513
+ @fg_thread = Thread.new do
514
+ user_io.background_other_threads
515
+ @fg_out.push(:other_backgrounded)
516
+ @fg_in.pop
517
+ user_io.foreground
518
+ end
378
519
  end
379
520
 
380
- it "does not prepend prefix" do
381
- expect($stdout).to receive(:write).with("test data").ordered
382
- tell
383
- end
521
+ after { @fg_thread.kill.join }
384
522
 
385
- it "prints prefix on following output" do
386
- expect($stdout).to receive(:write).with("test data").ordered
523
+ it "holds back the output until coming back to foreground" do
524
+ @fg_out.pop(timeout: 1)
525
+ expect($stdout).to_not(receive(:write))
387
526
  tell
388
- expect($stdout).to receive(:write).with("[the prefix] next\n")
389
- user_io.tell("next")
527
+ RSpec::Mocks.space.proxy_for($stdout).reset
528
+ expect($stdout).to(receive(:write).with("[foo] ").ordered)
529
+ expect($stdout).to(receive(:write).with("\e[33m").ordered)
530
+ expect($stdout).to(receive(:write).with("data").ordered)
531
+ expect($stdout).to(receive(:write).with("\n\e[22;39m[foo] \e[33m").ordered)
532
+ expect($stdout).to(receive(:write).with("in\n\e[22;39m[foo] \e[33mchunks").ordered)
533
+ expect($stdout).to(receive(:write).with("yo").ordered)
534
+ expect($stdout).to(receive(:write).with("\n").ordered)
535
+ expect($stdout).to(receive(:write).with("\e[22;39m").ordered)
536
+ @fg_in.push(:background_done)
537
+ @fg_thread.join
390
538
  end
391
539
  end
392
540
  end
393
541
 
394
- context "when in background" do
395
- let(:color_options) { {color: :yellow} }
396
- let(:options) { {output_prefix: "foo"} }
397
- let(:data) { ["data\n", "in\nchunks", "", "yo\n", ""] }
398
-
399
- let!(:foreground_thread) do
400
- @finished = false
401
- Thread.new do
402
- user_io.background_other_threads
403
- sleep 0.1 until @finished
404
- user_io.foreground
405
- end
406
- end
542
+ describe "#warn" do
543
+ subject(:warn) { user_io.warn(*warn_texts, **warn_options) }
407
544
 
408
- after { foreground_thread.kill.join }
545
+ before { allow(user_io).to(receive(:tell)).and_return(tell_result) }
409
546
 
410
- it "holds back the output until coming back to foreground" do
411
- expect($stdout).to_not receive(:write)
412
- tell
413
- RSpec::Mocks.space.proxy_for($stdout).reset
414
- expect($stdout).to receive(:write).with("[foo] ").ordered
415
- expect($stdout).to receive(:write).with("\e[33m").ordered
416
- expect($stdout).to receive(:write).with("data").ordered
417
- expect($stdout).to receive(:write).with("\n" "\e[22;39m" "[foo] " "\e[33m").ordered
418
- expect($stdout).to receive(:write).
419
- with("in\n" "\e[22;39m" "[foo] " "\e[33m" "chunks").ordered
420
- expect($stdout).to receive(:write).with("yo").ordered
421
- expect($stdout).to receive(:write).with("\n").ordered
422
- expect($stdout).to receive(:write).with("\e[22;39m").ordered
423
- @finished = true
424
- foreground_thread.join
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))
425
566
  end
426
567
  end
427
568
  end
428
569
  end
429
-
430
- end