spec_selector 0.1.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.
@@ -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