teek-ui 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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +452 -0
  4. data/lib/teek/ui/canvas_item.rb +270 -0
  5. data/lib/teek/ui/component_handle.rb +32 -0
  6. data/lib/teek/ui/document.rb +185 -0
  7. data/lib/teek/ui/errors.rb +41 -0
  8. data/lib/teek/ui/event_binding.rb +22 -0
  9. data/lib/teek/ui/event_bus.rb +53 -0
  10. data/lib/teek/ui/grid_validator.rb +89 -0
  11. data/lib/teek/ui/handle.rb +585 -0
  12. data/lib/teek/ui/image.rb +58 -0
  13. data/lib/teek/ui/keysyms.rb +72 -0
  14. data/lib/teek/ui/menu_builder.rb +130 -0
  15. data/lib/teek/ui/menu_entry_addressing.rb +62 -0
  16. data/lib/teek/ui/modal_stack.rb +99 -0
  17. data/lib/teek/ui/mouse_events.rb +31 -0
  18. data/lib/teek/ui/node.rb +141 -0
  19. data/lib/teek/ui/option_dump_parsing.rb +31 -0
  20. data/lib/teek/ui/overlay_anchors.rb +29 -0
  21. data/lib/teek/ui/overlay_validator.rb +33 -0
  22. data/lib/teek/ui/pane_validator.rb +30 -0
  23. data/lib/teek/ui/realized_node.rb +25 -0
  24. data/lib/teek/ui/realizer.rb +577 -0
  25. data/lib/teek/ui/scope.rb +40 -0
  26. data/lib/teek/ui/screens.rb +141 -0
  27. data/lib/teek/ui/session.rb +417 -0
  28. data/lib/teek/ui/tab_validator.rb +30 -0
  29. data/lib/teek/ui/text_content.rb +321 -0
  30. data/lib/teek/ui/tree_inspector.rb +97 -0
  31. data/lib/teek/ui/validator.rb +131 -0
  32. data/lib/teek/ui/var.rb +98 -0
  33. data/lib/teek/ui/version.rb +7 -0
  34. data/lib/teek/ui/widget_addressing.rb +47 -0
  35. data/lib/teek/ui/widget_dsl.rb +512 -0
  36. data/lib/teek/ui/widget_type.rb +242 -0
  37. data/lib/teek/ui/widget_types/button.rb +7 -0
  38. data/lib/teek/ui/widget_types/canvas.rb +15 -0
  39. data/lib/teek/ui/widget_types/checkbox.rb +7 -0
  40. data/lib/teek/ui/widget_types/column.rb +14 -0
  41. data/lib/teek/ui/widget_types/context_menu.rb +15 -0
  42. data/lib/teek/ui/widget_types/divider.rb +10 -0
  43. data/lib/teek/ui/widget_types/dropdown.rb +7 -0
  44. data/lib/teek/ui/widget_types/grid.rb +12 -0
  45. data/lib/teek/ui/widget_types/group.rb +7 -0
  46. data/lib/teek/ui/widget_types/label.rb +7 -0
  47. data/lib/teek/ui/widget_types/list.rb +7 -0
  48. data/lib/teek/ui/widget_types/menu_bar.rb +26 -0
  49. data/lib/teek/ui/widget_types/menu_checkbox.rb +14 -0
  50. data/lib/teek/ui/widget_types/menu_item.rb +20 -0
  51. data/lib/teek/ui/widget_types/menu_radio.rb +14 -0
  52. data/lib/teek/ui/widget_types/number_box.rb +7 -0
  53. data/lib/teek/ui/widget_types/pane.rb +37 -0
  54. data/lib/teek/ui/widget_types/panel.rb +7 -0
  55. data/lib/teek/ui/widget_types/progress.rb +7 -0
  56. data/lib/teek/ui/widget_types/radio.rb +7 -0
  57. data/lib/teek/ui/widget_types/row.rb +14 -0
  58. data/lib/teek/ui/widget_types/scrollable.rb +17 -0
  59. data/lib/teek/ui/widget_types/slider.rb +7 -0
  60. data/lib/teek/ui/widget_types/spacer.rb +14 -0
  61. data/lib/teek/ui/widget_types/split.rb +17 -0
  62. data/lib/teek/ui/widget_types/tab.rb +35 -0
  63. data/lib/teek/ui/widget_types/table.rb +9 -0
  64. data/lib/teek/ui/widget_types/tabs.rb +10 -0
  65. data/lib/teek/ui/widget_types/text_area.rb +7 -0
  66. data/lib/teek/ui/widget_types/text_box.rb +7 -0
  67. data/lib/teek/ui/widget_types/tree.rb +9 -0
  68. data/lib/teek/ui/widget_types/window.rb +50 -0
  69. data/lib/teek/ui/widget_types.rb +121 -0
  70. data/lib/teek/ui/widget_validators.rb +62 -0
  71. data/lib/teek/ui.rb +54 -0
  72. data/teek-ui.gemspec +24 -0
  73. data/test/support/fake_app.rb +57 -0
  74. data/test/test_busy.rb +82 -0
  75. data/test/test_canvas_items.rb +551 -0
  76. data/test/test_clipboard.rb +87 -0
  77. data/test/test_close_handling.rb +118 -0
  78. data/test/test_component.rb +151 -0
  79. data/test/test_component_realtk.rb +120 -0
  80. data/test/test_debug_info.rb +158 -0
  81. data/test/test_document.rb +317 -0
  82. data/test/test_event_bus.rb +163 -0
  83. data/test/test_events.rb +164 -0
  84. data/test/test_fake_app_contract.rb +62 -0
  85. data/test/test_grid.rb +97 -0
  86. data/test/test_handle.rb +386 -0
  87. data/test/test_handle_destroy_realtk.rb +344 -0
  88. data/test/test_helper.rb +20 -0
  89. data/test/test_image.rb +24 -0
  90. data/test/test_image_realtk.rb +146 -0
  91. data/test/test_incremental_realize.rb +203 -0
  92. data/test/test_keysyms.rb +76 -0
  93. data/test/test_layout.rb +185 -0
  94. data/test/test_lazy_realize.rb +128 -0
  95. data/test/test_managed_window.rb +304 -0
  96. data/test/test_menu_dsl.rb +230 -0
  97. data/test/test_menu_entry_addressing.rb +73 -0
  98. data/test/test_menu_realize.rb +296 -0
  99. data/test/test_modal_handle.rb +147 -0
  100. data/test/test_modal_stack.rb +219 -0
  101. data/test/test_modal_stack_realtk.rb +131 -0
  102. data/test/test_native_scrollable.rb +250 -0
  103. data/test/test_node.rb +193 -0
  104. data/test/test_overlay.rb +139 -0
  105. data/test/test_raw.rb +58 -0
  106. data/test/test_reactive_vars.rb +118 -0
  107. data/test/test_realizer.rb +264 -0
  108. data/test/test_scope.rb +38 -0
  109. data/test/test_screens.rb +260 -0
  110. data/test/test_screens_realtk.rb +314 -0
  111. data/test/test_scrollable.rb +185 -0
  112. data/test/test_scrollable_wheel.rb +172 -0
  113. data/test/test_scrollbar_auto_hide.rb +127 -0
  114. data/test/test_split.rb +145 -0
  115. data/test/test_tabs.rb +201 -0
  116. data/test/test_text_content.rb +664 -0
  117. data/test/test_toast.rb +126 -0
  118. data/test/test_tree_inspector.rb +183 -0
  119. data/test/test_ui.rb +428 -0
  120. data/test/test_validator.rb +209 -0
  121. data/test/test_var.rb +32 -0
  122. data/test/test_widget_dsl.rb +621 -0
  123. data/test/test_widget_introspection.rb +126 -0
  124. data/test/test_widget_types.rb +429 -0
  125. data/test/test_widget_types_realtk.rb +82 -0
  126. data/test/test_widget_validators.rb +82 -0
  127. metadata +207 -0
