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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/README.md +48 -0
- data/lib/spec_selector.rb +92 -0
- data/lib/spec_selector/data_map.rb +38 -0
- data/lib/spec_selector/data_presentation.rb +179 -0
- data/lib/spec_selector/format.rb +99 -0
- data/lib/spec_selector/helpers.rb +63 -0
- data/lib/spec_selector/initialize.rb +83 -0
- data/lib/spec_selector/instructions.rb +139 -0
- data/lib/spec_selector/scripts/rerun.sh +40 -0
- data/lib/spec_selector/state.rb +142 -0
- data/lib/spec_selector/terminal.rb +45 -0
- data/lib/spec_selector/ui.rb +169 -0
- data/license.md +8 -0
- data/spec/factories.rb +89 -0
- data/spec/shared.rb +145 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/spec_selector_spec.rb +165 -0
- data/spec/spec_selector_util/data_map_spec.rb +98 -0
- data/spec/spec_selector_util/data_presentation_spec.rb +314 -0
- data/spec/spec_selector_util/format_spec.rb +213 -0
- data/spec/spec_selector_util/helpers_spec.rb +222 -0
- data/spec/spec_selector_util/initialize_spec.rb +93 -0
- data/spec/spec_selector_util/ui_spec.rb +459 -0
- metadata +96 -0
- metadata.gz.sig +1 -0
@@ -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
|