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
data/test/test_grid.rb ADDED
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../../test/tk_test_helper'
5
+
6
+ class TestGrid < Minitest::Test
7
+ include TeekTestHelper
8
+
9
+ def test_labeled_field_form_realizes_at_the_right_positions
10
+ assert_tk_app("the canonical 2-column labeled-field form should realize with correct grid positions") do
11
+ require 'teek/ui'
12
+
13
+ session = Teek::UI.app(title: 'Grid Test') do |ui|
14
+ ui.grid(:form, gap: 4) do |g|
15
+ g.cell(row: 0, col: 0) { g.label(text: 'Name:') }
16
+ g.cell(row: 0, col: 1) { g.text_box(:name_field) }
17
+ g.cell(row: 1, col: 0) { g.label(text: 'Email:') }
18
+ g.cell(row: 1, col: 1) { g.text_box(:email_field) }
19
+ g.stretch(columns: [1])
20
+ end
21
+ end
22
+ session.run_async
23
+ session.app.update
24
+
25
+ name_info = session.app.command(:grid, :info, session[:name_field].path)
26
+ assert_match(/-row 0/, name_info)
27
+ assert_match(/-column 1/, name_info)
28
+
29
+ email_info = session.app.command(:grid, :info, session[:email_field].path)
30
+ assert_match(/-row 1/, email_info)
31
+ assert_match(/-column 1/, email_info)
32
+
33
+ # the input column (1) is the one that should absorb extra width
34
+ weight = session.app.tcl_eval("grid columnconfigure #{session[:form].path} 1 -weight")
35
+ assert_equal '1', weight
36
+
37
+ session.app.destroy
38
+ end
39
+ end
40
+
41
+ def test_span_produces_a_real_columnspan
42
+ assert_tk_app("span: should produce a real -columnspan in the realized grid") do
43
+ require 'teek/ui'
44
+
45
+ session = Teek::UI.app(title: 'Grid Test') do |ui|
46
+ ui.grid(:form) do |g|
47
+ g.cell(row: 0, col: 0) { g.label(text: 'Name:') }
48
+ g.cell(row: 0, col: 1) { g.text_box(:name_field) }
49
+ g.cell(row: 1, col: 0, span: 2) { g.divider(:sep) }
50
+ end
51
+ end
52
+ session.run_async
53
+ session.app.update
54
+
55
+ sep_info = session.app.command(:grid, :info, session[:sep].path)
56
+ assert_match(/-columnspan 2/, sep_info)
57
+
58
+ session.app.destroy
59
+ end
60
+ end
61
+
62
+ def test_a_widget_placed_directly_in_a_grid_without_cell_is_caught_by_validation
63
+ assert_tk_app("a grid child never wrapped in g.cell should be caught by validation, before any Tk call happens") do
64
+ require 'teek/ui'
65
+
66
+ session = Teek::UI.app(title: 'Grid Test') do |ui|
67
+ ui.grid(:form) { |g| g.label(:oops, text: 'no cell') }
68
+ end
69
+
70
+ error = assert_raises(Teek::UI::ValidationError) { session.realize }
71
+ assert_match(/cell/i, error.message)
72
+ assert_match(/oops/, error.message)
73
+
74
+ # validation runs before any interpreter is constructed at all - a
75
+ # failed realize never gets far enough to leave anything to clean up.
76
+ assert_raises(Teek::UI::NotRealizedError) { session.app }
77
+ end
78
+ end
79
+
80
+ def test_a_widget_added_to_a_grid_without_cell_via_session_add_still_raises_at_realize
81
+ assert_tk_app("session.add skips validation, so the realizer's own backstop check is what catches this instead") do
82
+ require 'teek/ui'
83
+
84
+ session = Teek::UI.app(title: 'Grid Test') do |ui|
85
+ ui.grid(:form) { |g| g.cell(row: 0, col: 0) { g.label(text: 'Name:') } }
86
+ end
87
+ session.run_async
88
+ session.app.update
89
+
90
+ error = assert_raises(ArgumentError) { session.add(:form) { |a| a.label(:oops, text: 'no cell') } }
91
+ assert_match(/cell/i, error.message)
92
+ assert_match(/oops/, error.message)
93
+
94
+ session.app.destroy
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,386 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+ require 'teek/ui/node'
5
+ require 'teek/ui/handle'
6
+ require 'teek/ui/widget_types'
7
+
8
+ class TestHandle < Minitest::Test
9
+ def test_path_raises_before_realize
10
+ node = Teek::UI::Node.new(type: :button, name: :save)
11
+ handle = Teek::UI::Handle.new(node)
12
+
13
+ error = assert_raises(Teek::UI::NotRealizedError) { handle.path }
14
+ assert_match(/not realized/i, error.message)
15
+ end
16
+
17
+ def test_configure_raises_before_realize
18
+ node = Teek::UI::Node.new(type: :button, name: :save)
19
+ handle = Teek::UI::Handle.new(node)
20
+
21
+ assert_raises(Teek::UI::NotRealizedError) { handle.configure(text: 'Go') }
22
+ end
23
+
24
+ def test_app_raises_before_realize
25
+ node = Teek::UI::Node.new(type: :button, name: :save)
26
+ handle = Teek::UI::Handle.new(node)
27
+
28
+ assert_raises(Teek::UI::NotRealizedError) { handle.app }
29
+ end
30
+
31
+ def test_app_returns_the_realized_app_once_realized
32
+ app = FakeApp.new
33
+ node = Teek::UI::Node.new(type: :button, name: :save)
34
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.win.save')
35
+ handle = Teek::UI::Handle.new(node)
36
+
37
+ assert_same app, handle.app
38
+ end
39
+
40
+ def test_path_returns_the_real_path_once_realized
41
+ node = Teek::UI::Node.new(type: :button, name: :save)
42
+ node.realized = Teek::UI::RealizedNode.new(app: FakeApp.new([]), path: '.win.save')
43
+ handle = Teek::UI::Handle.new(node)
44
+
45
+ assert_equal '.win.save', handle.path
46
+ end
47
+
48
+ def test_configure_delegates_to_the_realized_apps_command_once_realized
49
+ app = FakeApp.new([])
50
+ node = Teek::UI::Node.new(type: :button, name: :save)
51
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.win.save')
52
+ handle = Teek::UI::Handle.new(node)
53
+
54
+ handle.configure(text: 'Go', width: 10)
55
+
56
+ assert_equal [[['.win.save', :configure], { text: 'Go', width: 10 }]], app.calls
57
+ end
58
+
59
+ # Handle itself carries zero per-type addressing knowledge - it resolves
60
+ # a type's WidgetType#addressing strategy from the registry and
61
+ # delegates #configure/#path entirely to it. A plugin/custom widget
62
+ # type registering its own addressing: works correctly with NO changes
63
+ # to Handle's own code - this test IS the proof of that, not an
64
+ # incidental check of one more type.
65
+ def test_configure_and_path_delegate_via_a_custom_registered_addressing_strategy
66
+ calls = []
67
+ strategy_class = Class.new do
68
+ define_method(:initialize) { |node| @node = node }
69
+ define_method(:virtual_path) { "FAKE:#{@node.name}" }
70
+ define_method(:configure) { |**opts| calls << [@node, opts] }
71
+ end
72
+
73
+ Teek::UI::WidgetTypes.register(
74
+ Teek::UI::WidgetType.new(
75
+ type: :__test_handle_custom_addressing__, tk_command: 'ttk::label', addressing: strategy_class
76
+ )
77
+ )
78
+
79
+ node = Teek::UI::Node.new(type: :__test_handle_custom_addressing__, name: :thing)
80
+ handle = Teek::UI::Handle.new(node)
81
+
82
+ handle.configure(text: 'hi')
83
+
84
+ assert_equal [[node, { text: 'hi' }]], calls
85
+ assert_equal 'FAKE:thing', handle.path
86
+ end
87
+
88
+ def test_type_and_name_reflect_the_underlying_node_at_any_phase
89
+ node = Teek::UI::Node.new(type: :button, name: :save)
90
+ handle = Teek::UI::Handle.new(node)
91
+
92
+ assert_equal :button, handle.type
93
+ assert_equal :save, handle.name
94
+ end
95
+
96
+ def test_on_click_queues_an_event_binding_before_realize
97
+ node = Teek::UI::Node.new(type: :button, name: :go)
98
+ handle = Teek::UI::Handle.new(node)
99
+ clicked = -> { }
100
+
101
+ result = handle.on_click(&clicked)
102
+
103
+ assert_same handle, result, "on_click should return self for chaining"
104
+ assert_equal 1, node.events.length
105
+ binding = node.events.first
106
+ assert_equal '<Button-1>', binding.event
107
+ assert_equal clicked, binding.handler
108
+ assert_nil binding.target
109
+ end
110
+
111
+ def test_on_click_wires_immediately_once_already_realized
112
+ app = FakeApp.new
113
+ node = Teek::UI::Node.new(type: :button, name: :go)
114
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.win.go')
115
+ handle = Teek::UI::Handle.new(node)
116
+
117
+ handle.on_click { }
118
+
119
+ assert_equal 1, app.binds.length
120
+ assert_equal '.win.go', app.binds.first[:path]
121
+ assert_equal '<Button-1>', app.binds.first[:event]
122
+ end
123
+
124
+ def test_on_click_wired_after_realize_still_shows_up_in_events
125
+ app = FakeApp.new
126
+ node = Teek::UI::Node.new(type: :button, name: :go)
127
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.win.go')
128
+ handle = Teek::UI::Handle.new(node)
129
+
130
+ handle.on_click { }
131
+
132
+ assert_equal 1, node.events.length, "an event bound after realize should still be visible via #events - " \
133
+ "it wires immediately AND is recorded, not one or the other"
134
+ assert_equal '<Button-1>', node.events.first.event
135
+ end
136
+
137
+ def test_events_returns_every_binding_declared_so_far_before_realize
138
+ node = Teek::UI::Node.new(type: :button, name: :go)
139
+ handle = Teek::UI::Handle.new(node)
140
+
141
+ handle.on_click { }
142
+ handle.on_key(:enter) { }
143
+
144
+ assert_equal ['<Button-1>', '<Return>'], handle.events.map(&:event)
145
+ end
146
+
147
+ def test_events_reflects_bindings_from_both_before_and_after_realize
148
+ app = FakeApp.new
149
+ node = Teek::UI::Node.new(type: :button, name: :go)
150
+ handle = Teek::UI::Handle.new(node)
151
+ handle.on_click { }
152
+
153
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.win.go')
154
+ handle.on_key(:enter) { }
155
+
156
+ assert_equal ['<Button-1>', '<Return>'], handle.events.map(&:event)
157
+ end
158
+
159
+ def test_events_is_empty_for_a_handle_with_nothing_bound
160
+ node = Teek::UI::Node.new(type: :button, name: :go)
161
+ handle = Teek::UI::Handle.new(node)
162
+
163
+ assert_equal [], handle.events
164
+ end
165
+
166
+ def test_on_key_friendly_symbol_queues_the_resolved_pattern
167
+ node = Teek::UI::Node.new(type: :text_box, name: :query)
168
+ handle = Teek::UI::Handle.new(node)
169
+
170
+ handle.on_key(:enter) { }
171
+
172
+ assert_equal ['<Return>'], node.events.map(&:event)
173
+ end
174
+
175
+ def test_on_key_modifier_string_queues_the_resolved_pattern
176
+ node = Teek::UI::Node.new(type: :text_box, name: :query)
177
+ handle = Teek::UI::Handle.new(node)
178
+
179
+ handle.on_key('Ctrl-s') { }
180
+
181
+ assert_equal ['<Control-s>'], node.events.map(&:event)
182
+ end
183
+
184
+ def test_on_drag_queues_with_x_y_subs
185
+ node = Teek::UI::Node.new(type: :panel, name: :area)
186
+ handle = Teek::UI::Handle.new(node)
187
+
188
+ handle.on_drag { |_x, _y| }
189
+
190
+ binding = node.events.first
191
+ assert_equal '<B1-Motion>', binding.event
192
+ assert_equal %i[x y], binding.subs
193
+ end
194
+
195
+ def test_on_right_click_queues_the_platform_appropriate_event_patterns
196
+ node = Teek::UI::Node.new(type: :button, name: :go)
197
+ handle = Teek::UI::Handle.new(node)
198
+
199
+ handle.on_right_click { }
200
+
201
+ assert_equal Teek::UI::MouseEvents::RIGHT_CLICK_EVENTS, node.events.map(&:event)
202
+ end
203
+
204
+ def test_on_close_queues_the_block_on_a_window_node_before_realize
205
+ node = Teek::UI::Node.new(type: :window, name: :settings)
206
+ handle = Teek::UI::Handle.new(node)
207
+ closer = -> { }
208
+
209
+ result = handle.on_close(&closer)
210
+
211
+ assert_same handle, result, "on_close should return self for chaining"
212
+ assert_equal closer, node.opts[:on_close]
213
+ end
214
+
215
+ def test_on_close_wires_immediately_once_already_realized
216
+ app = FakeApp.new
217
+ node = Teek::UI::Node.new(type: :window, name: :settings)
218
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.settings')
219
+ handle = Teek::UI::Handle.new(node)
220
+
221
+ handle.on_close { }
222
+
223
+ assert_nil node.opts[:on_close], "should wire immediately, not queue, once realized"
224
+ assert_equal 1, app.on_closes.length
225
+ assert_equal '.settings', app.on_closes.first[:window]
226
+ end
227
+
228
+ def test_on_close_raises_on_a_non_window_node
229
+ node = Teek::UI::Node.new(type: :button, name: :go)
230
+ handle = Teek::UI::Handle.new(node)
231
+
232
+ error = assert_raises(ArgumentError) { handle.on_close { } }
233
+ assert_match(/window/i, error.message)
234
+ end
235
+
236
+ def test_on_right_click_with_a_menu_queues_root_coordinate_bindings_before_realize
237
+ node = Teek::UI::Node.new(type: :canvas, name: :board)
238
+ handle = Teek::UI::Handle.new(node)
239
+ menu_node = Teek::UI::Node.new(type: :context_menu, name: :ctx)
240
+ menu_handle = Teek::UI::Handle.new(menu_node)
241
+
242
+ result = handle.on_right_click(menu_handle)
243
+
244
+ assert_same handle, result, "on_right_click should return self for chaining"
245
+ assert_equal Teek::UI::MouseEvents::RIGHT_CLICK_EVENTS.length, node.events.length
246
+ assert_equal Teek::UI::MouseEvents::RIGHT_CLICK_EVENTS, node.events.map(&:event)
247
+ node.events.each { |binding| assert_equal %i[root_x root_y], binding.subs }
248
+ end
249
+
250
+ def test_on_right_click_with_a_menu_pops_up_at_the_events_root_coordinates
251
+ app = FakeApp.new
252
+ node = Teek::UI::Node.new(type: :canvas, name: :board)
253
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.board')
254
+ handle = Teek::UI::Handle.new(node)
255
+ menu_node = Teek::UI::Node.new(type: :context_menu, name: :ctx)
256
+ menu_node.realized = Teek::UI::RealizedNode.new(app: app, path: '.ctx')
257
+ menu_handle = Teek::UI::Handle.new(menu_node)
258
+
259
+ handle.on_right_click(menu_handle)
260
+ app.binds.first[:block].call(50, 60)
261
+
262
+ assert_equal 1, app.popups.length
263
+ assert_equal({ menu: '.ctx', x: 50, y: 60, entry: nil }, app.popups.first)
264
+ end
265
+
266
+ def test_on_right_click_with_a_menu_handle_of_the_wrong_type_raises
267
+ node = Teek::UI::Node.new(type: :canvas, name: :board)
268
+ handle = Teek::UI::Handle.new(node)
269
+ not_a_menu = Teek::UI::Handle.new(Teek::UI::Node.new(type: :button, name: :go))
270
+
271
+ error = assert_raises(ArgumentError) { handle.on_right_click(not_a_menu) }
272
+ assert_match(/menu/i, error.message)
273
+ end
274
+
275
+ def test_on_right_click_with_neither_a_menu_nor_a_block_raises
276
+ node = Teek::UI::Node.new(type: :canvas, name: :board)
277
+ handle = Teek::UI::Handle.new(node)
278
+
279
+ assert_raises(ArgumentError) { handle.on_right_click }
280
+ end
281
+
282
+ def test_on_right_click_with_both_a_menu_and_a_block_raises
283
+ node = Teek::UI::Node.new(type: :canvas, name: :board)
284
+ handle = Teek::UI::Handle.new(node)
285
+ menu_handle = Teek::UI::Handle.new(Teek::UI::Node.new(type: :menu, name: :m))
286
+
287
+ error = assert_raises(ArgumentError) { handle.on_right_click(menu_handle) { } }
288
+ assert_match(/either/i, error.message)
289
+ end
290
+
291
+ def test_modal_raises_before_realize
292
+ node = Teek::UI::Node.new(type: :window, name: :settings)
293
+ handle = Teek::UI::Handle.new(node)
294
+
295
+ assert_raises(Teek::UI::NotRealizedError) { handle.modal }
296
+ end
297
+
298
+ def test_grab_release_raises_before_realize
299
+ node = Teek::UI::Node.new(type: :window, name: :settings)
300
+ handle = Teek::UI::Handle.new(node)
301
+
302
+ assert_raises(Teek::UI::NotRealizedError) { handle.grab_release }
303
+ end
304
+
305
+ def test_modal_raises_on_a_non_window_node
306
+ app = FakeApp.new
307
+ node = Teek::UI::Node.new(type: :button, name: :go)
308
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.go')
309
+ handle = Teek::UI::Handle.new(node)
310
+
311
+ error = assert_raises(ArgumentError) { handle.modal }
312
+ assert_match(/window/i, error.message)
313
+ end
314
+
315
+ def test_grab_release_raises_on_a_non_window_node
316
+ app = FakeApp.new
317
+ node = Teek::UI::Node.new(type: :button, name: :go)
318
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.go')
319
+ handle = Teek::UI::Handle.new(node)
320
+
321
+ error = assert_raises(ArgumentError) { handle.grab_release }
322
+ assert_match(/window/i, error.message)
323
+ end
324
+
325
+ def test_modal_delegates_to_the_realized_apps_window_once_realized
326
+ app = FakeApp.new
327
+ node = Teek::UI::Node.new(type: :window, name: :settings)
328
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.settings')
329
+ handle = Teek::UI::Handle.new(node)
330
+ ran = false
331
+
332
+ handle.modal(global: true) { ran = true }
333
+
334
+ assert_equal 1, app.windows.length
335
+ assert_equal '.settings', app.windows.first.path
336
+ assert_equal [{ global: true }], app.windows.first.modal_calls
337
+ assert ran, "the setup block should run"
338
+ end
339
+
340
+ def test_grab_release_delegates_to_the_realized_apps_window
341
+ app = FakeApp.new
342
+ node = Teek::UI::Node.new(type: :window, name: :settings)
343
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.settings')
344
+ handle = Teek::UI::Handle.new(node)
345
+
346
+ handle.grab_release
347
+
348
+ assert_equal 1, app.windows.length
349
+ assert_equal '.settings', app.windows.first.path
350
+ assert_equal [true], app.windows.first.grab_releases
351
+ end
352
+
353
+ def test_show_raises_before_realize
354
+ node = Teek::UI::Node.new(type: :window, name: :settings)
355
+ handle = Teek::UI::Handle.new(node)
356
+
357
+ assert_raises(Teek::UI::NotRealizedError) { handle.show }
358
+ end
359
+
360
+ def test_hide_raises_before_realize
361
+ node = Teek::UI::Node.new(type: :window, name: :settings)
362
+ handle = Teek::UI::Handle.new(node)
363
+
364
+ assert_raises(Teek::UI::NotRealizedError) { handle.hide }
365
+ end
366
+
367
+ def test_show_raises_on_a_non_window_node
368
+ app = FakeApp.new
369
+ node = Teek::UI::Node.new(type: :button, name: :go)
370
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.go')
371
+ handle = Teek::UI::Handle.new(node)
372
+
373
+ error = assert_raises(ArgumentError) { handle.show }
374
+ assert_match(/window/i, error.message)
375
+ end
376
+
377
+ def test_hide_raises_on_a_non_window_node
378
+ app = FakeApp.new
379
+ node = Teek::UI::Node.new(type: :button, name: :go)
380
+ node.realized = Teek::UI::RealizedNode.new(app: app, path: '.go')
381
+ handle = Teek::UI::Handle.new(node)
382
+
383
+ error = assert_raises(ArgumentError) { handle.hide }
384
+ assert_match(/window/i, error.message)
385
+ end
386
+ end