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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +41 -0
- data/README.md +452 -0
- data/lib/teek/ui/canvas_item.rb +270 -0
- data/lib/teek/ui/component_handle.rb +32 -0
- data/lib/teek/ui/document.rb +185 -0
- data/lib/teek/ui/errors.rb +41 -0
- data/lib/teek/ui/event_binding.rb +22 -0
- data/lib/teek/ui/event_bus.rb +53 -0
- data/lib/teek/ui/grid_validator.rb +89 -0
- data/lib/teek/ui/handle.rb +585 -0
- data/lib/teek/ui/image.rb +58 -0
- data/lib/teek/ui/keysyms.rb +72 -0
- data/lib/teek/ui/menu_builder.rb +130 -0
- data/lib/teek/ui/menu_entry_addressing.rb +62 -0
- data/lib/teek/ui/modal_stack.rb +99 -0
- data/lib/teek/ui/mouse_events.rb +31 -0
- data/lib/teek/ui/node.rb +141 -0
- data/lib/teek/ui/option_dump_parsing.rb +31 -0
- data/lib/teek/ui/overlay_anchors.rb +29 -0
- data/lib/teek/ui/overlay_validator.rb +33 -0
- data/lib/teek/ui/pane_validator.rb +30 -0
- data/lib/teek/ui/realized_node.rb +25 -0
- data/lib/teek/ui/realizer.rb +577 -0
- data/lib/teek/ui/scope.rb +40 -0
- data/lib/teek/ui/screens.rb +141 -0
- data/lib/teek/ui/session.rb +417 -0
- data/lib/teek/ui/tab_validator.rb +30 -0
- data/lib/teek/ui/text_content.rb +321 -0
- data/lib/teek/ui/tree_inspector.rb +97 -0
- data/lib/teek/ui/validator.rb +131 -0
- data/lib/teek/ui/var.rb +98 -0
- data/lib/teek/ui/version.rb +7 -0
- data/lib/teek/ui/widget_addressing.rb +47 -0
- data/lib/teek/ui/widget_dsl.rb +512 -0
- data/lib/teek/ui/widget_type.rb +242 -0
- data/lib/teek/ui/widget_types/button.rb +7 -0
- data/lib/teek/ui/widget_types/canvas.rb +15 -0
- data/lib/teek/ui/widget_types/checkbox.rb +7 -0
- data/lib/teek/ui/widget_types/column.rb +14 -0
- data/lib/teek/ui/widget_types/context_menu.rb +15 -0
- data/lib/teek/ui/widget_types/divider.rb +10 -0
- data/lib/teek/ui/widget_types/dropdown.rb +7 -0
- data/lib/teek/ui/widget_types/grid.rb +12 -0
- data/lib/teek/ui/widget_types/group.rb +7 -0
- data/lib/teek/ui/widget_types/label.rb +7 -0
- data/lib/teek/ui/widget_types/list.rb +7 -0
- data/lib/teek/ui/widget_types/menu_bar.rb +26 -0
- data/lib/teek/ui/widget_types/menu_checkbox.rb +14 -0
- data/lib/teek/ui/widget_types/menu_item.rb +20 -0
- data/lib/teek/ui/widget_types/menu_radio.rb +14 -0
- data/lib/teek/ui/widget_types/number_box.rb +7 -0
- data/lib/teek/ui/widget_types/pane.rb +37 -0
- data/lib/teek/ui/widget_types/panel.rb +7 -0
- data/lib/teek/ui/widget_types/progress.rb +7 -0
- data/lib/teek/ui/widget_types/radio.rb +7 -0
- data/lib/teek/ui/widget_types/row.rb +14 -0
- data/lib/teek/ui/widget_types/scrollable.rb +17 -0
- data/lib/teek/ui/widget_types/slider.rb +7 -0
- data/lib/teek/ui/widget_types/spacer.rb +14 -0
- data/lib/teek/ui/widget_types/split.rb +17 -0
- data/lib/teek/ui/widget_types/tab.rb +35 -0
- data/lib/teek/ui/widget_types/table.rb +9 -0
- data/lib/teek/ui/widget_types/tabs.rb +10 -0
- data/lib/teek/ui/widget_types/text_area.rb +7 -0
- data/lib/teek/ui/widget_types/text_box.rb +7 -0
- data/lib/teek/ui/widget_types/tree.rb +9 -0
- data/lib/teek/ui/widget_types/window.rb +50 -0
- data/lib/teek/ui/widget_types.rb +121 -0
- data/lib/teek/ui/widget_validators.rb +62 -0
- data/lib/teek/ui.rb +54 -0
- data/teek-ui.gemspec +24 -0
- data/test/support/fake_app.rb +57 -0
- data/test/test_busy.rb +82 -0
- data/test/test_canvas_items.rb +551 -0
- data/test/test_clipboard.rb +87 -0
- data/test/test_close_handling.rb +118 -0
- data/test/test_component.rb +151 -0
- data/test/test_component_realtk.rb +120 -0
- data/test/test_debug_info.rb +158 -0
- data/test/test_document.rb +317 -0
- data/test/test_event_bus.rb +163 -0
- data/test/test_events.rb +164 -0
- data/test/test_fake_app_contract.rb +62 -0
- data/test/test_grid.rb +97 -0
- data/test/test_handle.rb +386 -0
- data/test/test_handle_destroy_realtk.rb +344 -0
- data/test/test_helper.rb +20 -0
- data/test/test_image.rb +24 -0
- data/test/test_image_realtk.rb +146 -0
- data/test/test_incremental_realize.rb +203 -0
- data/test/test_keysyms.rb +76 -0
- data/test/test_layout.rb +185 -0
- data/test/test_lazy_realize.rb +128 -0
- data/test/test_managed_window.rb +304 -0
- data/test/test_menu_dsl.rb +230 -0
- data/test/test_menu_entry_addressing.rb +73 -0
- data/test/test_menu_realize.rb +296 -0
- data/test/test_modal_handle.rb +147 -0
- data/test/test_modal_stack.rb +219 -0
- data/test/test_modal_stack_realtk.rb +131 -0
- data/test/test_native_scrollable.rb +250 -0
- data/test/test_node.rb +193 -0
- data/test/test_overlay.rb +139 -0
- data/test/test_raw.rb +58 -0
- data/test/test_reactive_vars.rb +118 -0
- data/test/test_realizer.rb +264 -0
- data/test/test_scope.rb +38 -0
- data/test/test_screens.rb +260 -0
- data/test/test_screens_realtk.rb +314 -0
- data/test/test_scrollable.rb +185 -0
- data/test/test_scrollable_wheel.rb +172 -0
- data/test/test_scrollbar_auto_hide.rb +127 -0
- data/test/test_split.rb +145 -0
- data/test/test_tabs.rb +201 -0
- data/test/test_text_content.rb +664 -0
- data/test/test_toast.rb +126 -0
- data/test/test_tree_inspector.rb +183 -0
- data/test/test_ui.rb +428 -0
- data/test/test_validator.rb +209 -0
- data/test/test_var.rb +32 -0
- data/test/test_widget_dsl.rb +621 -0
- data/test/test_widget_introspection.rb +126 -0
- data/test/test_widget_types.rb +429 -0
- data/test/test_widget_types_realtk.rb +82 -0
- data/test/test_widget_validators.rb +82 -0
- metadata +207 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require_relative '../../test/tk_test_helper'
|
|
5
|
+
|
|
6
|
+
# Real-Tk half of ModalStack's coverage - the callback lifecycle (on_enter/
|
|
7
|
+
# on_exit/on_focus_change) is covered headlessly in test_modal_stack.rb;
|
|
8
|
+
# these exercise the actual show/hide/grab side effects against real
|
|
9
|
+
# ui.dialog windows.
|
|
10
|
+
class TestModalStackRealTk < Minitest::Test
|
|
11
|
+
include TeekTestHelper
|
|
12
|
+
|
|
13
|
+
def test_push_shows_and_grabs_the_dialog
|
|
14
|
+
assert_tk_app("pushing a modal dialog should map and grab it") do
|
|
15
|
+
require 'teek/ui'
|
|
16
|
+
|
|
17
|
+
session = Teek::UI.app(title: 'ModalStack Test') { |ui| ui.dialog(:settings) }
|
|
18
|
+
session.run_async
|
|
19
|
+
session.app.update
|
|
20
|
+
|
|
21
|
+
session.modal = Teek::UI::ModalStack.new(on_enter: ->(_) { }, on_exit: -> { })
|
|
22
|
+
session.modal.push(:settings, session[:settings])
|
|
23
|
+
session.app.update
|
|
24
|
+
|
|
25
|
+
path = session[:settings].path
|
|
26
|
+
assert session.app.winfo.ismapped?(path)
|
|
27
|
+
assert_equal path, session.app.tcl_eval("grab current #{path}")
|
|
28
|
+
|
|
29
|
+
session.app.destroy
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_pushing_a_second_dialog_withdraws_the_first_and_grabs_the_second
|
|
34
|
+
assert_tk_app("pushing a second dialog should withdraw/release the first and grab the second") do
|
|
35
|
+
require 'teek/ui'
|
|
36
|
+
|
|
37
|
+
session = Teek::UI.app(title: 'ModalStack Test') do |ui|
|
|
38
|
+
ui.dialog(:settings)
|
|
39
|
+
ui.dialog(:replay)
|
|
40
|
+
end
|
|
41
|
+
session.run_async
|
|
42
|
+
session.app.update
|
|
43
|
+
|
|
44
|
+
session.modal = Teek::UI::ModalStack.new(on_enter: ->(_) { }, on_exit: -> { })
|
|
45
|
+
session.modal.push(:settings, session[:settings])
|
|
46
|
+
session.app.update
|
|
47
|
+
session.modal.push(:replay, session[:replay])
|
|
48
|
+
session.app.update
|
|
49
|
+
|
|
50
|
+
settings_path = session[:settings].path
|
|
51
|
+
replay_path = session[:replay].path
|
|
52
|
+
|
|
53
|
+
refute session.app.winfo.ismapped?(settings_path)
|
|
54
|
+
assert session.app.winfo.ismapped?(replay_path)
|
|
55
|
+
assert_equal replay_path, session.app.tcl_eval("grab current #{replay_path}")
|
|
56
|
+
assert_equal :replay, session.modal.current
|
|
57
|
+
assert_equal 2, session.modal.size
|
|
58
|
+
|
|
59
|
+
session.app.destroy
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_pop_re_shows_and_re_grabs_the_dialog_underneath
|
|
64
|
+
assert_tk_app("popping the top dialog should re-show and re-grab the one underneath") do
|
|
65
|
+
require 'teek/ui'
|
|
66
|
+
|
|
67
|
+
session = Teek::UI.app(title: 'ModalStack Test') do |ui|
|
|
68
|
+
ui.dialog(:settings)
|
|
69
|
+
ui.dialog(:replay)
|
|
70
|
+
end
|
|
71
|
+
session.run_async
|
|
72
|
+
session.app.update
|
|
73
|
+
|
|
74
|
+
session.modal = Teek::UI::ModalStack.new(on_enter: ->(_) { }, on_exit: -> { })
|
|
75
|
+
session.modal.push(:settings, session[:settings])
|
|
76
|
+
session.modal.push(:replay, session[:replay])
|
|
77
|
+
session.app.update
|
|
78
|
+
|
|
79
|
+
session.modal.pop
|
|
80
|
+
session.app.update
|
|
81
|
+
|
|
82
|
+
settings_path = session[:settings].path
|
|
83
|
+
replay_path = session[:replay].path
|
|
84
|
+
|
|
85
|
+
assert session.app.winfo.ismapped?(settings_path)
|
|
86
|
+
refute session.app.winfo.ismapped?(replay_path)
|
|
87
|
+
assert_equal settings_path, session.app.tcl_eval("grab current #{settings_path}")
|
|
88
|
+
assert_equal :settings, session.modal.current
|
|
89
|
+
|
|
90
|
+
session.app.destroy
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_the_on_enter_and_on_exit_callbacks_bracket_a_whole_modal_session
|
|
95
|
+
assert_tk_app("on_enter should fire on the first push, on_exit on the last pop") do
|
|
96
|
+
require 'teek/ui'
|
|
97
|
+
|
|
98
|
+
session = Teek::UI.app(title: 'ModalStack Test') do |ui|
|
|
99
|
+
ui.dialog(:settings)
|
|
100
|
+
ui.dialog(:replay)
|
|
101
|
+
end
|
|
102
|
+
session.run_async
|
|
103
|
+
session.app.update
|
|
104
|
+
|
|
105
|
+
events = []
|
|
106
|
+
session.modal = Teek::UI::ModalStack.new(
|
|
107
|
+
on_enter: ->(name) { events << [:enter, name] },
|
|
108
|
+
on_exit: -> { events << [:exit] },
|
|
109
|
+
on_focus_change: ->(name) { events << [:focus, name] },
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
session.modal.push(:settings, session[:settings])
|
|
113
|
+
session.modal.push(:replay, session[:replay])
|
|
114
|
+
session.app.update
|
|
115
|
+
session.modal.pop
|
|
116
|
+
session.modal.pop
|
|
117
|
+
session.app.update
|
|
118
|
+
|
|
119
|
+
assert_equal [
|
|
120
|
+
[:enter, :settings],
|
|
121
|
+
[:focus, :settings],
|
|
122
|
+
[:focus, :replay],
|
|
123
|
+
[:focus, :settings],
|
|
124
|
+
[:exit],
|
|
125
|
+
], events
|
|
126
|
+
refute session.modal.active?
|
|
127
|
+
|
|
128
|
+
session.app.destroy
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require_relative '../../test/tk_test_helper'
|
|
5
|
+
|
|
6
|
+
# A bare list/text_area/table/tree/canvas auto-attaches a scrollbar
|
|
7
|
+
# wherever it's declared - no ui.scrollable wrapper needed. See
|
|
8
|
+
# Realizer#create_native_scrollable/#auto_scrollable?/#resolve_scroll.
|
|
9
|
+
#
|
|
10
|
+
# The widget is wrapped in an invisible frame (widget + scrollbar,
|
|
11
|
+
# gridded together) that takes over the node's own allocated path; the
|
|
12
|
+
# real widget lives one level deeper, at <path>.widget, which is what
|
|
13
|
+
# session[:name].path/.configure/events all still act on directly - see
|
|
14
|
+
# RealizedNode's arrange_path split. Validation that scroll: raises on an
|
|
15
|
+
# unsupported widget type is headless, in test_widget_dsl.rb.
|
|
16
|
+
class TestNativeScrollable < Minitest::Test
|
|
17
|
+
include TeekTestHelper
|
|
18
|
+
|
|
19
|
+
def test_a_bare_list_auto_attaches_a_vertical_scrollbar_by_default
|
|
20
|
+
assert_tk_app("ui.list should get a scrollbar with no ui.scrollable wrapper") do
|
|
21
|
+
require 'teek/ui'
|
|
22
|
+
|
|
23
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') { |ui| ui.list(:items) }
|
|
24
|
+
session.run_async
|
|
25
|
+
session.app.update
|
|
26
|
+
|
|
27
|
+
list_path = session[:items].path
|
|
28
|
+
wrapper_path = list_path.sub(/\.widget\z/, '')
|
|
29
|
+
vsb_path = "#{wrapper_path}.vsb"
|
|
30
|
+
|
|
31
|
+
refute_equal wrapper_path, list_path, "the handle path should be the real widget, not the wrapper"
|
|
32
|
+
assert_equal '1', session.app.tcl_eval("winfo exists #{vsb_path}")
|
|
33
|
+
assert_equal '0', session.app.tcl_eval("winfo exists #{wrapper_path}.hsb"), "x: defaults to false"
|
|
34
|
+
assert_equal "#{list_path} yview", session.app.command(vsb_path, :cget, '-command')
|
|
35
|
+
|
|
36
|
+
session.app.destroy
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_configure_and_events_act_on_the_real_widget_not_the_wrapper
|
|
41
|
+
assert_tk_app("Handle#configure/#on_click should still act on the real widget once auto-wrapped") do
|
|
42
|
+
require 'teek/ui'
|
|
43
|
+
|
|
44
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') { |ui| ui.list(:items) }
|
|
45
|
+
session.run_async
|
|
46
|
+
session.app.update
|
|
47
|
+
|
|
48
|
+
clicked = false
|
|
49
|
+
session[:items].on_click { clicked = true }
|
|
50
|
+
session[:items].configure(exportselection: false)
|
|
51
|
+
session.app.update
|
|
52
|
+
|
|
53
|
+
assert_equal '0', session.app.command(session[:items].path, :cget, '-exportselection')
|
|
54
|
+
|
|
55
|
+
session.app.tcl_eval("event generate #{session[:items].path} <Button-1>")
|
|
56
|
+
session.app.update
|
|
57
|
+
|
|
58
|
+
assert clicked
|
|
59
|
+
|
|
60
|
+
session.app.destroy
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_scroll_false_opts_out_leaving_a_plain_unwrapped_widget
|
|
65
|
+
assert_tk_app("scroll: false should leave the widget exactly as it was before auto-scroll existed") do
|
|
66
|
+
require 'teek/ui'
|
|
67
|
+
|
|
68
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') do |ui|
|
|
69
|
+
ui.column(:panel) { |c| c.list(:items, scroll: false) }
|
|
70
|
+
end
|
|
71
|
+
session.run_async
|
|
72
|
+
session.app.update
|
|
73
|
+
|
|
74
|
+
assert_equal "#{session[:panel].path}.items", session[:items].path
|
|
75
|
+
assert_equal '0', session.app.tcl_eval("winfo exists #{session[:items].path}.vsb")
|
|
76
|
+
|
|
77
|
+
session.app.destroy
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_text_area_and_table_and_tree_also_auto_attach
|
|
82
|
+
assert_tk_app("text_area/table/tree should get the same auto-attach treatment as list") do
|
|
83
|
+
require 'teek/ui'
|
|
84
|
+
|
|
85
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') do |ui|
|
|
86
|
+
ui.text_area(:notes)
|
|
87
|
+
ui.table(:grid_view)
|
|
88
|
+
ui.tree(:hierarchy)
|
|
89
|
+
end
|
|
90
|
+
session.run_async
|
|
91
|
+
session.app.update
|
|
92
|
+
|
|
93
|
+
%i[notes grid_view hierarchy].each do |name|
|
|
94
|
+
wrapper = session[name].path.sub(/\.widget\z/, '')
|
|
95
|
+
assert_equal '1', session.app.tcl_eval("winfo exists #{wrapper}.vsb"), "#{name} should have auto-attached a scrollbar"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
session.app.destroy
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_canvas_defaults_to_no_auto_attach
|
|
103
|
+
assert_tk_app("canvas should default to scroll: false, unlike the other native types") do
|
|
104
|
+
require 'teek/ui'
|
|
105
|
+
|
|
106
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') { |ui| ui.canvas(:board) }
|
|
107
|
+
session.run_async
|
|
108
|
+
session.app.update
|
|
109
|
+
|
|
110
|
+
assert_equal '.board', session[:board].path
|
|
111
|
+
assert_equal '0', session.app.tcl_eval("winfo exists .board.vsb")
|
|
112
|
+
|
|
113
|
+
session.app.destroy
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_canvas_scroll_true_forces_it_on
|
|
118
|
+
assert_tk_app("scroll: true should override canvas's own false default") do
|
|
119
|
+
require 'teek/ui'
|
|
120
|
+
|
|
121
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') { |ui| ui.canvas(:board, scroll: true) }
|
|
122
|
+
session.run_async
|
|
123
|
+
session.app.update
|
|
124
|
+
|
|
125
|
+
wrapper = session[:board].path.sub(/\.widget\z/, '')
|
|
126
|
+
assert_equal '1', session.app.tcl_eval("winfo exists #{wrapper}.vsb")
|
|
127
|
+
|
|
128
|
+
session.app.destroy
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_canvas_children_still_create_inside_the_real_canvas_once_wrapped
|
|
133
|
+
assert_tk_app("a canvas's own DSL children (via ui.raw) should target the real canvas, not the wrapper") do
|
|
134
|
+
require 'teek/ui'
|
|
135
|
+
|
|
136
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') do |ui|
|
|
137
|
+
# ui.canvas's own block yields the builder itself (not a scoped
|
|
138
|
+
# Handle - see WidgetDSL's own doc comment), so the real canvas
|
|
139
|
+
# handle is looked up by name via the outer `ui` instead.
|
|
140
|
+
ui.canvas(:board, scroll: true) { |c| c.raw { |app| app.command(ui[:board].path, :create, :text, 10, 10, text: 'hi') } }
|
|
141
|
+
end
|
|
142
|
+
session.run_async
|
|
143
|
+
session.app.update
|
|
144
|
+
|
|
145
|
+
item_ids = session.app.split_list(session.app.command(session[:board].path, :find, :all))
|
|
146
|
+
assert_equal 1, item_ids.length
|
|
147
|
+
|
|
148
|
+
session.app.destroy
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_x_true_wires_a_horizontal_scrollbar_on_a_native_widget_too
|
|
153
|
+
assert_tk_app("x: true on a bare native widget should wire a horizontal scrollbar") do
|
|
154
|
+
require 'teek/ui'
|
|
155
|
+
|
|
156
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') { |ui| ui.tree(:hierarchy, x: true) }
|
|
157
|
+
session.run_async
|
|
158
|
+
session.app.update
|
|
159
|
+
|
|
160
|
+
wrapper = session[:hierarchy].path.sub(/\.widget\z/, '')
|
|
161
|
+
assert_equal '1', session.app.tcl_eval("winfo exists #{wrapper}.hsb")
|
|
162
|
+
assert_equal "#{session[:hierarchy].path} xview", session.app.command("#{wrapper}.hsb", :cget, '-command')
|
|
163
|
+
|
|
164
|
+
session.app.destroy
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_wrapped_widget_composes_correctly_inside_a_flow_container
|
|
169
|
+
assert_tk_app("the layout should pack/place the WRAPPER, not the raw widget, inside a flow container") do
|
|
170
|
+
require 'teek/ui'
|
|
171
|
+
|
|
172
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') do |ui|
|
|
173
|
+
ui.column do |c|
|
|
174
|
+
c.label(text: 'Header')
|
|
175
|
+
c.list(:items, grow: true)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
session.run_async
|
|
179
|
+
session.app.update
|
|
180
|
+
|
|
181
|
+
wrapper = session[:items].path.sub(/\.widget\z/, '')
|
|
182
|
+
assert_equal 'pack', session.app.tcl_eval("winfo manager #{wrapper}")
|
|
183
|
+
assert session.app.winfo.ismapped?(wrapper)
|
|
184
|
+
|
|
185
|
+
session.app.destroy
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def test_app_level_scroll_false_overrides_the_global_default
|
|
190
|
+
assert_tk_app("Teek::UI.app(scroll: false) should suppress auto-attach app-wide") do
|
|
191
|
+
require 'teek/ui'
|
|
192
|
+
|
|
193
|
+
session = Teek::UI.app(title: 'Native Scrollable Test', scroll: false) { |ui| ui.list(:items) }
|
|
194
|
+
session.run_async
|
|
195
|
+
session.app.update
|
|
196
|
+
|
|
197
|
+
assert_equal '.items', session[:items].path
|
|
198
|
+
assert_equal '0', session.app.tcl_eval("winfo exists .items.vsb")
|
|
199
|
+
|
|
200
|
+
session.app.destroy
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_per_widget_scroll_true_overrides_an_app_level_false
|
|
205
|
+
assert_tk_app("a widget's own scroll: true should win over the app-level default") do
|
|
206
|
+
require 'teek/ui'
|
|
207
|
+
|
|
208
|
+
session = Teek::UI.app(title: 'Native Scrollable Test', scroll: false) { |ui| ui.list(:items, scroll: true) }
|
|
209
|
+
session.run_async
|
|
210
|
+
session.app.update
|
|
211
|
+
|
|
212
|
+
wrapper = session[:items].path.sub(/\.widget\z/, '')
|
|
213
|
+
assert_equal '1', session.app.tcl_eval("winfo exists #{wrapper}.vsb")
|
|
214
|
+
|
|
215
|
+
session.app.destroy
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_global_default_false_suppresses_auto_attach_unless_overridden
|
|
220
|
+
assert_tk_app("Teek::UI.auto_scroll = false should suppress the default everywhere it isn't overridden") do
|
|
221
|
+
require 'teek/ui'
|
|
222
|
+
|
|
223
|
+
# The test runner keeps one Tk worker process alive across every test
|
|
224
|
+
# in this file, reusing its already-loaded Teek::UI module - so a
|
|
225
|
+
# global class-level default mutated here would otherwise leak into
|
|
226
|
+
# every test that runs afterward in the same process. Always restore
|
|
227
|
+
# it, success or failure.
|
|
228
|
+
original = Teek::UI.auto_scroll
|
|
229
|
+
begin
|
|
230
|
+
Teek::UI.auto_scroll = false
|
|
231
|
+
session = Teek::UI.app(title: 'Native Scrollable Test') do |ui|
|
|
232
|
+
ui.list(:default_items)
|
|
233
|
+
ui.list(:forced_items, scroll: true)
|
|
234
|
+
end
|
|
235
|
+
session.run_async
|
|
236
|
+
session.app.update
|
|
237
|
+
|
|
238
|
+
assert_equal '.default_items', session[:default_items].path
|
|
239
|
+
assert_equal '0', session.app.tcl_eval("winfo exists .default_items.vsb")
|
|
240
|
+
|
|
241
|
+
forced_wrapper = session[:forced_items].path.sub(/\.widget\z/, '')
|
|
242
|
+
assert_equal '1', session.app.tcl_eval("winfo exists #{forced_wrapper}.vsb")
|
|
243
|
+
|
|
244
|
+
session.app.destroy
|
|
245
|
+
ensure
|
|
246
|
+
Teek::UI.auto_scroll = original
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
data/test/test_node.rb
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_helper'
|
|
4
|
+
require 'teek/ui/node'
|
|
5
|
+
require 'teek/ui/scope'
|
|
6
|
+
|
|
7
|
+
class TestNode < Minitest::Test
|
|
8
|
+
def test_defaults
|
|
9
|
+
node = Teek::UI::Node.new(type: :button)
|
|
10
|
+
|
|
11
|
+
assert_equal :button, node.type
|
|
12
|
+
assert_nil node.name
|
|
13
|
+
assert_nil node.key
|
|
14
|
+
assert_equal({}, node.opts)
|
|
15
|
+
assert_equal [], node.children
|
|
16
|
+
assert_equal [], node.events
|
|
17
|
+
assert_nil node.layout
|
|
18
|
+
assert_nil node.realized
|
|
19
|
+
assert_same Teek::UI::Scope::TOP_LEVEL, node.scope
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_scope_is_settable_and_readable
|
|
23
|
+
scope = Teek::UI::Scope.new(:sidebar)
|
|
24
|
+
node = Teek::UI::Node.new(type: :button, scope: scope)
|
|
25
|
+
|
|
26
|
+
assert_same scope, node.scope
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_key_defaults_to_the_name_when_no_key_given
|
|
30
|
+
node = Teek::UI::Node.new(type: :button, name: :save)
|
|
31
|
+
|
|
32
|
+
assert_equal 'save', node.key
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_explicit_key_overrides_the_name_derived_default
|
|
36
|
+
node = Teek::UI::Node.new(type: :button, name: :save, key: 'custom')
|
|
37
|
+
|
|
38
|
+
assert_equal 'custom', node.key
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_opts_are_retained_verbatim
|
|
42
|
+
node = Teek::UI::Node.new(type: :label, opts: { text: 'Hi', width: 10 })
|
|
43
|
+
|
|
44
|
+
assert_equal({ text: 'Hi', width: 10 }, node.opts)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_add_child_appends_and_returns_the_child
|
|
48
|
+
parent = Teek::UI::Node.new(type: :column)
|
|
49
|
+
child = Teek::UI::Node.new(type: :button)
|
|
50
|
+
|
|
51
|
+
result = parent.add_child(child)
|
|
52
|
+
|
|
53
|
+
assert_equal [child], parent.children
|
|
54
|
+
assert_same child, result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_layout_and_realized_are_settable_after_construction
|
|
58
|
+
node = Teek::UI::Node.new(type: :button)
|
|
59
|
+
|
|
60
|
+
node.layout = { grow: true }
|
|
61
|
+
node.realized = :fake_handle
|
|
62
|
+
|
|
63
|
+
assert_equal({ grow: true }, node.layout)
|
|
64
|
+
assert_equal :fake_handle, node.realized
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_each_visits_self_then_children_depth_first_preorder
|
|
68
|
+
root = Teek::UI::Node.new(type: :column)
|
|
69
|
+
a = root.add_child(Teek::UI::Node.new(type: :button, name: :a))
|
|
70
|
+
b = root.add_child(Teek::UI::Node.new(type: :column, name: :b))
|
|
71
|
+
c = b.add_child(Teek::UI::Node.new(type: :button, name: :c))
|
|
72
|
+
|
|
73
|
+
visited = []
|
|
74
|
+
root.each { |n| visited << n }
|
|
75
|
+
|
|
76
|
+
assert_equal [root, a, b, c], visited
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_each_without_a_block_returns_an_enumerator
|
|
80
|
+
root = Teek::UI::Node.new(type: :column)
|
|
81
|
+
root.add_child(Teek::UI::Node.new(type: :button, name: :a))
|
|
82
|
+
|
|
83
|
+
assert_equal [:column, :button], root.each.map(&:type)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_add_child_sets_the_childs_parent
|
|
87
|
+
parent = Teek::UI::Node.new(type: :column)
|
|
88
|
+
child = Teek::UI::Node.new(type: :button)
|
|
89
|
+
|
|
90
|
+
parent.add_child(child)
|
|
91
|
+
|
|
92
|
+
assert_same parent, child.parent
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_document_defaults_to_nil
|
|
96
|
+
node = Teek::UI::Node.new(type: :button)
|
|
97
|
+
|
|
98
|
+
assert_nil node.document
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_document_is_settable_at_construction
|
|
102
|
+
fake_document = Object.new
|
|
103
|
+
node = Teek::UI::Node.new(type: :button, document: fake_document)
|
|
104
|
+
|
|
105
|
+
assert_same fake_document, node.document
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_remove_child_removes_it_from_the_parents_children
|
|
109
|
+
parent = Teek::UI::Node.new(type: :column)
|
|
110
|
+
a = parent.add_child(Teek::UI::Node.new(type: :button, name: :a))
|
|
111
|
+
b = parent.add_child(Teek::UI::Node.new(type: :button, name: :b))
|
|
112
|
+
|
|
113
|
+
parent.remove_child(a)
|
|
114
|
+
|
|
115
|
+
assert_equal [b], parent.children
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_remove_child_clears_the_removed_nodes_own_parent
|
|
119
|
+
parent = Teek::UI::Node.new(type: :column)
|
|
120
|
+
child = parent.add_child(Teek::UI::Node.new(type: :button))
|
|
121
|
+
|
|
122
|
+
parent.remove_child(child)
|
|
123
|
+
|
|
124
|
+
assert_nil child.parent
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_remove_child_returns_the_removed_node
|
|
128
|
+
parent = Teek::UI::Node.new(type: :column)
|
|
129
|
+
child = parent.add_child(Teek::UI::Node.new(type: :button))
|
|
130
|
+
|
|
131
|
+
result = parent.remove_child(child)
|
|
132
|
+
|
|
133
|
+
assert_same child, result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_parent_is_nil_before_being_attached
|
|
137
|
+
node = Teek::UI::Node.new(type: :button)
|
|
138
|
+
|
|
139
|
+
assert_nil node.parent
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_parent_setter_is_not_publicly_callable
|
|
143
|
+
node = Teek::UI::Node.new(type: :button)
|
|
144
|
+
|
|
145
|
+
assert_raises(NoMethodError) { node.parent = Teek::UI::Node.new(type: :column) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_root_logical_path_is_a_bare_dot
|
|
149
|
+
root = Teek::UI::Node.new(type: :root)
|
|
150
|
+
|
|
151
|
+
assert_equal '.', root.logical_path
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_logical_path_for_a_top_level_named_node
|
|
155
|
+
root = Teek::UI::Node.new(type: :root)
|
|
156
|
+
child = root.add_child(Teek::UI::Node.new(type: :button, name: :save))
|
|
157
|
+
|
|
158
|
+
assert_equal '.save', child.logical_path
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_logical_path_nests_through_named_ancestors
|
|
162
|
+
root = Teek::UI::Node.new(type: :root)
|
|
163
|
+
column = root.add_child(Teek::UI::Node.new(type: :column, name: :toolbar))
|
|
164
|
+
button = column.add_child(Teek::UI::Node.new(type: :button, name: :save))
|
|
165
|
+
|
|
166
|
+
assert_equal '.toolbar.save', button.logical_path
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_logical_path_uses_the_auto_generated_key_when_unnamed
|
|
170
|
+
root = Teek::UI::Node.new(type: :root)
|
|
171
|
+
child = root.add_child(Teek::UI::Node.new(type: :button, key: '#anon1'))
|
|
172
|
+
|
|
173
|
+
assert_equal '.#anon1', child.logical_path
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_logical_path_of_an_unattached_node_treats_it_as_top_level
|
|
177
|
+
node = Teek::UI::Node.new(type: :button, name: :save)
|
|
178
|
+
|
|
179
|
+
assert_equal '.save', node.logical_path
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_display_name_is_the_bare_type_when_unnamed
|
|
183
|
+
node = Teek::UI::Node.new(type: :column)
|
|
184
|
+
|
|
185
|
+
assert_equal 'column', node.display_name
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def test_display_name_includes_the_name_when_given
|
|
189
|
+
node = Teek::UI::Node.new(type: :column, name: :ctrl)
|
|
190
|
+
|
|
191
|
+
assert_equal 'column(:ctrl)', node.display_name
|
|
192
|
+
end
|
|
193
|
+
end
|