uh-layout 0.1.1

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,170 @@
1
+ module Uh
2
+ class Layout
3
+ describe Container do
4
+ let(:entries) { %i[foo bar] }
5
+ subject(:container) { described_class.new entries }
6
+
7
+ describe '#initialize' do
8
+ it 'assigns no entries when no arguments are given' do
9
+ expect(described_class.new).to be_empty
10
+ end
11
+ end
12
+
13
+ describe '#to_ary' do
14
+ it 'supports implicit conversion to array' do
15
+ expect([] + container).to eq %i[foo bar]
16
+ end
17
+ end
18
+
19
+ describe '#current' do
20
+ context 'when container has multiple entries' do
21
+ it 'returns the first entry' do
22
+ expect(container.current).to be :foo
23
+ end
24
+ end
25
+
26
+ context 'when container has no entry' do
27
+ subject(:container) { described_class.new }
28
+
29
+ it 'returns nil' do
30
+ expect(container.current).to be nil
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#current=' do
36
+ context 'when given argument is an entry' do
37
+ before { container.current = :bar }
38
+
39
+ it 'assigns given entry as the current one' do
40
+ expect(container.current).to be :bar
41
+ end
42
+ end
43
+
44
+ context 'when given argument is not an entry' do
45
+ it 'does not change current entry' do
46
+ expect { container.current = :baz }
47
+ .not_to change { container.current }
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#<<' do
53
+ it 'adds given entry' do
54
+ container << :baz
55
+ expect(container).to include :baz
56
+ end
57
+ end
58
+
59
+ describe '#remove' do
60
+ let(:entries) { %i[foo bar baz] }
61
+
62
+ before { container.current = :bar }
63
+
64
+ it 'removes given argument from entries' do
65
+ container.remove :foo
66
+ expect(container).not_to include :foo
67
+ end
68
+
69
+ it 'preserves the current entry' do
70
+ container.remove :foo
71
+ expect(container.current).to be :bar
72
+ end
73
+
74
+ it 'returns self' do
75
+ expect(container.remove :foo).to be container
76
+ end
77
+
78
+ it 'raises an ArgumentError when given entry is not included' do
79
+ expect { container.remove :unknown_entry }.to raise_error ArgumentError
80
+ end
81
+
82
+ context 'when the first and current entry is removed' do
83
+ before do
84
+ container.current = :foo
85
+ container.remove :foo
86
+ end
87
+
88
+ it 'assigns next entry as the current one' do
89
+ expect(container.current).to be :bar
90
+ end
91
+ end
92
+
93
+ context 'when given entry is the only one' do
94
+ let(:entries) { [:foo] }
95
+
96
+ it 'has no more current entry' do
97
+ container.remove :foo
98
+ expect(container.current).to be nil
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#remove_if' do
104
+ it 'removes entries for which given block returns true' do
105
+ container.remove_if { |e| e == :foo }
106
+ expect(container).not_to include :foo
107
+ end
108
+ end
109
+
110
+ describe '#get' do
111
+ it 'returns consecutive entry in given direction' do
112
+ expect(container.get :succ).to be :bar
113
+ end
114
+
115
+ it 'returns nil when no consecutive entry exists' do
116
+ expect(container.get :pred).to be nil
117
+ end
118
+
119
+ context 'with cycle option' do
120
+ it 'returns consecutive entry, cycling before first one' do
121
+ expect(container.get :pred, cycle: true).to be :bar
122
+ end
123
+
124
+ it 'returns consecutive entry, cycling after last one' do
125
+ container.current = :bar
126
+ expect(container.get :succ, cycle: true).to be :foo
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#sel' do
132
+ it 'sets consecutive entry in given direction as the current one' do
133
+ container.sel :next
134
+ expect(container.current).to be :bar
135
+ end
136
+ end
137
+
138
+ describe '#set' do
139
+ let(:entries) { %i[foo bar baz] }
140
+
141
+ it 'swaps current entry with consecutive one in given direction' do
142
+ container.set :next
143
+ expect(container.to_a).to eq %i[bar foo baz]
144
+ end
145
+
146
+ it 'does not change current entry' do
147
+ expect { container.set :next }.not_to change { container.current }
148
+ end
149
+
150
+ context 'when direction is out of range' do
151
+ it 'rotates the entries' do
152
+ container.set :pred
153
+ expect(container.to_a).to eq %i[bar baz foo]
154
+ end
155
+
156
+ it 'does not change current entry' do
157
+ expect { container.set :pred }.not_to change { container.current }
158
+ end
159
+ end
160
+ end
161
+
162
+ describe '#swap' do
163
+ it 'swaps entries matched by given indexes' do
164
+ container.swap 0, 1
165
+ expect(container.to_a).to eq %i[bar foo]
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,40 @@
1
+ module Uh
2
+ class Layout
3
+ describe Screen do
4
+ let(:geo) { build_geo }
5
+ let(:other_geo) { build_geo 640, 0, 320, 240 }
6
+ let(:client) { build_client }
7
+ subject(:screen) { described_class.new(0, geo) }
8
+
9
+ it 'has one default tag with id 1 assigned' do
10
+ expect(screen.tags).to include an_object_having_attributes id: '1'
11
+ end
12
+
13
+ it 'has one default tag with screen geo copy assigned' do
14
+ expect(screen.tags.first.geo).to eq(screen.geo).and not_be screen.geo
15
+ end
16
+
17
+ describe '#height=' do
18
+ it 'changes screen height' do
19
+ expect { screen.height = 42 }.to change { screen.height }.to 42
20
+ end
21
+
22
+ it 'changes tags height' do
23
+ expect { screen.height = 42 }
24
+ .to change { screen.tags.first.height }.to 42
25
+ end
26
+ end
27
+
28
+ describe '#include?' do
29
+ it 'returns false when screen does not include given client' do
30
+ expect(screen.include? client).to be false
31
+ end
32
+
33
+ it 'returns true when screen includes given client' do
34
+ screen.current_tag.current_column_or_create << client
35
+ expect(screen.include? client).to be true
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ module Uh
2
+ class Layout
3
+ describe Tag do
4
+ let(:geo) { build_geo }
5
+ let(:other_geo) { build_geo 640, 0, 320, 240 }
6
+ let(:client) { build_client }
7
+ let(:other_client) { build_client }
8
+ let(:column) { Column.new(geo) }
9
+ subject(:tag) { described_class.new('1', geo) }
10
+
11
+ describe '.new' do
12
+ it 'raises error unless id converts to string' do
13
+ expect { described_class.new(1, geo) }.to raise_error(ArgumentError)
14
+ end
15
+ end
16
+
17
+ describe '#clients' do
18
+ it 'returns all clients contained in assigned columns' do
19
+ tag.columns << column.tap { |column| column << client << other_client }
20
+ expect(tag.clients).to eq [client, other_client]
21
+ end
22
+ end
23
+
24
+ describe '#include?' do
25
+ it 'returns false when tag does not include given client' do
26
+ expect(tag.include? client).to be false
27
+ end
28
+
29
+ it 'returns true when tag includes given client' do
30
+ tag.columns << column.tap { |column| column << client }
31
+ expect(tag.include? client).to be true
32
+ end
33
+ end
34
+
35
+ describe '#current_column_or_create' do
36
+ context 'when tag has no column' do
37
+ it 'creates a new column' do
38
+ expect { tag.current_column_or_create }
39
+ .to change { tag.columns.size }.from(0).to(1)
40
+ end
41
+
42
+ it 'returns the new column' do
43
+ expect(tag.current_column_or_create).to eq tag.columns.current
44
+ end
45
+ end
46
+
47
+ context 'when tag has a column' do
48
+ before { tag.columns << column }
49
+
50
+ it 'does not create any column' do
51
+ expect { tag.current_column_or_create }
52
+ .not_to change { tag.columns.size }
53
+ end
54
+
55
+ it 'returns the current column' do
56
+ expect(tag.current_column_or_create).to be column
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,414 @@
1
+ module Uh
2
+ describe Layout do
3
+ let(:geo) { build_geo }
4
+ let(:client) { build_client }
5
+ let(:other_client) { build_client }
6
+ let(:widget) { double('widget').as_null_object }
7
+ subject(:layout) { described_class.new }
8
+
9
+ before do
10
+ layout.screens << Layout::Screen.new(0, geo)
11
+ layout.screens << Layout::Screen.new(1, geo)
12
+ layout.widgets << widget
13
+ end
14
+
15
+ describe '#include?' do
16
+ it 'returns false when layout does not include given client' do
17
+ expect(layout.include? client).to be false
18
+ end
19
+
20
+ it 'returns true when layout includes given client' do
21
+ layout << client
22
+ expect(layout.include? client).to be true
23
+ end
24
+ end
25
+
26
+ describe '#arranger_for_current_tag' do
27
+ it 'returns an arranger for current tag columns and geo' do
28
+ expect(layout.arranger_for_current_tag)
29
+ .to respond_to(:update_geos)
30
+ .and have_attributes(
31
+ columns: layout.current_tag.columns,
32
+ geo: layout.current_tag.geo
33
+ )
34
+ end
35
+ end
36
+
37
+ describe '#update_widgets' do
38
+ it 'updates widgets' do
39
+ expect(layout.widgets).to all receive :update
40
+ layout.update_widgets
41
+ end
42
+
43
+ it 'redraws widgets' do
44
+ expect(layout.widgets).to all receive :redraw
45
+ layout.update_widgets
46
+ end
47
+ end
48
+
49
+ describe '#suggest_geo' do
50
+ it 'returns current tag geo copy' do
51
+ expect(layout.suggest_geo)
52
+ .to eq(layout.current_tag.geo)
53
+ .and not_be layout.current_tag.geo
54
+ end
55
+
56
+ context 'when current tag has a column' do
57
+ before { layout.current_tag.columns << Layout::Column.new(build_geo 42) }
58
+
59
+ it 'returns current column geo' do
60
+ expect(layout.suggest_geo)
61
+ .to eq(layout.current_column.geo)
62
+ .and not_be layout.current_column.geo
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '#<<' do
68
+ before { layout << other_client }
69
+
70
+ it 'adds given client to current column' do
71
+ layout << client
72
+ expect(layout.current_column).to include client
73
+ end
74
+
75
+ it 'sets given client as the current one in current column' do
76
+ layout << client
77
+ expect(layout.current_column.current_client).to be client
78
+ end
79
+
80
+ it 'arranges current column clients' do
81
+ expect(layout.current_column).to receive :arrange_clients
82
+ layout << client
83
+ end
84
+
85
+ it 'shows and hides clients in current column' do
86
+ expect(layout.current_column).to receive :show_hide_clients
87
+ layout << client
88
+ end
89
+
90
+ it 'focuses given client' do
91
+ expect(client).to receive :focus
92
+ layout << client
93
+ end
94
+
95
+ it 'updates widgets' do
96
+ expect(widget).to receive :update
97
+ layout << client
98
+ end
99
+
100
+ it 'returns self' do
101
+ expect(layout << client).to be layout
102
+ end
103
+ end
104
+
105
+ describe '#remove' do
106
+ before { layout << client << other_client }
107
+
108
+ it 'removes given client from the layout' do
109
+ layout.remove client
110
+ expect(layout).not_to include client
111
+ end
112
+
113
+ it 'redraws columns with an arranger' do
114
+ expect_any_instance_of(Layout::Column::Arranger).to receive :redraw
115
+ layout.remove client
116
+ end
117
+
118
+ it 'arranges clients in removed client tag columns' do
119
+ expect(layout.current_tag.columns).to all receive :arrange_clients
120
+ layout.remove client
121
+ end
122
+
123
+ it 'shows and hides clients in removed client column' do
124
+ expect(layout.current_column).to receive :show_hide_clients
125
+ layout.remove client
126
+ end
127
+
128
+ it 'focuses the new current client' do
129
+ expect(other_client).to receive :focus
130
+ layout.remove client
131
+ end
132
+
133
+ it 'updates widgets' do
134
+ expect(widget).to receive :update
135
+ layout.remove client
136
+ end
137
+ end
138
+
139
+ describe 'handle_screen_sel' do
140
+ it 'selects consecutive screen in given direction' do
141
+ expect { layout.handle_screen_sel :succ }
142
+ .to change { layout.current_screen.id }.from(0).to(1)
143
+ end
144
+
145
+ it 'focus selected screen current client' do
146
+ layout << client
147
+ expect(client).to receive :focus
148
+ 2.times { layout.handle_screen_sel :succ }
149
+ end
150
+
151
+ it 'updates widgets' do
152
+ expect(widget).to receive :update
153
+ layout.handle_screen_sel :succ
154
+ end
155
+ end
156
+
157
+ describe 'handle_screen_set' do
158
+ before { layout << client }
159
+
160
+ it 'removes current client from origin screen' do
161
+ layout.handle_screen_set :succ
162
+ expect(layout.screens[0].tags.flat_map(&:clients)).not_to include client
163
+ end
164
+
165
+ it 'adds current client to consecutive screen in given direction' do
166
+ layout.handle_screen_set :succ
167
+ expect(layout.screens[1].tags.flat_map(&:clients)).to include client
168
+ end
169
+
170
+ it 'selects consecutive screen in given direction' do
171
+ expect { layout.handle_screen_set :succ }
172
+ .to change { layout.current_screen.id }.from(0).to(1)
173
+ end
174
+
175
+ context 'without client' do
176
+ before { layout.remove client }
177
+
178
+ it 'does not raise any error' do
179
+ expect { layout.handle_screen_set :succ }.not_to raise_error
180
+ end
181
+ end
182
+ end
183
+
184
+ describe 'handle_tag_sel' do
185
+ before { layout << client }
186
+
187
+ it 'hides clients on previously selected tag' do
188
+ layout.handle_tag_sel '2'
189
+ expect(client).to be_hidden
190
+ end
191
+
192
+ it 'sets the selected tag as the current one' do
193
+ layout.handle_tag_sel '2'
194
+ expect(layout.current_tag.id).to eq '2'
195
+ end
196
+
197
+ it 'shows and hides clients in selected tag columns' do
198
+ layout.handle_tag_sel '2'
199
+ expect(layout.current_screen.tags[0].columns)
200
+ .to all receive :show_hide_clients
201
+ layout.handle_tag_sel '1'
202
+ end
203
+
204
+ it 'focuses selected tag current client' do
205
+ layout.handle_tag_sel '2'
206
+ expect(client).to receive :focus
207
+ layout.handle_tag_sel '1'
208
+ end
209
+
210
+ it 'updates widgets' do
211
+ expect(widget).to receive :update
212
+ layout.handle_tag_sel '2'
213
+ end
214
+ end
215
+
216
+ describe 'handle_tag_set' do
217
+ context 'without client' do
218
+ it 'does not raise any error' do
219
+ expect { layout.handle_tag_set '2' }.not_to raise_error
220
+ end
221
+ end
222
+
223
+ context 'with a client' do
224
+ before { layout << other_client << client }
225
+
226
+ it 'removes current client from origin tag' do
227
+ origin_tag = layout.current_tag
228
+ layout.handle_tag_set '2'
229
+ expect(origin_tag).not_to include client
230
+ end
231
+
232
+ it 'shows and hides clients in current column' do
233
+ expect(layout.current_column).to receive :show_hide_clients
234
+ layout.handle_tag_set '2'
235
+ end
236
+
237
+ it 'hides current client' do
238
+ expect(client).to receive :hide
239
+ layout.handle_tag_set '2'
240
+ end
241
+
242
+ it 'adds current client to given tag' do
243
+ layout.handle_tag_set '2'
244
+ dest_tag = layout.current_screen.tags.find { |e| e.id == '2' }
245
+ expect(dest_tag).to include client
246
+ end
247
+
248
+ it 'arranges clients in given tag columns' do
249
+ layout.current_screen.tags << tag = Layout::Tag.new('2', geo)
250
+ expect(tag.current_column_or_create).to receive :arrange_clients
251
+ layout.handle_tag_set '2'
252
+ end
253
+
254
+ it 'updates widgets' do
255
+ expect(widget).to receive :update
256
+ layout.handle_tag_set '2'
257
+ end
258
+ end
259
+ end
260
+
261
+ describe '#handle_column_sel' do
262
+ context 'without client' do
263
+ it 'does not raise any error' do
264
+ expect { layout.handle_column_sel :succ }.not_to raise_error
265
+ end
266
+ end
267
+
268
+ context 'with two clients in two columns' do
269
+ before do
270
+ layout << client
271
+ layout.current_tag.columns << Layout::Column.new(geo).tap do |o|
272
+ o << other_client
273
+ end
274
+ end
275
+
276
+ it 'selects the column consecutive to current one in given direction' do
277
+ layout.handle_column_sel :succ
278
+ expect(layout.current_column).to be layout.current_tag.columns[1]
279
+ end
280
+
281
+ it 'focuses the current client of selected column' do
282
+ expect(other_client).to receive :focus
283
+ layout.handle_column_sel :succ
284
+ end
285
+
286
+ it 'updates widgets' do
287
+ expect(widget).to receive :update
288
+ layout.handle_column_sel :succ
289
+ end
290
+ end
291
+ end
292
+
293
+ describe '#handle_client_sel' do
294
+ context 'without client' do
295
+ it 'does not raise any error' do
296
+ expect { layout.handle_client_sel :succ }.not_to raise_error
297
+ end
298
+ end
299
+
300
+ context 'with one column and two clients' do
301
+ before { layout << client << other_client }
302
+
303
+ it 'selects current column consecutive client in given direction' do
304
+ expect { layout.handle_client_sel :pred }
305
+ .to change { layout.current_client }.from(other_client).to(client)
306
+ end
307
+
308
+ it 'focuses current client' do
309
+ expect(client).to receive :focus
310
+ layout.handle_client_sel :pred
311
+ end
312
+
313
+ it 'updates column clients visibility' do
314
+ expect(layout.current_column).to receive :show_hide_clients
315
+ layout.handle_client_sel :pred
316
+ end
317
+
318
+ it 'updates widgets' do
319
+ expect(widget).to receive :update
320
+ layout.handle_client_sel :pred
321
+ end
322
+ end
323
+ end
324
+
325
+ describe '#handle_client_swap' do
326
+ context 'without client' do
327
+ it 'does not raise any error' do
328
+ expect { layout.handle_client_swap :pred }.not_to raise_error
329
+ end
330
+ end
331
+
332
+ context 'with one column and two clients' do
333
+ before { layout << other_client << client }
334
+
335
+ it 'swaps current client with the other client' do
336
+ layout.handle_client_swap :pred
337
+ expect(layout.current_column.clients.to_a)
338
+ .to eq [client, other_client]
339
+ end
340
+
341
+ it 'does not change current client' do
342
+ expect { layout.handle_client_swap :pred }
343
+ .not_to change { layout.current_client }
344
+ end
345
+
346
+ it 'updates widgets' do
347
+ expect(widget).to receive :update
348
+ layout.handle_client_swap :pred
349
+ end
350
+ end
351
+ end
352
+
353
+ describe '#handle_client_column_set' do
354
+ context 'without client' do
355
+ it 'does not raise any error' do
356
+ expect { layout.handle_client_column_set :succ }.not_to raise_error
357
+ end
358
+ end
359
+
360
+ context 'with one column and two clients' do
361
+ let(:arranger) { instance_spy Layout::Column::Arranger }
362
+
363
+ before { layout << other_client << client }
364
+
365
+ it 'moves current client with column arranger' do
366
+ expect(arranger).to receive(:move_current_client).with(:succ)
367
+ layout.handle_client_column_set :succ, arranger: arranger
368
+ end
369
+
370
+ it 'updates columns geos with column arranger' do
371
+ expect(arranger).to receive :update_geos
372
+ layout.handle_client_column_set :succ, arranger: arranger
373
+ end
374
+
375
+ it 'arranges clients in current tag columns' do
376
+ expect(layout.current_tag.columns).to all receive :arrange_clients
377
+ layout.handle_client_column_set :succ
378
+ end
379
+
380
+ it 'shows and hides clients in selected tag columns' do
381
+ expect(layout.current_tag.columns).to all receive :show_hide_clients
382
+ layout.handle_client_column_set :succ
383
+ end
384
+
385
+ it 'does not change current client' do
386
+ expect { layout.handle_client_column_set :succ }
387
+ .not_to change { layout.current_client }
388
+ end
389
+
390
+ it 'updates widgets' do
391
+ expect(widget).to receive :update
392
+ layout.handle_client_column_set :succ
393
+ end
394
+ end
395
+ end
396
+
397
+ describe '#handle_kill_current' do
398
+ context 'without client' do
399
+ it 'does not raise any error' do
400
+ expect { layout.handle_kill_current }.not_to raise_error
401
+ end
402
+ end
403
+
404
+ context 'with a client' do
405
+ before { layout << client }
406
+
407
+ it 'kills current client' do
408
+ expect(client).to receive :kill
409
+ layout.handle_kill_current
410
+ end
411
+ end
412
+ end
413
+ end
414
+ end