spec_selector 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,459 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe 'SpecSelectorUtil::UI' do
4
+ include_context 'shared'
5
+
6
+ let(:spec_selector) { SpecSelector.new(StringIO.new) }
7
+ let(:output) { spec_selector.ivar(:@output).string }
8
+
9
+ describe '#exit_only', break_loop: true do
10
+ before { allow(spec_selector).to receive(:quit) }
11
+
12
+ it 'prints instructions to quit' do
13
+ spec_selector.exit_only
14
+ expect(output).to match(/Press Q to quit/)
15
+ end
16
+
17
+ context 'when user enters "q"' do
18
+ before { $stdin = StringIO.new('q') }
19
+
20
+ it 'calls #quit' do
21
+ spec_selector.exit_only
22
+ expect(spec_selector).to have_received(:quit)
23
+ end
24
+ end
25
+
26
+ context 'when user does not enter "q"' do
27
+ it 'does not call #quit' do
28
+ spec_selector.exit_only
29
+ expect(spec_selector).not_to have_received(:quit)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#selector' do
35
+ before do
36
+ ivars_set({ :@active_map => mixed_map, :@example_count => 10 })
37
+ allow_methods(:display_list, :navigate)
38
+ spec_selector.selector
39
+ end
40
+
41
+ it 'sets default value of @list to @active_map[:top_level]' do
42
+ expect(spec_selector.ivar(:@list)).to eq(mixed_map[:top_level])
43
+ end
44
+
45
+ it 'sets default value of @selected to @list.first' do
46
+ expect(ivar(:@selected)).to eq(ivar(:@list).first)
47
+ end
48
+
49
+ it 'calls #display_list' do
50
+ expect(spec_selector).to have_received(:display_list)
51
+ end
52
+
53
+ it 'calls #navigate' do
54
+ expect(spec_selector).to have_received(:navigate)
55
+ end
56
+ end
57
+
58
+ describe '#navigate', break_loop: true do
59
+ context 'when @selected is included in @list' do
60
+ it 'sets @selector_index to index position of @selected in @list' do
61
+ ivars_set(:@selected => fail_group, :@list => mixed_map[:top_level])
62
+ spec_selector.navigate
63
+ expect(ivar(:@selector_index)).to eq(1)
64
+ end
65
+ end
66
+
67
+ context 'when @selected is not included in @list' do
68
+ it 'sets @selected to zero' do
69
+ ivars_set(:@selected => failed_example, :@list => mixed_map[:top_level])
70
+ spec_selector.navigate
71
+ expect(ivar(:selector_index)).to eq(0)
72
+ end
73
+ end
74
+
75
+ it 'calls #bind_input continuously' do
76
+ allow_methods(:bind_input)
77
+ spec_selector.navigate
78
+ expect(spec_selector).to have_received(:bind_input).at_least(:twice)
79
+ end
80
+ end
81
+
82
+ describe '#bind_input' do
83
+ it 'captures user input' do
84
+ binding = nil
85
+ trace = TracePoint.new(:call) { |t| binding = t.binding }
86
+ trace.enable(target: spec_selector.method(:bind_input))
87
+ allow_methods(:tree_nav_keys)
88
+ allow(spec_selector).to receive(:user_input).and_return("\r")
89
+ spec_selector.bind_input
90
+ expect(binding.eval('input')).to be("\r")
91
+ end
92
+
93
+ context 'when DIRECTION_KEYS includes input string' do
94
+ it 'passes input string to #direction_keys' do
95
+ allow(spec_selector).to receive(:user_input).and_return("\e[A")
96
+ allow(spec_selector).to receive(:direction_keys)
97
+ spec_selector.bind_input
98
+ expect(spec_selector).to have_received(:direction_keys).with("\e[A")
99
+ end
100
+ end
101
+
102
+ context 'when TREE_NAVIGATION_KEYS includes input string' do
103
+ it 'passes input string to #tree_nav_keys' do
104
+ allow(spec_selector).to receive(:user_input).and_return("\x7F")
105
+ allow(spec_selector).to receive(:tree_nav_keys)
106
+ spec_selector.bind_input
107
+ expect(spec_selector).to have_received(:tree_nav_keys).with("\x7F")
108
+ end
109
+ end
110
+
111
+ context 'when input string matches pattern in OPTION_KEYS' do
112
+ it 'passes input string to #option_keys' do
113
+ allow(spec_selector).to receive(:user_input).and_return('q')
114
+ allow(spec_selector).to receive(:option_keys)
115
+ spec_selector.bind_input
116
+ expect(spec_selector).to have_received(:option_keys).with('q')
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#quit' do
122
+ before do
123
+ allow_methods(:clear_frame, :reveal_cursor, :exit)
124
+ spec_selector.quit
125
+ end
126
+
127
+ it 'calls #clear_frame' do
128
+ expect(spec_selector).to have_received(:clear_frame)
129
+ end
130
+
131
+ it 'calls #reveal_cursor' do
132
+ expect(spec_selector).to have_received(:reveal_cursor)
133
+ end
134
+
135
+ it 'exits program' do
136
+ expect(spec_selector).to have_received(:exit)
137
+ end
138
+ end
139
+
140
+ describe '#top_level_list' do
141
+ before do
142
+ ivars_set(:@active_map => mixed_map, :@list => [passing_example])
143
+ ivars_set(:@selected => passing_example, :@example_display => true)
144
+ allow_methods(:display_list)
145
+ spec_selector.top_level_list
146
+ end
147
+
148
+ it 'sets @example_display to false' do
149
+ expect(ivar(:@example_display)).to be false
150
+ end
151
+
152
+ it 'sets @list to @active_map[:top_level]' do
153
+ expect(ivar(:@list)).to eq(mixed_map[:top_level])
154
+ end
155
+
156
+ it 'calls #display_list' do
157
+ expect(spec_selector).to have_received(:display_list)
158
+ end
159
+ end
160
+
161
+ describe '#select_item' do
162
+ context 'when @example_display is true' do
163
+ it 'returns immediately' do
164
+ spec_selector.ivar_set(:@example_display, true)
165
+ allow(spec_selector).to receive(:example?)
166
+ spec_selector.select_item
167
+ expect(spec_selector).not_to have_received(:example?)
168
+ end
169
+ end
170
+
171
+ context 'when @example_display is false' do
172
+ it 'does not return immediately' do
173
+ ivars_set(:@selected => fail_group, :@example_display => false)
174
+ allow_methods(:display_list, :example?, :set_selected)
175
+ spec_selector.select_item
176
+ expect(spec_selector).to have_received(:example?)
177
+ end
178
+ end
179
+
180
+ context 'when @selected is an example' do
181
+ it 'calls #display_example' do
182
+ ivars_set(:@selected => failed_example, :@list => [failed_example])
183
+ allow_methods(:display_example, :selector)
184
+ spec_selector.select_item
185
+ expect(spec_selector).to have_received(:display_example)
186
+ end
187
+ end
188
+
189
+ context 'when @selected is not an example' do
190
+ before do
191
+ ivars_set(:@active_map => mixed_map, :@selected => pass_group)
192
+ allow_methods(:display_list)
193
+ spec_selector.select_item
194
+ end
195
+
196
+ it 'sets @list subgroups and/or examples that belong to @selected' do
197
+ expect(ivar(:@list)).to eq(pass_group.examples)
198
+ end
199
+
200
+ it 'calls #display_list' do
201
+ expect(spec_selector).to have_received(:display_list)
202
+ end
203
+ end
204
+ end
205
+
206
+ describe '#top_fail' do
207
+ before { allow(spec_selector).to receive(:display_example) }
208
+
209
+ context 'when there are no failed examples' do
210
+ it 'returns immediately' do
211
+ spec_selector.top_fail
212
+ expect(spec_selector).not_to have_received(:display_example)
213
+ end
214
+ end
215
+
216
+ context 'when there are failed examples' do
217
+ before do
218
+ spec_selector.ivar_set(:@failed, fail_group.examples)
219
+ spec_selector.top_fail
220
+ end
221
+
222
+ it 'sets @selected to the top failed example' do
223
+ expect(ivar(:@selected)).to eq(ivar(:@failed).first)
224
+ end
225
+
226
+ it 'calls #display_example' do
227
+ expect(spec_selector).to have_received(:display_example)
228
+ end
229
+ end
230
+ end
231
+
232
+ describe '#back' do
233
+ before { allow_methods(:parent_list, :display_list) }
234
+
235
+ context 'when top level list is currently displayed' do
236
+ it 'returns immediately' do
237
+ spec_selector.back
238
+ expect(spec_selector).not_to have_received(:parent_list)
239
+ expect(spec_selector).not_to have_received(:display_list)
240
+ end
241
+ end
242
+
243
+ context 'when top level list is not currently displayed' do
244
+ before do
245
+ spec_selector.ivar_set(:@list, mixed_map[fail_group.metadata[:block]])
246
+ spec_selector.back
247
+ end
248
+
249
+ it 'calls #parent_list' do
250
+ expect(spec_selector).to have_received(:parent_list)
251
+ end
252
+
253
+ it 'calls #selector' do
254
+ expect(spec_selector).to have_received(:display_list)
255
+ end
256
+ end
257
+ end
258
+
259
+ describe '#parent_list' do
260
+ context 'when @example_display is true' do
261
+ before do
262
+ attrs = {
263
+ :@active_map => mixed_map,
264
+ :@selected => fail_group.examples.first,
265
+ :@example_display => true
266
+ }
267
+ ivars_set(attrs)
268
+ allow(ivar(:@selected)).to receive(:example_group) { fail_group }
269
+ spec_selector.parent_list
270
+ end
271
+
272
+ it 'sets @example_display to false' do
273
+ expect(ivar(:@example_display)).to be false
274
+ end
275
+
276
+ it 'sets @list to example group that includes @selected' do
277
+ expect(ivar(:@list)).to eq(fail_group.examples)
278
+ end
279
+ end
280
+
281
+ context 'when @example_display is already false' do
282
+ before do
283
+ ivars_set(:@selected => fail_subgroup, :@active_map => deep_map)
284
+ ivars_set(:@groups => {
285
+ fail_parent_group.metadata[:block] => fail_parent_group
286
+ })
287
+ spec_selector.parent_list
288
+ end
289
+
290
+ it 'sets @list to group that includes parent group' do
291
+ expect(ivar(:@list)).to eq(deep_map[:top_level])
292
+ end
293
+
294
+ it 'sets @selected to parent group' do
295
+ expect(ivar(:@selected)).to eq(fail_parent_group)
296
+ end
297
+ end
298
+ end
299
+
300
+ describe '#direction_keys' do
301
+ let(:list) do
302
+ [fail_group, pass_group, fail_group, fail_group, fail_group]
303
+ end
304
+
305
+ before do
306
+ ivars_set(:@list => list, :@selector_index => 2)
307
+ allow_methods(:display_list, :display_example)
308
+ end
309
+
310
+ context 'when input string is "\e[A"' do
311
+ it 'decrements @selector_index' do
312
+ spec_selector.direction_keys("\e[A")
313
+ expect(ivar(:@selector_index)).to eq(1)
314
+ end
315
+ end
316
+
317
+ context 'when input string is not "\e[A"' do
318
+ # the only possible value of input string in this case is "\e[B"
319
+ it 'increments @selector_index' do
320
+ spec_selector.direction_keys("\e[B")
321
+ expect(ivar(:@selector_index)).to eq(3)
322
+ end
323
+ end
324
+
325
+ it 'sets @selected to @list element at @selector_index' do
326
+ spec_selector.direction_keys("\e[A")
327
+ expect(ivar(:@selected)).to eq(pass_group)
328
+ end
329
+
330
+ context 'when @example_display is true' do
331
+ it 'calls #display_example' do
332
+ ivar_set(:@example_display, true)
333
+ spec_selector.direction_keys("\e[A")
334
+ expect(spec_selector).to have_received(:display_example)
335
+ end
336
+ end
337
+
338
+ context 'when @example_display is false' do
339
+ it 'calls #display_list' do
340
+ ivar_set(:@example_display, false)
341
+ spec_selector.direction_keys("\e[A")
342
+ expect(spec_selector).to have_received(:display_list)
343
+ end
344
+ end
345
+ end
346
+
347
+ describe '#tree_nav_keys' do
348
+ before do
349
+ allow_methods(:select_item, :back, :top_level_list)
350
+ end
351
+
352
+ context 'when input string is "\r"' do
353
+ it 'calls #select_item' do
354
+ spec_selector.tree_nav_keys("\r")
355
+ expect(spec_selector).to have_received(:select_item)
356
+ end
357
+ end
358
+
359
+ context 'when input string is "\x7F"' do
360
+ it 'calls #back' do
361
+ spec_selector.tree_nav_keys("\x7F")
362
+ expect(spec_selector).to have_received(:back)
363
+ end
364
+ end
365
+
366
+ context 'when input string is "\e"' do
367
+ it 'calls #top_level_list' do
368
+ spec_selector.tree_nav_keys("\e")
369
+ expect(spec_selector).to have_received(:top_level_list)
370
+ end
371
+ end
372
+ end
373
+
374
+ describe '#option_keys' do
375
+ before do
376
+ allow_methods(:top_fail, :toggle_passing, :quit)
377
+ end
378
+
379
+ context 'when input string matches /t/i' do
380
+ it 'calls #top_fail' do
381
+ spec_selector.option_keys('t')
382
+ expect(spec_selector).to have_received(:top_fail)
383
+ end
384
+ end
385
+
386
+ context 'when input string matches /p/i' do
387
+ it 'calls #toggle_passing' do
388
+ spec_selector.option_keys('p')
389
+ expect(spec_selector).to have_received(:toggle_passing)
390
+ end
391
+ end
392
+
393
+ context 'when input string matches /q/i' do
394
+ it 'calls #quit' do
395
+ spec_selector.option_keys('q')
396
+ expect(spec_selector).to have_received(:quit)
397
+ end
398
+ end
399
+
400
+ context 'when input string matches /i/i' do
401
+ context 'when instruction page is not open' do
402
+ it 'calls #view_instructions_page' do
403
+ allow(spec_selector).to receive(:view_instructions_page)
404
+ spec_selector.option_keys('i')
405
+ expect(spec_selector).to have_received(:view_instructions_page)
406
+ end
407
+ end
408
+
409
+ context 'when instruction page is already open' do
410
+ it 'calls #exit_instruction_page_only' do
411
+ spec_selector.ivar_set(:@instructions, true)
412
+ allow(spec_selector).to receive(:exit_instruction_page_only)
413
+ spec_selector.option_keys('i')
414
+ expect(spec_selector).to have_received(:exit_instruction_page_only)
415
+ end
416
+ end
417
+ end
418
+ end
419
+
420
+ describe '#user_input' do
421
+ it 'initially captures one byte of keyboard input' do
422
+ binding = nil
423
+ trace = TracePoint.new(:call) { |t| binding = t.binding }
424
+ trace.enable(target: spec_selector.method(:user_input))
425
+ allow($stdin).to receive(:getch).and_return("\x7F")
426
+ spec_selector.user_input
427
+ expect(binding.eval('input')).to eq("\x7F")
428
+ end
429
+
430
+ context 'when there is no further readable data in buffer' do
431
+ before do
432
+ stdin, user = IO.pipe
433
+ user.print "\e"
434
+ allow($stdin).to receive(:getch).and_return(stdin.getc)
435
+ allow(IO).to receive(:select).and_return(more_data?(stdin))
436
+ end
437
+
438
+ it 'returns input string' do
439
+ expect(spec_selector.user_input).to eq("\e")
440
+ end
441
+ end
442
+
443
+ context 'when input buffer still contains readable data' do
444
+ # user is assumed to have pressed a directional key
445
+ before do
446
+ stdin, user = IO.pipe
447
+ user.print "\e[A"
448
+ allow($stdin).to receive(:getch).and_return(stdin.getc)
449
+ allow(IO).to receive(:select).and_return(more_data?(stdin))
450
+ last_two_bytes = stdin.read_nonblock(2)
451
+ allow($stdin).to receive(:read_nonblock).with(2).and_return(last_two_bytes)
452
+ end
453
+
454
+ it 'appends the next two bytes to the input string' do
455
+ expect(spec_selector.user_input).to eq("\e[A")
456
+ end
457
+ end
458
+ end
459
+ end