scarpe 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.yardopts +11 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +112 -0
- data/README.md +31 -24
- data/Rakefile +13 -1
- data/docs/yard/catscradle.md +44 -0
- data/docs/yard/template/default/fulldoc/html/setup.rb +13 -0
- data/docs/yard/template/default/layout/html/setup.rb +9 -0
- data/examples/background_with_image.rb +16 -0
- data/examples/bloopsaphone/working/bronx_army_knife.rb +66 -0
- data/examples/bloopsaphone/working/morning_serenity.rb +21 -0
- data/examples/bloopsaphone/working/simpsons_theme_song_by_why.rb +6 -4
- data/examples/button_go_away.rb +1 -1
- data/examples/check.rb +18 -0
- data/examples/clear_and_append.rb +24 -0
- data/examples/download_and_show_image.rb +28 -0
- data/examples/edit_box.rb +3 -5
- data/examples/fonts.rb +2 -2
- data/examples/get_headers.rb +10 -0
- data/examples/highlander.rb +2 -0
- data/examples/link.rb +2 -2
- data/examples/local_fonts.rb +4 -0
- data/examples/local_images.rb +4 -0
- data/examples/motion_events.rb +20 -0
- data/examples/parse_xl_funnies.rb +58 -0
- data/examples/radio/radio.rb +16 -0
- data/examples/radio/radio_groups.rb +18 -0
- data/examples/radio/radio_same_slot.rb +6 -0
- data/examples/ruby_racer.rb +13 -15
- data/examples/selfitude.rb +18 -0
- data/examples/shapes/shapes_fill.rb +4 -3
- data/examples/shoes_school.rb +2 -4
- data/examples/show_hide.rb +6 -0
- data/examples/skip_ci/change_my_audio_source.rb +21 -0
- data/examples/skip_ci/guitar_fretboard.rb +137 -0
- data/examples/video.rb +10 -0
- data/exe/scarpe +42 -66
- data/fonts/Pacifico.ttf +0 -0
- data/lacci/Gemfile +22 -0
- data/lacci/Gemfile.lock +72 -0
- data/lacci/Rakefile +12 -0
- data/lacci/lacci.gemspec +37 -0
- data/lacci/lib/lacci/scarpe_cli.rb +70 -0
- data/lacci/lib/lacci/scarpe_core.rb +21 -0
- data/lacci/lib/lacci/version.rb +13 -0
- data/lacci/lib/shoes/app.rb +264 -0
- data/{lib/scarpe → lacci/lib/shoes}/background.rb +1 -1
- data/{lib/scarpe → lacci/lib/shoes}/border.rb +1 -1
- data/{lib/scarpe → lacci/lib/shoes}/colors.rb +1 -1
- data/lacci/lib/shoes/constants.rb +29 -0
- data/{lib/scarpe → lacci/lib/shoes}/display_service.rb +40 -45
- data/lacci/lib/shoes/download.rb +123 -0
- data/lacci/lib/shoes/log.rb +71 -0
- data/lacci/lib/shoes/spacing.rb +9 -0
- data/{lib/scarpe → lacci/lib/shoes}/widget.rb +63 -43
- data/{lib/scarpe → lacci/lib/shoes/widgets}/alert.rb +3 -3
- data/{lib/scarpe → lacci/lib/shoes/widgets}/arc.rb +7 -5
- data/{lib/scarpe → lacci/lib/shoes/widgets}/button.rb +3 -3
- data/lacci/lib/shoes/widgets/check.rb +28 -0
- data/lacci/lib/shoes/widgets/document_root.rb +20 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_box.rb +10 -5
- data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_line.rb +2 -2
- data/lacci/lib/shoes/widgets/flow.rb +22 -0
- data/lacci/lib/shoes/widgets/font.rb +14 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/image.rb +3 -7
- data/lacci/lib/shoes/widgets/line.rb +18 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/link.rb +2 -2
- data/{lib/scarpe → lacci/lib/shoes/widgets}/list_box.rb +2 -2
- data/{lib/scarpe → lacci/lib/shoes/widgets}/para.rb +4 -26
- data/lacci/lib/shoes/widgets/radio.rb +35 -0
- data/lacci/lib/shoes/widgets/shape.rb +37 -0
- data/lacci/lib/shoes/widgets/slot.rb +75 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/span.rb +2 -2
- data/lacci/lib/shoes/widgets/stack.rb +24 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/star.rb +6 -9
- data/lacci/lib/shoes/widgets/subscription_item.rb +60 -0
- data/lacci/lib/shoes/widgets/text_widget.rb +51 -0
- data/lacci/lib/shoes/widgets/video.rb +15 -0
- data/lacci/lib/shoes/widgets.rb +29 -0
- data/lacci/lib/shoes.rb +127 -0
- data/lacci/test/test_colors.rb +39 -0
- data/lacci/test/test_helper.rb +9 -0
- data/lacci/test/test_lacci.rb +9 -0
- data/lib/scarpe/cats_cradle.rb +249 -0
- data/lib/scarpe/evented_assertions.rb +88 -0
- data/lib/scarpe/version.rb +1 -1
- data/lib/scarpe/wv/alert.rb +3 -2
- data/lib/scarpe/wv/app.rb +30 -8
- data/lib/scarpe/wv/arc.rb +5 -6
- data/lib/scarpe/wv/background.rb +10 -1
- data/lib/scarpe/wv/border.rb +5 -3
- data/lib/scarpe/wv/button.rb +11 -9
- data/lib/scarpe/wv/check.rb +29 -0
- data/lib/scarpe/wv/control_interface.rb +14 -20
- data/lib/scarpe/wv/control_interface_test.rb +13 -28
- data/lib/scarpe/wv/document_root.rb +3 -45
- data/lib/scarpe/wv/edit_box.rb +5 -7
- data/lib/scarpe/wv/edit_line.rb +2 -2
- data/lib/scarpe/wv/flow.rb +10 -20
- data/lib/scarpe/wv/font.rb +36 -0
- data/lib/scarpe/wv/html.rb +3 -2
- data/lib/scarpe/wv/image.rb +7 -2
- data/lib/scarpe/wv/line.rb +4 -7
- data/lib/scarpe/wv/link.rb +1 -0
- data/lib/scarpe/wv/list_box.rb +3 -3
- data/lib/scarpe/wv/para.rb +16 -14
- data/lib/scarpe/wv/radio.rb +34 -0
- data/lib/scarpe/wv/shape.rb +44 -8
- data/lib/scarpe/wv/slot.rb +81 -0
- data/lib/scarpe/wv/spacing.rb +1 -1
- data/lib/scarpe/wv/span.rb +10 -8
- data/lib/scarpe/wv/stack.rb +10 -30
- data/lib/scarpe/wv/star.rb +11 -12
- data/lib/scarpe/wv/subscription_item.rb +50 -0
- data/lib/scarpe/wv/video.rb +34 -0
- data/lib/scarpe/wv/web_wrangler.rb +238 -58
- data/lib/scarpe/wv/webview_local_display.rb +27 -5
- data/lib/scarpe/wv/webview_relay_display.rb +18 -119
- data/lib/scarpe/wv/webview_relay_util.rb +143 -0
- data/lib/scarpe/wv/widget.rb +80 -11
- data/lib/scarpe/wv/wv_display_worker.rb +17 -4
- data/lib/scarpe/wv.rb +33 -4
- data/lib/scarpe/wv_local.rb +1 -1
- data/lib/scarpe/wv_relay.rb +1 -1
- data/lib/scarpe.rb +3 -32
- data/scarpe-components/.gitignore +1 -0
- data/scarpe-components/Gemfile +22 -0
- data/scarpe-components/README.md +35 -0
- data/scarpe-components/Rakefile +12 -0
- data/scarpe-components/lib/scarpe/components/base64.rb +29 -0
- data/scarpe-components/lib/scarpe/components/file_helpers.rb +65 -0
- data/scarpe-components/lib/scarpe/components/modular_logger.rb +113 -0
- data/scarpe-components/lib/scarpe/components/print_logger.rb +43 -0
- data/{lib/scarpe → scarpe-components/lib/scarpe/components}/promises.rb +102 -35
- data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +170 -0
- data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +217 -0
- data/scarpe-components/lib/scarpe/components/version.rb +7 -0
- data/scarpe-components/scarpe-components.gemspec +38 -0
- data/scarpe-components/test/test_components.rb +9 -0
- data/scarpe-components/test/test_helper.rb +23 -0
- data/scarpe-components/test/test_promises.rb +260 -0
- data/scarpe-components/test/test_segmented_app_files.rb +182 -0
- data/{lib/scarpe → spikes}/glibui/widget.rb +2 -2
- data/{lib/scarpe → spikes}/glibui.rb +1 -1
- data/templates/basic_class_template.erb +1 -1
- data/templates/class_template_with_event_bind.erb +1 -1
- data/templates/class_template_with_shapes.erb +1 -1
- data/templates/webview_template.erb +0 -3
- metadata +151 -118
- data/examples/fill.rb +0 -25
- data/examples/legacy/not_checked/shoes-contrib/basic/class-book.yaml +0 -387
- data/examples/legacy/not_checked/shoes-contrib/good/good-clock.rb +0 -51
- data/examples/legacy/not_checked/shoes-contrib/good/good-follow.rb +0 -26
- data/examples/legacy/not_checked/shoes-contrib/good/good-reminder.rb +0 -174
- data/examples/legacy/not_checked/shoes-contrib/good/good-vjot.rb +0 -56
- data/examples/legacy/not_checked/shoes-contrib/simple/simple-timer.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/good-clock.rb +0 -51
- data/examples/legacy/not_checked/shoes-dep-samples/good-follow.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/good-reminder.rb +0 -174
- data/examples/legacy/not_checked/shoes-dep-samples/good-vjot.rb +0 -56
- data/examples/legacy/not_checked/shoes-dep-samples/simple-accordion.rb +0 -75
- data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-shapes.rb +0 -17
- data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-text.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-arc.rb +0 -23
- data/examples/legacy/not_checked/shoes-dep-samples/simple-bounce.rb +0 -24
- data/examples/legacy/not_checked/shoes-dep-samples/simple-calc.rb +0 -70
- data/examples/legacy/not_checked/shoes-dep-samples/simple-chipmunk.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/simple-control-sizes.rb +0 -24
- data/examples/legacy/not_checked/shoes-dep-samples/simple-curve.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/simple-dialogs.rb +0 -29
- data/examples/legacy/not_checked/shoes-dep-samples/simple-draw.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-editor.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-form.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-form.shy +0 -0
- data/examples/legacy/not_checked/shoes-dep-samples/simple-mask.rb +0 -21
- data/examples/legacy/not_checked/shoes-dep-samples/simple-menu.rb +0 -31
- data/examples/legacy/not_checked/shoes-dep-samples/simple-menu1.rb +0 -35
- data/examples/legacy/not_checked/shoes-dep-samples/simple-rubygems.rb +0 -29
- data/examples/legacy/not_checked/shoes-dep-samples/simple-slide.rb +0 -45
- data/examples/legacy/not_checked/shoes-dep-samples/simple-sphere.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-sqlite3.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-timer.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-video.rb +0 -13
- data/examples/legacy/not_checked/simple/anim-text.rb +0 -13
- data/examples/legacy/not_checked/simple/arc.rb +0 -23
- data/examples/legacy/not_checked/simple/bounce.rb +0 -24
- data/examples/legacy/not_checked/simple/chipmunk.rb +0 -26
- data/examples/legacy/not_checked/simple/curve.rb +0 -26
- data/examples/legacy/not_checked/simple/dialogs.rb +0 -29
- data/examples/legacy/not_checked/simple/downloader.rb +0 -40
- data/examples/legacy/not_checked/simple/draw.rb +0 -13
- data/examples/legacy/not_checked/simple/mask.rb +0 -21
- data/examples/legacy/not_checked/simple/slide.rb +0 -45
- data/examples/legacy/not_checked/simple/sphere.rb +0 -28
- data/lib/constants.rb +0 -5
- data/lib/scarpe/app.rb +0 -78
- data/lib/scarpe/document_root.rb +0 -20
- data/lib/scarpe/fill.rb +0 -23
- data/lib/scarpe/flow.rb +0 -19
- data/lib/scarpe/line.rb +0 -25
- data/lib/scarpe/logger.rb +0 -155
- data/lib/scarpe/shape.rb +0 -19
- data/lib/scarpe/spacing.rb +0 -9
- data/lib/scarpe/stack.rb +0 -70
- data/lib/scarpe/text_widget.rb +0 -42
- data/lib/scarpe/unit_test_helpers.rb +0 -163
- data/lib/scarpe/widgets.rb +0 -30
- data/lib/scarpe/wv/fill.rb +0 -30
- data/lib/scarpe/wv/shape_helper.rb +0 -44
- data/scarpe-0.2.0.gem +0 -0
- /data/{lib/scarpe → spikes}/glibui/README.md +0 -0
- /data/{lib/scarpe → spikes}/glibui/alert.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/app.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/background.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/border.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/button.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/dimensions.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/document_root.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/edit_box.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/edit_line.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/flow.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/html.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/image.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/link.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/local_display.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/para.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/spacing.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/stack.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/text_widget.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/alert.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/button.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/colors.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/core.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/flow.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/libui.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/notepad.md +0 -0
- /data/{lib/scarpe → spikes}/libui/para.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/stack.rb +0 -0
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "scarpe/components/unit_test_helpers"
|
4
|
+
require "scarpe/evented_assertions"
|
5
|
+
|
6
|
+
require "fiber"
|
7
|
+
|
8
|
+
module Scarpe::Test
|
9
|
+
# We'd like something we can call Shoes widget methods on, such as para.replace.
|
10
|
+
# But we'd also like to be able to grab the corresponding display widget and
|
11
|
+
# call some of *those* methods.
|
12
|
+
class CCProxy
|
13
|
+
attr_reader :display
|
14
|
+
attr_reader :obj
|
15
|
+
|
16
|
+
def initialize(obj)
|
17
|
+
@obj = obj
|
18
|
+
# TODO: how to do this with Webview relay? Proxy object to send a message, maybe?
|
19
|
+
@display = ::Shoes::DisplayService.display_service.query_display_widget_for(obj.linkable_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method, ...)
|
23
|
+
if @obj.respond_to?(method)
|
24
|
+
self.singleton_class.define_method(method) do |*args, **kwargs, &block|
|
25
|
+
@obj.send(method, *args, **kwargs, &block)
|
26
|
+
end
|
27
|
+
send(method, ...)
|
28
|
+
else
|
29
|
+
super # raise an exception
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to_missing?(method_name, include_private = false)
|
34
|
+
@obj.respond_to_missing?(method_name, include_private)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# This class defines the CatsCradle DSL. It also holds a "bag of fibers"
|
39
|
+
# with promises for when they should next resume.
|
40
|
+
class CCInstance
|
41
|
+
include Shoes::Log
|
42
|
+
include Scarpe::Test::EventedAssertions
|
43
|
+
include Scarpe::Test::Helpers
|
44
|
+
|
45
|
+
def self.instance
|
46
|
+
@instance ||= CCInstance.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize
|
50
|
+
log_init("CatsCradle")
|
51
|
+
|
52
|
+
@assertion_data = []
|
53
|
+
@assertions_passed = 0
|
54
|
+
@assertions_failed = []
|
55
|
+
|
56
|
+
@waiting_fibers = []
|
57
|
+
@event_promises = {}
|
58
|
+
|
59
|
+
@manager_fiber = Fiber.new do
|
60
|
+
loop do
|
61
|
+
# A fiber can run briefly and then exit. It can run and then block on an API call.
|
62
|
+
# These fibers return promises to indicate to CatsCradle when they can run again.
|
63
|
+
# A fiber that is no longer #alive? is assumed to be successfully finished.
|
64
|
+
@waiting_fibers.each do |fiber_data|
|
65
|
+
next unless fiber_data[:promise].fulfilled?
|
66
|
+
|
67
|
+
@log.debug("Resuming fiber with value #{fiber_data[:promise].returned_value.inspect}")
|
68
|
+
result = fiber_data[:fiber].transfer fiber_data[:promise].returned_value
|
69
|
+
|
70
|
+
# Dead fibers will be removed later, just leave it
|
71
|
+
next unless fiber_data[:fiber].alive?
|
72
|
+
|
73
|
+
case result
|
74
|
+
when ::Scarpe::Promise
|
75
|
+
fiber_data[:promise] = result
|
76
|
+
else
|
77
|
+
raise "Unexpected object returned from Fiber#transfer for still-living Fiber! #{result.inspect}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Throw out dead fibers or those that will never wake
|
82
|
+
@waiting_fibers.select! do |fiber_data|
|
83
|
+
fiber_data[:fiber].alive? && !fiber_data[:promise].rejected?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Done with this iteration
|
87
|
+
Fiber.yield
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# If we add "every" events, that's likely to complicate timing and event_promise handling.
|
93
|
+
EVENT_TYPES = [:next_heartbeat, :next_redraw]
|
94
|
+
|
95
|
+
# This needs to be called after the basic display service objects exist
|
96
|
+
# and we can find the control interface.
|
97
|
+
def event_init
|
98
|
+
return if @cc_init_done
|
99
|
+
|
100
|
+
@cc_init_done = true
|
101
|
+
|
102
|
+
@control_interface = ::Shoes::DisplayService.display_service.control_interface
|
103
|
+
@wrangler = @control_interface.wrangler
|
104
|
+
|
105
|
+
cc_instance = self # ControlInterface#on_event does an instance eval. We'll reset self with another.
|
106
|
+
|
107
|
+
@control_interface.on_event(:every_heartbeat) do
|
108
|
+
cc_instance.instance_eval do
|
109
|
+
p = @event_promises.delete(:next_heartbeat)
|
110
|
+
p&.fulfilled!
|
111
|
+
|
112
|
+
# Give every ready fiber a chance to run once.
|
113
|
+
@manager_fiber.resume
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
@control_interface.on_event(:every_redraw) do
|
118
|
+
cc_instance.instance_eval do
|
119
|
+
p = @event_promises.delete(:next_redraw)
|
120
|
+
p&.fulfilled!
|
121
|
+
|
122
|
+
# Give every ready fiber a chance to run once.
|
123
|
+
@manager_fiber.resume
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def event_promise(event)
|
129
|
+
@event_promises[event] ||= ::Scarpe::Promise.new
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_event(event, &block)
|
133
|
+
raise "Unknown event type: #{event.inspect}!" unless EVENT_TYPES.include?(event)
|
134
|
+
|
135
|
+
f = Fiber.new do
|
136
|
+
CCInstance.instance.instance_eval(&block)
|
137
|
+
end
|
138
|
+
@waiting_fibers << { promise: event_promise(event), fiber: f }
|
139
|
+
end
|
140
|
+
|
141
|
+
# What to do about TextWidgets? Link, code, em, strong?
|
142
|
+
# Also, wait, what's up with span? What *is* that?
|
143
|
+
Shoes::Widget.widget_classes.each do |widget_class|
|
144
|
+
finder_name = widget_class.dsl_name
|
145
|
+
|
146
|
+
define_method(finder_name) do |*args|
|
147
|
+
app = Shoes::App.instance
|
148
|
+
|
149
|
+
widgets = app.find_widgets_by(widget_class, *args)
|
150
|
+
raise "Found more than one #{finder_name} matching #{args.inspect}!" if widgets.size > 1
|
151
|
+
raise "Found no #{finder_name} matching #{args.inspect}!" if widgets.empty?
|
152
|
+
|
153
|
+
CCProxy.new(widgets[0])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def proxy_for(shoes_widget)
|
158
|
+
CCProxy.new(shoes_widget)
|
159
|
+
end
|
160
|
+
|
161
|
+
def wait(promise)
|
162
|
+
raise("Must supply a promise to wait!") unless promise.is_a?(::Scarpe::Promise)
|
163
|
+
|
164
|
+
# Wait until this promise is complete before running again
|
165
|
+
@manager_fiber.transfer(promise)
|
166
|
+
end
|
167
|
+
|
168
|
+
# This returns a promise, which can be waited on using wait()
|
169
|
+
def fully_updated
|
170
|
+
@wrangler.promise_dom_fully_updated
|
171
|
+
end
|
172
|
+
|
173
|
+
def dom_html(timeout: 1.0)
|
174
|
+
query_js_value("document.getElementById('wrapper-wvroot').innerHTML", timeout:)
|
175
|
+
end
|
176
|
+
|
177
|
+
def query_js_value(js_code, timeout: 1.0)
|
178
|
+
js_promise = @wrangler.eval_js_async(js_code, timeout:)
|
179
|
+
|
180
|
+
# This promise will return the string, so we can just pass it to #transfer
|
181
|
+
@manager_fiber.transfer(js_promise)
|
182
|
+
end
|
183
|
+
|
184
|
+
def assert(value, msg = nil)
|
185
|
+
msg ||= "Assertion #{value ? "succeeded" : "failed"}"
|
186
|
+
@assertion_data << [value ? true : false, msg]
|
187
|
+
if value
|
188
|
+
@assertions_passed += 1
|
189
|
+
else
|
190
|
+
@assertions_failed << msg
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def assert_equal(expected, actual, msg = nil)
|
195
|
+
msg ||= "Expected #{actual.inspect} to equal #{expected.inspect}!"
|
196
|
+
assert actual == expected, msg
|
197
|
+
end
|
198
|
+
|
199
|
+
def assertion_data_as_a_struct
|
200
|
+
{
|
201
|
+
still_pending: 0,
|
202
|
+
succeeded: @assertions_passed,
|
203
|
+
failed: @assertions_failed.size,
|
204
|
+
failures: @assertions_failed,
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_metadata
|
209
|
+
{}
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_finished
|
213
|
+
if !@assertions_failed.empty?
|
214
|
+
return_results(false, "Assertions failed", assertion_data_as_a_struct)
|
215
|
+
elsif @assertions_passed > 0
|
216
|
+
return_results(true, "All assertions passed", assertion_data_as_a_struct)
|
217
|
+
else
|
218
|
+
return_results(true, "Test finished successfully")
|
219
|
+
end
|
220
|
+
::Shoes::DisplayService.dispatch_event("destroy", nil)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# "Cat's Cradle" is a children's game where they interlace string between
|
225
|
+
# their fingers to make beautiful complicated shapes. The interlacing
|
226
|
+
# of fibers made it a good name for a prototype.
|
227
|
+
|
228
|
+
# An attempt at an experimental Fiber-based testing system to deal with
|
229
|
+
# Shoes, Display and JS all at the same time.
|
230
|
+
#
|
231
|
+
# In general, we'll use Fiber.transfer to bounce control back and forth
|
232
|
+
# between the evented implementations (e.g. waiting for redraw) that
|
233
|
+
# need to return control to Webview, and the procedural test flows
|
234
|
+
# that look far better if we don't do that explicitly.
|
235
|
+
#
|
236
|
+
# Ruby Fiber basic docs: https://ruby-doc.org/core-3.0.0/Fiber.html
|
237
|
+
#
|
238
|
+
# This module is mixed into Shoes::App if we're running CatsCradle-based tests
|
239
|
+
module CatsCradle
|
240
|
+
def event_init
|
241
|
+
@cc_instance = CCInstance.instance
|
242
|
+
@cc_instance.event_init
|
243
|
+
end
|
244
|
+
|
245
|
+
def on_heartbeat(&block)
|
246
|
+
@cc_instance.on_event(:next_heartbeat, &block)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tempfile"
|
4
|
+
require "json"
|
5
|
+
require "fileutils"
|
6
|
+
|
7
|
+
module Scarpe::Test; end
|
8
|
+
|
9
|
+
# We need a separate assertion system for the kind of Scarpe
|
10
|
+
# testing where we start a subprocess, perform the assertion
|
11
|
+
# logic in the subprocess, and then run Minitest in the parent.
|
12
|
+
# It's an unusual setup.
|
13
|
+
module Scarpe::Test::EventedAssertions
|
14
|
+
# Assert that `text` includes `subtext`.
|
15
|
+
#
|
16
|
+
# @param text [String] the longer text
|
17
|
+
# @param subtext [String] the text that is asserted to be included
|
18
|
+
# @param msg [String,nil] if supplied, the failure message for the assertion
|
19
|
+
# @return [void]
|
20
|
+
def assert_include(text, subtext, msg = nil)
|
21
|
+
msg ||= "Expected #{text.inspect} to include #{subtext.inspect}"
|
22
|
+
assert text.include?(subtext), msg
|
23
|
+
end
|
24
|
+
|
25
|
+
# Assert that `text` does not include `subtext`.
|
26
|
+
#
|
27
|
+
# @param text [String] the longer text
|
28
|
+
# @param subtext [String] the text that is asserted to not be included
|
29
|
+
# @param msg [String,nil] if supplied, the failure message for the assertion
|
30
|
+
# @return [void]
|
31
|
+
def assert_not_include(text, subtext, msg = nil)
|
32
|
+
msg ||= "Expected #{text.inspect} not to include #{subtext.inspect}"
|
33
|
+
assert !text.include?(subtext), msg
|
34
|
+
end
|
35
|
+
|
36
|
+
# Assert that `actual_html` is the same as `expected_tag` with `opts`.
|
37
|
+
# This uses Scarpe's HTML tag-based renderer to render the tag and options
|
38
|
+
# into text, and valides that the text is the same.
|
39
|
+
#
|
40
|
+
# @see Scarpe::HTML.render
|
41
|
+
#
|
42
|
+
# @param actual_html [String] the html to compare to
|
43
|
+
# @param expected_tag [String,Symbol] the HTML tag, used to send a method call
|
44
|
+
# @param opts keyword options passed to the tag method call
|
45
|
+
# @yield block passed to the tag method call.
|
46
|
+
# @return [void]
|
47
|
+
def assert_html(actual_html, expected_tag, **opts, &block)
|
48
|
+
expected_html = Scarpe::HTML.render do |h|
|
49
|
+
h.public_send(expected_tag, opts, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal expected_html, actual_html
|
53
|
+
end
|
54
|
+
|
55
|
+
# This does a final return of results. If it gets called
|
56
|
+
# multiple times, the test fails because that's not allowed.
|
57
|
+
#
|
58
|
+
# @param result_bool [Boolean] true if the results are success, false if failure
|
59
|
+
# @param msg [String] the message included with the results
|
60
|
+
# @param data [Hash] any additional data to pass with the results
|
61
|
+
# @return void
|
62
|
+
def return_results(result_bool, msg, data = {})
|
63
|
+
result_file = ENV["SCARPE_TEST_RESULTS"] || "./scarpe_results.txt"
|
64
|
+
|
65
|
+
result_structs = [result_bool, msg, data.merge(test_metadata)]
|
66
|
+
if File.exist?(result_file)
|
67
|
+
results_returned = JSON.parse File.read(result_file)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Multiple different sets of results is bad, even if both are passing.
|
71
|
+
if results_returned && results_returned[0..1] != result_structs[0..1]
|
72
|
+
# Just raising here doesn't reliably fail the test.
|
73
|
+
# See: https://github.com/scarpe-team/scarpe/issues/212
|
74
|
+
Shoes::Log.logger("Test Results").error("Writing multi-result failure file to #{result_file.inspect}!")
|
75
|
+
|
76
|
+
new_res_data = { first_result: results_returned, second_result: result_structs }.merge(test_metadata)
|
77
|
+
bad_result = [false, "Returned two sets of results!", new_res_data]
|
78
|
+
File.write(result_file, JSON.pretty_generate(bad_result))
|
79
|
+
|
80
|
+
return
|
81
|
+
elsif results_returned
|
82
|
+
Shoes::Log.logger("Test Results").warn "Returning identical results twice: #{results_returned.inspect}"
|
83
|
+
end
|
84
|
+
|
85
|
+
Shoes::Log.logger("Test Results").debug("Writing results file #{result_file.inspect} to disk!")
|
86
|
+
File.write(result_file, JSON.pretty_generate(result_structs))
|
87
|
+
end
|
88
|
+
end
|
data/lib/scarpe/version.rb
CHANGED
data/lib/scarpe/wv/alert.rb
CHANGED
@@ -23,8 +23,9 @@ class Scarpe
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
protected
|
27
27
|
|
28
|
+
# If the whole widget is hidden, the parent style adds display:none
|
28
29
|
def overlay_style
|
29
30
|
{
|
30
31
|
position: "fixed",
|
@@ -38,7 +39,7 @@ class Scarpe
|
|
38
39
|
display: "flex",
|
39
40
|
"align-items": "center",
|
40
41
|
"justify-content": "center",
|
41
|
-
}
|
42
|
+
}.merge(style)
|
42
43
|
end
|
43
44
|
|
44
45
|
def modal_style
|
data/lib/scarpe/wv/app.rb
CHANGED
@@ -3,15 +3,11 @@
|
|
3
3
|
class Scarpe
|
4
4
|
# Scarpe::WebviewApp must only be used from the main thread, due to GTK+ limitations.
|
5
5
|
class WebviewApp < WebviewWidget
|
6
|
-
attr_reader :debug
|
7
6
|
attr_reader :control_interface
|
8
7
|
|
9
8
|
attr_writer :shoes_linkable_id
|
10
9
|
|
11
10
|
def initialize(properties)
|
12
|
-
# Is this a thing? Do we care about this?
|
13
|
-
# opts = @control_interface.app_opts_get_override(opts)
|
14
|
-
|
15
11
|
super
|
16
12
|
|
17
13
|
# It's possible to provide a Ruby script by setting
|
@@ -24,6 +20,7 @@ class Scarpe
|
|
24
20
|
# events, specify overrides and so on.
|
25
21
|
@control_interface = ControlInterface.new
|
26
22
|
if ENV["SCARPE_TEST_CONTROL"]
|
23
|
+
require "scarpe/components/unit_test_helpers"
|
27
24
|
@control_interface.instance_eval File.read(ENV["SCARPE_TEST_CONTROL"])
|
28
25
|
end
|
29
26
|
|
@@ -31,8 +28,9 @@ class Scarpe
|
|
31
28
|
@view = Scarpe::WebWrangler.new title: @title,
|
32
29
|
width: @width,
|
33
30
|
height: @height,
|
34
|
-
resizable: @resizable
|
35
|
-
|
31
|
+
resizable: @resizable
|
32
|
+
|
33
|
+
@callbacks = {}
|
36
34
|
|
37
35
|
# The control interface has to exist to get callbacks like "override Scarpe app opts".
|
38
36
|
# But the Scarpe App needs those options to be created. So we can't pass these to
|
@@ -50,11 +48,11 @@ class Scarpe
|
|
50
48
|
scarpe_app = self
|
51
49
|
|
52
50
|
@view.init_code("scarpeInit") do
|
53
|
-
|
51
|
+
request_redraw!
|
54
52
|
end
|
55
53
|
|
56
54
|
@view.bind("scarpeHandler") do |*args|
|
57
|
-
|
55
|
+
handle_callback(*args)
|
58
56
|
end
|
59
57
|
|
60
58
|
@view.bind("scarpeExit") do
|
@@ -81,5 +79,29 @@ class Scarpe
|
|
81
79
|
@view = nil
|
82
80
|
end
|
83
81
|
end
|
82
|
+
|
83
|
+
# All JS callbacks to Scarpe widgets are dispatched
|
84
|
+
# via this handler
|
85
|
+
def handle_callback(name, *args)
|
86
|
+
@callbacks[name].call(*args)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Bind a Scarpe callback name; see handle_callback above.
|
90
|
+
# See Scarpe::Widget for how the naming is set up
|
91
|
+
def bind(name, &block)
|
92
|
+
@callbacks[name] = block
|
93
|
+
end
|
94
|
+
|
95
|
+
# Request a full redraw if Webview is running. Otherwise
|
96
|
+
# this is a no-op.
|
97
|
+
#
|
98
|
+
# @return [void]
|
99
|
+
def request_redraw!
|
100
|
+
wrangler = WebviewDisplayService.instance.wrangler
|
101
|
+
if wrangler.is_running
|
102
|
+
wrangler.replace(@document_root.to_html)
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
84
106
|
end
|
85
107
|
end
|
data/lib/scarpe/wv/arc.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "shape_helper"
|
4
|
-
|
5
3
|
class Scarpe
|
6
4
|
class WebviewArc < Scarpe::WebviewWidget
|
7
|
-
include ShapeHelper
|
8
5
|
def initialize(properties)
|
9
6
|
super(properties)
|
10
7
|
end
|
@@ -20,17 +17,19 @@ class Scarpe
|
|
20
17
|
end
|
21
18
|
end
|
22
19
|
|
23
|
-
|
20
|
+
protected
|
24
21
|
|
25
22
|
def style
|
26
|
-
{
|
23
|
+
super.merge({
|
27
24
|
left: "#{@left}px",
|
28
25
|
top: "#{@top}px",
|
29
26
|
width: "#{@width}px",
|
30
27
|
height: "#{@height}px",
|
31
|
-
}
|
28
|
+
})
|
32
29
|
end
|
33
30
|
|
31
|
+
private
|
32
|
+
|
34
33
|
def arc_path
|
35
34
|
center_x = @width / 2
|
36
35
|
center_y = @height / 2
|
data/lib/scarpe/wv/background.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "scarpe/components/base64"
|
4
|
+
|
3
5
|
class Scarpe
|
4
6
|
module WebviewBackground
|
7
|
+
include Components::Base64
|
8
|
+
|
5
9
|
def style
|
6
10
|
styles = (super if defined?(super)) || {}
|
7
11
|
return styles unless @background_color
|
8
12
|
|
9
|
-
color =
|
13
|
+
color = case @background_color
|
14
|
+
when Array
|
15
|
+
"rgba(#{@background_color.join(", ")})"
|
16
|
+
when Range
|
10
17
|
"linear-gradient(45deg, #{@background_color.first}, #{@background_color.last})"
|
18
|
+
when ->(value) { File.exist?(value) }
|
19
|
+
"url(data:image/png;base64,#{encode_file_to_base64(@background_color)})"
|
11
20
|
else
|
12
21
|
@background_color
|
13
22
|
end
|
data/lib/scarpe/wv/border.rb
CHANGED
@@ -6,12 +6,14 @@ class Scarpe
|
|
6
6
|
styles = (super if defined?(super)) || {}
|
7
7
|
return styles unless @border_color
|
8
8
|
|
9
|
-
border_color =
|
10
|
-
|
9
|
+
border_color = case @border_color
|
10
|
+
when Range
|
11
|
+
{ "border-image": "linear-gradient(45deg, #{@border_color.first}, #{@border_color.last})" }
|
12
|
+
when Array
|
13
|
+
{ "border-color": "rgba(#{@border_color.join(", ")})" }
|
11
14
|
else
|
12
15
|
{ "border-color": @border_color }
|
13
16
|
end
|
14
|
-
|
15
17
|
styles.merge(
|
16
18
|
"border-style": "solid",
|
17
19
|
"border-width": "#{@options[:strokewidth] || 1}px",
|
data/lib/scarpe/wv/button.rb
CHANGED
@@ -20,24 +20,26 @@ class Scarpe
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
protected
|
24
24
|
|
25
25
|
def style
|
26
|
-
styles =
|
27
|
-
|
28
|
-
styles["
|
29
|
-
styles["padding-
|
26
|
+
styles = super
|
27
|
+
|
28
|
+
styles[:"background-color"] = @color
|
29
|
+
styles[:"padding-top"] = @padding_top
|
30
|
+
styles[:"padding-bottom"] = @padding_bottom
|
30
31
|
styles[:color] = @text_color
|
31
32
|
styles[:width] = Dimensions.length(@width) if @width
|
32
33
|
styles[:height] = Dimensions.length(@height) if @height
|
33
|
-
styles["font-size"] = @font_size
|
34
|
+
styles[:"font-size"] = @font_size
|
34
35
|
|
35
36
|
styles[:top] = Dimensions.length(@top) if @top
|
36
37
|
styles[:left] = Dimensions.length(@left) if @left
|
37
38
|
styles[:position] = "absolute" if @top || @left
|
38
|
-
styles["font-size"] = Dimensions.length(font_size) if @size
|
39
|
-
styles["font-family"] = @font if @font
|
40
|
-
styles[
|
39
|
+
styles[:"font-size"] = Dimensions.length(font_size) if @size
|
40
|
+
styles[:"font-family"] = @font if @font
|
41
|
+
styles[:color] = rgb_to_hex(@stroke) if @stroke
|
42
|
+
|
41
43
|
styles
|
42
44
|
end
|
43
45
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Scarpe
|
4
|
+
class WebviewCheck < Scarpe::WebviewWidget
|
5
|
+
attr_reader :text
|
6
|
+
|
7
|
+
def initialize(properties)
|
8
|
+
super
|
9
|
+
|
10
|
+
bind("click") do
|
11
|
+
send_self_event(event_name: "click", target: shoes_linkable_id)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def properties_changed(changes)
|
16
|
+
checked = changes.delete("checked")
|
17
|
+
|
18
|
+
html_element.toggle_input_button(checked)
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def element
|
24
|
+
HTML.render do |h|
|
25
|
+
h.input(type: :checkbox, id: html_id, onclick: handler_js_code("click"), value: "hmm #{text}", checked: @checked, style:)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# The ControlInterface is used for testing. It's a way to register interest
|
4
|
-
# in important events like redraw, init and shutdown, and to
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
4
|
+
# in important events like redraw, init and shutdown, and to configure a
|
5
|
+
# Shoes app for testing. Note that no part of the Scarpe framework should
|
6
|
+
# ever *depend* on ControlInterface. It's for testing, not normal operation.
|
7
|
+
# If no ControlInterface were ever created or called, Scarpe apps should run
|
8
|
+
# fine with no modifications.
|
9
9
|
#
|
10
10
|
# And if you depend on this from the framework, I'll add a check-mode that
|
11
11
|
# never dispatches any events to any handlers. Do NOT test me on this.
|
12
12
|
|
13
13
|
class Scarpe
|
14
14
|
class ControlInterface
|
15
|
-
include
|
15
|
+
include Shoes::Log
|
16
16
|
|
17
17
|
SUBSCRIBE_EVENTS = [:init, :shutdown, :next_redraw, :every_redraw, :next_heartbeat, :every_heartbeat]
|
18
18
|
DISPATCH_EVENTS = [:init, :shutdown, :redraw, :heartbeat]
|
@@ -33,7 +33,7 @@ class Scarpe
|
|
33
33
|
"<#ControlInterface>"
|
34
34
|
end
|
35
35
|
|
36
|
-
# This should get called once, from
|
36
|
+
# This should get called once, from Shoes::App
|
37
37
|
def set_system_components(app:, doc_root:, wrangler:)
|
38
38
|
unless app && wrangler
|
39
39
|
@log.error("False app passed to set_system_components!") unless app
|
@@ -79,23 +79,17 @@ class Scarpe
|
|
79
79
|
# The control interface has overrides for certain settings. If the override has been specified,
|
80
80
|
# those settings will be overridden.
|
81
81
|
|
82
|
-
# Override the Shoes app opts like "debug:" and "die_after:" with new ones.
|
83
|
-
def override_app_opts(new_opts)
|
84
|
-
@new_app_opts = new_opts
|
85
|
-
end
|
86
|
-
|
87
|
-
# Called by Scarpe::App to get the override options
|
88
|
-
def app_opts_get_override(opts)
|
89
|
-
@new_app_opts || opts
|
90
|
-
end
|
91
|
-
|
92
82
|
# On recognised events, this sets a handler for that event
|
93
83
|
def on_event(event, &block)
|
94
84
|
unless SUBSCRIBE_EVENTS.include?(event)
|
95
85
|
raise "Illegal subscribe to event #{event.inspect}! Valid values are: #{SUBSCRIBE_EVENTS.inspect}"
|
96
86
|
end
|
97
87
|
|
98
|
-
@
|
88
|
+
@unsub_id ||= 0
|
89
|
+
@unsub_id += 1
|
90
|
+
|
91
|
+
@event_handlers[event] << { handler: block, unsub: @unsub_id }
|
92
|
+
@unsub_id
|
99
93
|
end
|
100
94
|
|
101
95
|
# Send out the specified event
|
@@ -145,8 +139,8 @@ class Scarpe
|
|
145
139
|
private
|
146
140
|
|
147
141
|
def dumb_dispatch_event(event, *args, **keywords)
|
148
|
-
@event_handlers[event].each do |
|
149
|
-
instance_eval(*args, **keywords, &handler)
|
142
|
+
@event_handlers[event].each do |data|
|
143
|
+
instance_eval(*args, **keywords, &data[:handler])
|
150
144
|
end
|
151
145
|
end
|
152
146
|
end
|