@@ -0,0 +1,621 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+ require 'teek/ui/session'
5
+
6
+ class TestWidgetDsl < Minitest::Test
7
+ LEAF_WIDGET_TYPES = %i[
8
+ text_box text_area label button checkbox radio slider dropdown
9
+ number_box list table tree progress divider
10
+ ].freeze
11
+
12
+ def build_session
13
+ Teek::UI::Session.new(title: 'Widget DSL Test')
14
+ end
15
+
16
+ def test_each_leaf_widget_method_appends_a_node_of_the_matching_type
17
+ LEAF_WIDGET_TYPES.each do |method|
18
+ session = build_session
19
+
20
+ handle = session.send(method, :w, text: 'x')
21
+
22
+ node = session.document.root.children.first
23
+ assert_equal method, node.type, "##{method} should create a :#{method} node"
24
+ assert_equal({ text: 'x' }, node.opts)
25
+ assert_kind_of Teek::UI::Handle, handle
26
+ assert_equal method, handle.type
27
+ end
28
+ end
29
+
30
+ def test_leaf_widgets_work_unnamed
31
+ session = build_session
32
+
33
+ session.label(text: 'Hi')
34
+
35
+ node = session.document.root.children.first
36
+ assert_equal :label, node.type
37
+ assert_nil node.name
38
+ refute_nil node.key
39
+ end
40
+
41
+ def test_named_widget_is_addressable_via_bracket_lookup
42
+ session = build_session
43
+
44
+ session.text_box(:query)
45
+
46
+ handle = session[:query]
47
+ assert_kind_of Teek::UI::Handle, handle
48
+ assert_equal :text_box, handle.type
49
+ assert_equal :query, handle.name
50
+ end
51
+
52
+ def test_bracket_lookup_returns_nil_for_an_unknown_name
53
+ session = build_session
54
+
55
+ assert_nil session[:nope]
56
+ end
57
+
58
+ def test_duplicate_name_raises_through_the_dsl
59
+ session = build_session
60
+ session.button(:save)
61
+
62
+ assert_raises(ArgumentError) { session.button(:save) }
63
+ end
64
+
65
+ def test_panel_nests_children_declared_in_its_block
66
+ session = build_session
67
+
68
+ session.panel(:controls) do |p|
69
+ p.button(:go, text: 'Go')
70
+ p.button(:stop, text: 'Stop')
71
+ end
72
+
73
+ panel_node = session.document.root.children.first
74
+ assert_equal :panel, panel_node.type
75
+ assert_equal [:button, :button], panel_node.children.map(&:type)
76
+ assert_equal [:go, :stop], panel_node.children.map(&:name)
77
+ end
78
+
79
+ def test_container_block_yields_the_same_session_object
80
+ session = build_session
81
+ yielded = nil
82
+
83
+ session.panel(:controls) { |p| yielded = p }
84
+
85
+ # not a separate scoped builder - the same object, so a name declared
86
+ # inside the block is addressable from outside it too via ui[:name]
87
+ assert_same session, yielded
88
+ end
89
+
90
+ def test_nested_containers_attach_at_the_correct_depth
91
+ session = build_session
92
+
93
+ session.panel(:outer) do |outer|
94
+ outer.panel(:inner) do |inner|
95
+ inner.button(:deep)
96
+ end
97
+ end
98
+
99
+ outer_node = session.document.root.children.first
100
+ inner_node = outer_node.children.first
101
+ deep_node = inner_node.children.first
102
+
103
+ assert_equal :panel, outer_node.type
104
+ assert_equal :panel, inner_node.type
105
+ assert_equal :button, deep_node.type
106
+ assert_equal [], deep_node.children
107
+ end
108
+
109
+ def test_a_container_without_a_block_still_creates_a_childless_node
110
+ session = build_session
111
+
112
+ session.window(:settings, title: 'Settings')
113
+
114
+ node = session.document.root.children.first
115
+ assert_equal :window, node.type
116
+ assert_equal [], node.children
117
+ end
118
+
119
+ def test_box_is_a_synonym_for_panel
120
+ session = build_session
121
+
122
+ session.box(:sidebar)
123
+
124
+ node = session.document.root.children.first
125
+ assert_equal :panel, node.type
126
+ end
127
+
128
+ %i[group canvas window].each do |container|
129
+ define_method("test_#{container}_is_a_container_that_nests_children") do
130
+ session = build_session
131
+
132
+ session.send(container, :c) { |b| b.label(:inner_label) }
133
+
134
+ node = session.document.root.children.first
135
+ assert_equal container, node.type
136
+ assert_equal [:label], node.children.map(&:type)
137
+ end
138
+ end
139
+
140
+ def test_var_is_tracked_on_the_session
141
+ session = build_session
142
+
143
+ speed = session.var(5)
144
+
145
+ assert_kind_of Teek::UI::Var, speed
146
+ assert_includes session.vars, speed
147
+ end
148
+
149
+ def test_var_names_are_unique_within_a_session
150
+ session = build_session
151
+
152
+ a = session.var(1)
153
+ b = session.var(2)
154
+
155
+ refute_equal a.name, b.name
156
+ end
157
+
158
+ def test_bind_translates_to_the_variable_option_for_a_slider
159
+ session = build_session
160
+ speed = session.var(5)
161
+
162
+ session.slider(:s, from: 1, to: 10, bind: speed)
163
+
164
+ node = session.document.root.children.first
165
+ assert_equal speed.name, node.opts[:variable]
166
+ refute node.opts.key?(:bind), "bind: should not leak through to the realized widget options"
167
+ end
168
+
169
+ def test_bind_translates_to_the_textvariable_option_for_a_text_box
170
+ session = build_session
171
+ speed = session.var(5)
172
+
173
+ session.text_box(:t, bind: speed)
174
+
175
+ node = session.document.root.children.first
176
+ assert_equal speed.name, node.opts[:textvariable]
177
+ end
178
+
179
+ def test_image_is_tracked_on_the_session
180
+ session = build_session
181
+
182
+ icon = session.image('/tmp/whatever.png')
183
+
184
+ assert_kind_of Teek::UI::Image, icon
185
+ assert_includes session.images, icon
186
+ end
187
+
188
+ def test_image_names_are_unique_within_a_session
189
+ session = build_session
190
+
191
+ a = session.image('/tmp/a.png')
192
+ b = session.image('/tmp/b.png')
193
+
194
+ refute_equal a.name, b.name
195
+ end
196
+
197
+ def test_a_widgets_image_option_holds_the_declared_image_directly
198
+ session = build_session
199
+ icon = session.image('/tmp/whatever.png')
200
+
201
+ session.label(:pic, image: icon)
202
+
203
+ node = session.document.root.children.first
204
+ assert_same icon, node.opts[:image]
205
+ end
206
+
207
+ def test_bind_on_an_unsupported_widget_type_raises
208
+ session = build_session
209
+ speed = session.var(5)
210
+
211
+ assert_raises(ArgumentError) { session.divider(:d, bind: speed) }
212
+ end
213
+
214
+ def test_scroll_on_an_unsupported_widget_type_raises
215
+ session = build_session
216
+
217
+ error = assert_raises(ArgumentError) { session.button(:go, scroll: true) }
218
+ assert_match(/scroll:/, error.message)
219
+ end
220
+
221
+ def test_scroll_on_a_natively_scrollable_widget_type_does_not_raise
222
+ session = build_session
223
+
224
+ session.list(:items, scroll: false)
225
+ session.canvas(:board, scroll: true)
226
+
227
+ assert_equal [false, true], session.document.root.children.map { |n| n.opts[:scroll] }
228
+ end
229
+
230
+ def test_column_and_row_are_containers_carrying_gap_align_pad_in_opts
231
+ session = build_session
232
+
233
+ session.column(:c, gap: 8, align: :stretch, pad: 4) { |c| c.button(:go) }
234
+
235
+ node = session.document.root.children.first
236
+ assert_equal :column, node.type
237
+ assert_equal({ gap: 8, align: :stretch, pad: 4 }, node.opts)
238
+ assert_equal [:button], node.children.map(&:type)
239
+ end
240
+
241
+ def test_row_defaults_gap_align_pad_when_not_given
242
+ session = build_session
243
+
244
+ session.row(:r)
245
+
246
+ node = session.document.root.children.first
247
+ assert_equal :row, node.type
248
+ assert_equal({}, node.opts)
249
+ end
250
+
251
+ def test_grow_is_captured_on_the_childs_layout_and_stripped_from_opts
252
+ session = build_session
253
+
254
+ session.column(:c) { |c| c.button(:go, text: 'Go', grow: true) }
255
+
256
+ button_node = session.document.root.children.first.children.first
257
+ assert_equal({ grow: true }, button_node.layout)
258
+ assert_equal({ text: 'Go' }, button_node.opts)
259
+ end
260
+
261
+ def test_grow_defaults_to_nil_layout_when_not_given
262
+ session = build_session
263
+
264
+ session.button(:go)
265
+
266
+ node = session.document.root.children.first
267
+ assert_nil node.layout
268
+ end
269
+
270
+ def test_grow_works_on_a_container_child_too
271
+ session = build_session
272
+
273
+ session.column(:outer) { |o| o.row(:inner, grow: true) }
274
+
275
+ inner_node = session.document.root.children.first.children.first
276
+ assert_equal({ grow: true }, inner_node.layout)
277
+ end
278
+
279
+ def test_lazy_true_marks_the_container_node_lazy_and_is_stripped_from_opts
280
+ session = build_session
281
+
282
+ session.panel(:picker, lazy: true, text: 'ignored opt just for this test')
283
+
284
+ node = session.document.root.children.first
285
+ assert node.lazy?
286
+ refute node.opts.key?(:lazy)
287
+ end
288
+
289
+ def test_lazy_defaults_to_false_when_not_given
290
+ session = build_session
291
+
292
+ session.panel(:picker)
293
+
294
+ node = session.document.root.children.first
295
+ refute node.lazy?
296
+ end
297
+
298
+ def test_spacer_is_a_leaf_node_with_grow_baked_in
299
+ session = build_session
300
+
301
+ session.column(:c) { |c| c.spacer }
302
+
303
+ spacer_node = session.document.root.children.first.children.first
304
+ assert_equal :spacer, spacer_node.type
305
+ assert_equal({ grow: true }, spacer_node.layout)
306
+ assert_equal [], spacer_node.children
307
+ end
308
+
309
+ def test_grid_is_a_container_type
310
+ session = build_session
311
+
312
+ session.grid(:g, gap: 6) { |g| g.cell(row: 0, col: 0) { g.label(text: 'User') } }
313
+
314
+ node = session.document.root.children.first
315
+ assert_equal :grid, node.type
316
+ assert_equal({ gap: 6 }, node.opts)
317
+ end
318
+
319
+ def test_cell_tags_the_single_widget_it_creates_with_row_col_span
320
+ session = build_session
321
+
322
+ session.grid(:g) { |g| g.cell(row: 1, col: 2, span: 3) { g.label(:l, text: 'x') } }
323
+
324
+ label_node = session.document.root.children.first.children.first
325
+ assert_equal({ row: 1, col: 2, span: 3 }, label_node.layout[:cell])
326
+ end
327
+
328
+ def test_cell_span_defaults_to_1
329
+ session = build_session
330
+
331
+ session.grid(:g) { |g| g.cell(row: 0, col: 0) { g.label(:l, text: 'x') } }
332
+
333
+ label_node = session.document.root.children.first.children.first
334
+ assert_equal 1, label_node.layout[:cell][:span]
335
+ end
336
+
337
+ def test_cell_merges_with_an_existing_grow_layout_intent
338
+ session = build_session
339
+
340
+ session.grid(:g) { |g| g.cell(row: 0, col: 0) { g.text_box(:t, grow: true) } }
341
+
342
+ node = session.document.root.children.first.children.first
343
+ assert_equal true, node.layout[:grow]
344
+ assert_equal({ row: 0, col: 0, span: 1 }, node.layout[:cell])
345
+ end
346
+
347
+ def test_cell_raises_if_its_block_creates_no_widget
348
+ session = build_session
349
+
350
+ error = assert_raises(ArgumentError) { session.grid(:g) { |g| g.cell(row: 0, col: 0) { } } }
351
+ assert_match(/exactly one widget/, error.message)
352
+ end
353
+
354
+ def test_cell_raises_if_its_block_creates_more_than_one_widget
355
+ session = build_session
356
+
357
+ error = assert_raises(ArgumentError) do
358
+ session.grid(:g) { |g| g.cell(row: 0, col: 0) { g.label(:a); g.label(:b) } }
359
+ end
360
+ assert_match(/exactly one widget/, error.message)
361
+ end
362
+
363
+ def test_cell_outside_a_grid_raises
364
+ session = build_session
365
+
366
+ error = assert_raises(ArgumentError) { session.cell(row: 0, col: 0) { } }
367
+ assert_match(/grid/, error.message)
368
+ end
369
+
370
+ def test_stretch_sets_stretch_columns_and_rows_on_the_grid_nodes_opts
371
+ session = build_session
372
+
373
+ session.grid(:g) { |g| g.stretch(columns: [1], rows: [0]) }
374
+
375
+ node = session.document.root.children.first
376
+ assert_equal [1], node.opts[:stretch_columns]
377
+ assert_equal [0], node.opts[:stretch_rows]
378
+ end
379
+
380
+ def test_stretch_outside_a_grid_raises
381
+ session = build_session
382
+
383
+ error = assert_raises(ArgumentError) { session.stretch(columns: [0]) }
384
+ assert_match(/grid/, error.message)
385
+ end
386
+
387
+ def test_tabs_is_a_container_type
388
+ session = build_session
389
+
390
+ session.tabs(:t) { |t| t.tab('General') { |g| g.button(:go, text: 'Go') } }
391
+
392
+ node = session.document.root.children.first
393
+ assert_equal :tabs, node.type
394
+ assert_equal [:tab], node.children.map(&:type)
395
+ end
396
+
397
+ def test_tab_stashes_its_label_and_nests_its_blocks_children
398
+ session = build_session
399
+
400
+ session.tabs { |t| t.tab('General') { |g| g.button(:go, text: 'Go') } }
401
+
402
+ tab_node = session.document.root.children.first.children.first
403
+ assert_equal 'General', tab_node.opts[:tab_label]
404
+ assert_equal [:button], tab_node.children.map(&:type)
405
+ assert_equal [:go], tab_node.children.map(&:name)
406
+ end
407
+
408
+ def test_tab_accepts_an_optional_name_for_ui_bracket_lookup
409
+ session = build_session
410
+
411
+ session.tabs { |t| t.tab('Advanced', :advanced_tab) }
412
+
413
+ assert_equal :advanced_tab, session[:advanced_tab].name
414
+ assert_equal :tab, session[:advanced_tab].type
415
+ end
416
+
417
+ def test_tab_outside_ui_tabs_raises
418
+ session = build_session
419
+
420
+ error = assert_raises(ArgumentError) { session.tab('General') }
421
+ assert_match(/ui\.tabs/, error.message)
422
+ end
423
+
424
+ def test_split_is_a_container_type
425
+ session = build_session
426
+
427
+ session.split { |s| s.pane { |p| p.button(:go, text: 'Go') } }
428
+
429
+ node = session.document.root.children.first
430
+ assert_equal :split, node.type
431
+ assert_equal [:pane], node.children.map(&:type)
432
+ end
433
+
434
+ def test_split_defaults_to_horizontal_orientation
435
+ session = build_session
436
+
437
+ session.split { }
438
+
439
+ node = session.document.root.children.first
440
+ assert_equal 'horizontal', node.opts[:orient]
441
+ end
442
+
443
+ def test_split_accepts_vertical_orientation
444
+ session = build_session
445
+
446
+ session.split(orientation: :vertical) { }
447
+
448
+ node = session.document.root.children.first
449
+ assert_equal 'vertical', node.opts[:orient]
450
+ end
451
+
452
+ def test_split_raises_on_an_invalid_orientation
453
+ session = build_session
454
+
455
+ error = assert_raises(ArgumentError) { session.split(orientation: :diagonal) }
456
+ assert_match(/:horizontal or :vertical/, error.message)
457
+ end
458
+
459
+ def test_split_accepts_a_name_like_any_other_container
460
+ session = build_session
461
+
462
+ session.split(:main) { |s| s.pane { } }
463
+
464
+ node = session.document.root.children.first
465
+ assert_equal :main, node.name
466
+ end
467
+
468
+ def test_pane_nests_its_blocks_children
469
+ session = build_session
470
+
471
+ session.split { |s| s.pane { |p| p.button(:go, text: 'Go') } }
472
+
473
+ pane_node = session.document.root.children.first.children.first
474
+ assert_equal [:button], pane_node.children.map(&:type)
475
+ assert_equal [:go], pane_node.children.map(&:name)
476
+ end
477
+
478
+ def test_pane_accepts_an_optional_name_for_ui_bracket_lookup
479
+ session = build_session
480
+
481
+ session.split { |s| s.pane(:left) }
482
+
483
+ assert_equal :left, session[:left].name
484
+ assert_equal :pane, session[:left].type
485
+ end
486
+
487
+ def test_pane_weight_is_stashed_in_opts_without_leaking_weight_itself
488
+ session = build_session
489
+
490
+ session.split { |s| s.pane(weight: 2) }
491
+
492
+ pane_node = session.document.root.children.first.children.first
493
+ assert_equal 2, pane_node.opts[:pane_weight]
494
+ refute pane_node.opts.key?(:weight)
495
+ end
496
+
497
+ def test_pane_without_a_weight_stashes_nothing
498
+ session = build_session
499
+
500
+ session.split { |s| s.pane }
501
+
502
+ pane_node = session.document.root.children.first.children.first
503
+ refute pane_node.opts.key?(:pane_weight)
504
+ end
505
+
506
+ def test_pane_outside_ui_split_raises
507
+ session = build_session
508
+
509
+ error = assert_raises(ArgumentError) { session.pane }
510
+ assert_match(/ui\.split/, error.message)
511
+ end
512
+
513
+ def test_raw_creates_a_raw_op_node_attached_to_the_current_parent
514
+ session = build_session
515
+
516
+ session.column(:c) { |c| c.raw { |_app| } }
517
+
518
+ node = session.document.root.children.first.children.first
519
+ assert_equal :raw_op, node.type
520
+ assert_kind_of Proc, node.opts[:block]
521
+ end
522
+
523
+ def test_raw_does_not_execute_the_block_during_build
524
+ session = build_session
525
+ executed = false
526
+
527
+ session.raw { |_app| executed = true }
528
+
529
+ refute executed, "ui.raw should defer its block to realize, not run it during build"
530
+ end
531
+
532
+ def test_scrollable_nests_children_declared_in_its_block
533
+ session = build_session
534
+
535
+ session.scrollable(:region) do |s|
536
+ s.list(:items)
537
+ end
538
+
539
+ node = session.document.root.children.first
540
+ assert_equal :scrollable, node.type
541
+ assert_equal [:list], node.children.map(&:type)
542
+ assert_equal [:items], node.children.map(&:name)
543
+ end
544
+
545
+ def test_scrollable_x_and_y_opts_land_on_the_node
546
+ session = build_session
547
+
548
+ session.scrollable(:region, x: true, y: false) { |s| s.list(:items) }
549
+
550
+ node = session.document.root.children.first
551
+ assert_equal true, node.opts[:x]
552
+ assert_equal false, node.opts[:y]
553
+ end
554
+
555
+ def test_scrollable_without_a_block_still_creates_a_childless_node
556
+ session = build_session
557
+
558
+ session.scrollable(:region)
559
+
560
+ node = session.document.root.children.first
561
+ assert_equal :scrollable, node.type
562
+ assert_equal [], node.children
563
+ end
564
+
565
+ def test_screens_is_memoized_across_calls
566
+ session = build_session
567
+
568
+ assert_same session.screens, session.screens
569
+ end
570
+
571
+ def test_modal_defaults_to_nil
572
+ session = build_session
573
+
574
+ assert_nil session.modal
575
+ end
576
+
577
+ def test_modal_is_assignable_and_reads_back
578
+ session = build_session
579
+ stack = Teek::UI::ModalStack.new(on_enter: ->(_) { }, on_exit: -> { })
580
+
581
+ session.modal = stack
582
+
583
+ assert_same stack, session.modal
584
+ end
585
+
586
+ def test_current_path_at_the_top_level_is_top_level
587
+ session = build_session
588
+
589
+ assert_equal '(top level)', session.current_path
590
+ end
591
+
592
+ def test_current_path_reflects_open_containers
593
+ session = build_session
594
+
595
+ session.column do |c|
596
+ c.row { assert_equal 'column > row', session.current_path }
597
+ end
598
+ end
599
+
600
+ def test_current_path_includes_a_containers_name
601
+ session = build_session
602
+
603
+ session.column(:ctrl) { assert_equal 'column(:ctrl)', session.current_path }
604
+ end
605
+
606
+ def test_current_path_returns_to_top_level_after_the_block_closes
607
+ session = build_session
608
+
609
+ session.column { }
610
+
611
+ assert_equal '(top level)', session.current_path
612
+ end
613
+
614
+ def test_current_path_inside_a_menu_subtree
615
+ session = build_session
616
+
617
+ session.menu_bar do |mb|
618
+ mb.menu(label: 'File') { assert_equal 'menu_bar > menu', session.current_path }
619
+ end
620
+ end
621
+ end