scarpe 0.2.2 → 0.3.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 +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +16 -2
- data/Gemfile.lock +7 -3
- data/README.md +24 -8
- data/Rakefile +1 -1
- data/examples/animate.rb +20 -0
- data/examples/arrow.rb +10 -0
- data/examples/btn_tooltip.rb +7 -0
- data/examples/button_style_changed.rb +7 -0
- data/examples/button_styles_default.rb +6 -0
- data/examples/gen.rb +8 -8
- data/examples/highlander.rb +3 -3
- data/examples/legacy/README.md +6 -0
- data/examples/legacy/not_checked/shoes-contrib/basic/shoes-notes.rb +1 -1
- data/examples/legacy/not_checked/simple/anim-shapes.rb +1 -1
- data/examples/legacy/not_checked/speedometer_app.rb +55 -0
- data/examples/legacy/working/simple/image-icon.rb +3 -0
- data/examples/legacy/{not_checked → working}/simple/image.rb +1 -1
- data/examples/list_box_choose.rb +17 -0
- data/examples/local_assets/local_file_server.rb +82 -0
- data/examples/local_assets/sample.gif +0 -0
- data/examples/local_assets/sample.mp4 +0 -0
- data/examples/local_fonts.rb +2 -2
- data/examples/local_images.rb +2 -3
- data/examples/para/para_text.rb +14 -0
- data/examples/progress.rb +31 -0
- data/examples/radio/radio_groups.rb +2 -2
- data/examples/rect.rb +4 -0
- data/examples/rotate_shapes.rb +17 -0
- data/examples/simpler-menu.rb +21 -0
- data/exe/scarpe +2 -1
- data/lacci/Gemfile +2 -0
- data/lacci/Gemfile.lock +8 -1
- data/lacci/lacci.gemspec +1 -1
- data/lacci/lib/lacci/scarpe_cli.rb +2 -1
- data/lacci/lib/lacci/scarpe_core.rb +2 -1
- data/lacci/lib/lacci/version.rb +1 -1
- data/lacci/lib/scarpe/niente/app.rb +23 -0
- data/lacci/lib/scarpe/niente/display_service.rb +62 -0
- data/lacci/lib/scarpe/niente/drawable.rb +57 -0
- data/lacci/lib/scarpe/niente/logger.rb +29 -0
- data/lacci/lib/scarpe/niente/shoes_spec.rb +87 -0
- data/lacci/lib/scarpe/niente.rb +20 -0
- data/lacci/lib/shoes/app.rb +88 -43
- data/lacci/lib/shoes/background.rb +2 -2
- data/lacci/lib/shoes/border.rb +2 -2
- data/lacci/lib/shoes/builtins.rb +63 -0
- data/lacci/lib/shoes/changelog.rb +52 -0
- data/lacci/lib/shoes/colors.rb +3 -1
- data/lacci/lib/shoes/constants.rb +19 -1
- data/lacci/lib/shoes/display_service.rb +39 -16
- data/lacci/lib/shoes/download.rb +2 -2
- data/lacci/lib/shoes/drawable.rb +380 -0
- data/lacci/lib/shoes/drawables/arc.rb +49 -0
- data/lacci/lib/shoes/drawables/arrow.rb +41 -0
- data/lacci/lib/shoes/drawables/button.rb +73 -0
- data/lacci/lib/shoes/{widgets → drawables}/check.rb +5 -4
- data/lacci/lib/shoes/{widgets → drawables}/document_root.rb +3 -3
- data/lacci/lib/shoes/{widgets → drawables}/edit_box.rb +6 -6
- data/lacci/lib/shoes/{widgets → drawables}/edit_line.rb +6 -6
- data/lacci/lib/shoes/{widgets → drawables}/flow.rb +6 -6
- data/lacci/lib/shoes/{widgets → drawables}/image.rb +6 -6
- data/lacci/lib/shoes/{widgets → drawables}/line.rb +7 -5
- data/lacci/lib/shoes/drawables/link.rb +34 -0
- data/lacci/lib/shoes/drawables/list_box.rb +56 -0
- data/lacci/lib/shoes/drawables/para.rb +118 -0
- data/lacci/lib/shoes/drawables/progress.rb +14 -0
- data/lacci/lib/shoes/drawables/radio.rb +33 -0
- data/lacci/lib/shoes/drawables/rect.rb +17 -0
- data/lacci/lib/shoes/{widgets → drawables}/shape.rb +6 -7
- data/lacci/lib/shoes/{widgets → drawables}/slot.rb +32 -20
- data/lacci/lib/shoes/{widgets → drawables}/span.rb +8 -7
- data/lacci/lib/shoes/{widgets → drawables}/stack.rb +6 -4
- data/lacci/lib/shoes/drawables/star.rb +50 -0
- data/lacci/lib/shoes/drawables/subscription_item.rb +93 -0
- data/lacci/lib/shoes/drawables/text_drawable.rb +63 -0
- data/lacci/lib/shoes/drawables/video.rb +16 -0
- data/lacci/lib/shoes/drawables/widget.rb +69 -0
- data/lacci/lib/shoes/drawables.rb +31 -0
- data/lacci/lib/shoes/errors.rb +28 -0
- data/lacci/lib/shoes/log.rb +2 -2
- data/lacci/lib/shoes/ruby_extensions.rb +15 -0
- data/lacci/lib/shoes/spacing.rb +2 -2
- data/lacci/lib/shoes-spec.rb +93 -0
- data/lacci/lib/shoes.rb +27 -7
- data/lacci/test/test_helper.rb +54 -0
- data/lacci/test/test_lacci.rb +12 -3
- data/lacci/test/test_shoes_errors.rb +49 -0
- data/lib/scarpe/cats_cradle.rb +81 -59
- data/lib/scarpe/errors.rb +77 -0
- data/lib/scarpe/evented_assertions.rb +50 -17
- data/lib/scarpe/shoes_spec.rb +181 -0
- data/lib/scarpe/version.rb +2 -2
- data/lib/scarpe/wv/app.rb +20 -20
- data/lib/scarpe/wv/arc.rb +4 -47
- data/lib/scarpe/wv/arrow.rb +9 -0
- data/lib/scarpe/wv/button.rb +7 -35
- data/lib/scarpe/wv/check.rb +3 -5
- data/lib/scarpe/wv/control_interface.rb +18 -20
- data/lib/scarpe/wv/document_root.rb +81 -4
- data/lib/scarpe/wv/{widget.rb → drawable.rb} +66 -43
- data/lib/scarpe/wv/edit_box.rb +4 -17
- data/lib/scarpe/wv/edit_line.rb +4 -18
- data/lib/scarpe/wv/flow.rb +2 -18
- data/lib/scarpe/wv/image.rb +8 -28
- data/lib/scarpe/wv/line.rb +3 -25
- data/lib/scarpe/wv/link.rb +3 -16
- data/lib/scarpe/wv/list_box.rb +6 -29
- data/lib/scarpe/wv/para.rb +11 -30
- data/lib/scarpe/wv/progress.rb +19 -0
- data/lib/scarpe/wv/radio.rb +9 -10
- data/lib/scarpe/wv/rect.rb +13 -0
- data/lib/scarpe/wv/shape.rb +3 -8
- data/lib/scarpe/wv/slot.rb +8 -25
- data/lib/scarpe/wv/span.rb +3 -27
- data/lib/scarpe/wv/stack.rb +2 -18
- data/lib/scarpe/wv/star.rb +3 -53
- data/lib/scarpe/wv/subscription_item.rb +38 -4
- data/lib/scarpe/wv/text_drawable.rb +32 -0
- data/lib/scarpe/wv/video.rb +15 -15
- data/lib/scarpe/wv/web_wrangler.rb +299 -329
- data/lib/scarpe/wv/webview_local_display.rb +48 -33
- data/lib/scarpe/wv/webview_relay_display.rb +12 -12
- data/lib/scarpe/wv/webview_relay_util.rb +7 -10
- data/lib/scarpe/wv/wv_display_worker.rb +2 -2
- data/lib/scarpe/wv.rb +45 -12
- data/lib/scarpe/wv_local.rb +1 -1
- data/lib/scarpe/wv_relay.rb +1 -1
- data/lib/scarpe.rb +1 -0
- data/logger/debug_web_wrangler.json +1 -1
- data/logger/scarpe_wv_test.json +1 -1
- data/scarpe-components/Gemfile.lock +86 -0
- data/scarpe-components/lib/scarpe/components/base64.rb +3 -7
- data/scarpe-components/lib/scarpe/components/calzini/alert.rb +49 -0
- data/scarpe-components/lib/scarpe/components/calzini/art_widgets.rb +203 -0
- data/scarpe-components/lib/scarpe/components/calzini/button.rb +39 -0
- data/scarpe-components/lib/scarpe/components/calzini/misc.rb +146 -0
- data/scarpe-components/lib/scarpe/components/calzini/para.rb +35 -0
- data/scarpe-components/lib/scarpe/components/calzini/slots.rb +155 -0
- data/scarpe-components/lib/scarpe/components/calzini/text_widgets.rb +65 -0
- data/scarpe-components/lib/scarpe/components/calzini.rb +149 -0
- data/scarpe-components/lib/scarpe/components/errors.rb +20 -0
- data/scarpe-components/lib/scarpe/components/file_helpers.rb +1 -0
- data/scarpe-components/lib/scarpe/components/html.rb +131 -0
- data/scarpe-components/lib/scarpe/components/minitest_export_reporter.rb +75 -0
- data/scarpe-components/lib/scarpe/components/minitest_import_runnable.rb +98 -0
- data/scarpe-components/lib/scarpe/components/minitest_result.rb +86 -0
- data/scarpe-components/lib/scarpe/components/modular_logger.rb +5 -5
- data/scarpe-components/lib/scarpe/components/print_logger.rb +9 -5
- data/scarpe-components/lib/scarpe/components/promises.rb +14 -14
- data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +36 -17
- data/scarpe-components/lib/scarpe/components/string_helpers.rb +10 -0
- data/scarpe-components/lib/scarpe/components/tiranti.rb +225 -0
- data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +45 -5
- data/scarpe-components/lib/scarpe/components/version.rb +2 -2
- data/scarpe-components/test/calzini/test_calzini_alert.rb +30 -0
- data/scarpe-components/test/calzini/test_calzini_art_drawables.rb +105 -0
- data/scarpe-components/test/calzini/test_calzini_button.rb +52 -0
- data/scarpe-components/test/calzini/test_calzini_misc.rb +115 -0
- data/scarpe-components/test/calzini/test_calzini_para.rb +37 -0
- data/scarpe-components/test/calzini/test_calzini_slots.rb +130 -0
- data/scarpe-components/test/calzini/test_calzini_text_drawables.rb +41 -0
- data/scarpe-components/test/mtr_data/exception.json +1 -0
- data/scarpe-components/test/mtr_data/fail_with_message.json +1 -0
- data/scarpe-components/test/mtr_data/skipped_no_message.json +1 -0
- data/scarpe-components/test/mtr_data/skipped_w_msg.json +1 -0
- data/scarpe-components/test/mtr_data/succeed_2_asserts.json +1 -0
- data/scarpe-components/test/test_dimensions.rb +26 -0
- data/scarpe-components/test/test_helper.rb +20 -0
- data/scarpe-components/test/test_html.rb +65 -0
- data/scarpe-components/test/test_minitest_result.rb +61 -0
- data/scarpe-components/test/test_promises.rb +5 -4
- data/scarpe-components/test/test_segmented_app_files.rb +8 -6
- data/scarpegen.rb +14 -14
- data/sig/scarpe.rbs +1 -1
- data/templates/basic_class_template.erb +13 -14
- data/templates/class_template_with_event_bind.erb +4 -4
- data/templates/class_template_with_shapes.erb +8 -17
- data/templates/example_template.erb +1 -1
- data/templates/module_template.erb +4 -4
- data/templates/webview_template.erb +3 -2
- metadata +113 -55
- data/examples/legacy/not_checked/shoes-contrib/elements/image-icon.rb +0 -3
- data/lacci/lib/shoes/widget.rb +0 -218
- data/lacci/lib/shoes/widgets/alert.rb +0 -19
- data/lacci/lib/shoes/widgets/arc.rb +0 -51
- data/lacci/lib/shoes/widgets/button.rb +0 -35
- data/lacci/lib/shoes/widgets/font.rb +0 -14
- data/lacci/lib/shoes/widgets/link.rb +0 -25
- data/lacci/lib/shoes/widgets/list_box.rb +0 -25
- data/lacci/lib/shoes/widgets/para.rb +0 -68
- data/lacci/lib/shoes/widgets/radio.rb +0 -35
- data/lacci/lib/shoes/widgets/star.rb +0 -44
- data/lacci/lib/shoes/widgets/subscription_item.rb +0 -60
- data/lacci/lib/shoes/widgets/text_widget.rb +0 -51
- data/lacci/lib/shoes/widgets/video.rb +0 -15
- data/lacci/lib/shoes/widgets.rb +0 -29
- data/lib/scarpe/wv/alert.rb +0 -66
- data/lib/scarpe/wv/background.rb +0 -27
- data/lib/scarpe/wv/border.rb +0 -24
- data/lib/scarpe/wv/control_interface_test.rb +0 -238
- data/lib/scarpe/wv/dimensions.rb +0 -22
- data/lib/scarpe/wv/font.rb +0 -36
- data/lib/scarpe/wv/html.rb +0 -108
- data/lib/scarpe/wv/spacing.rb +0 -41
- data/lib/scarpe/wv/text_widget.rb +0 -30
- /data/examples/legacy/not_checked/{expert → shoes-contrib/basic}/definr.rb +0 -0
- /data/examples/legacy/not_checked/{expert → shoes-contrib/basic}/funnies.rb +0 -0
- /data/examples/legacy/not_checked/shoes-contrib/{elements → basic}/list_box-select-class.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/basic-edit-box.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/basic-fps.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/border-cat.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/check-mate.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/manipulation → working/simple}/clear-slot.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/clock.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/gradient-shoes.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/list_box-shape-report.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/list_box.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/phat-button.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib → working}/simple/simple-calc.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/position → working/simple}/stack-width.rb +0 -0
- /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/width-introspec.rb +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class TestShoesErrors < NienteTest
|
|
6
|
+
def test_drawable_attr_error
|
|
7
|
+
run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
|
|
8
|
+
Shoes.app do
|
|
9
|
+
button "OK" do
|
|
10
|
+
star 10, 25, "sammy"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
SHOES_APP
|
|
14
|
+
assert_raises Shoes::Errors::InvalidAttributeValueError do
|
|
15
|
+
button().trigger_click
|
|
16
|
+
end
|
|
17
|
+
SHOES_SPEC
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_too_many_instances_error
|
|
21
|
+
run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
|
|
22
|
+
$ruby_main = self
|
|
23
|
+
Shoes.app do
|
|
24
|
+
end
|
|
25
|
+
SHOES_APP
|
|
26
|
+
assert_raises Shoes::Errors::TooManyInstancesError do
|
|
27
|
+
$ruby_main.instance_eval do
|
|
28
|
+
Shoes.app {}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
SHOES_SPEC
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_drawables_found_errors
|
|
35
|
+
run_test_niente_code(<<~SHOES_APP, app_test_code: <<~SHOES_SPEC)
|
|
36
|
+
Shoes.app do
|
|
37
|
+
button "OK"
|
|
38
|
+
button "Not OK"
|
|
39
|
+
end
|
|
40
|
+
SHOES_APP
|
|
41
|
+
assert_raises Shoes::Errors::MultipleDrawablesFoundError do
|
|
42
|
+
button()
|
|
43
|
+
end
|
|
44
|
+
assert_raises Shoes::Errors::NoDrawablesFoundError do
|
|
45
|
+
edit_line()
|
|
46
|
+
end
|
|
47
|
+
SHOES_SPEC
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/scarpe/cats_cradle.rb
CHANGED
|
@@ -6,8 +6,8 @@ require "scarpe/evented_assertions"
|
|
|
6
6
|
require "fiber"
|
|
7
7
|
|
|
8
8
|
module Scarpe::Test
|
|
9
|
-
# We'd like something we can call Shoes
|
|
10
|
-
# But we'd also like to be able to grab the corresponding display
|
|
9
|
+
# We'd like something we can call Shoes drawable methods on, such as para.replace.
|
|
10
|
+
# But we'd also like to be able to grab the corresponding display drawable and
|
|
11
11
|
# call some of *those* methods.
|
|
12
12
|
class CCProxy
|
|
13
13
|
attr_reader :display
|
|
@@ -16,7 +16,7 @@ module Scarpe::Test
|
|
|
16
16
|
def initialize(obj)
|
|
17
17
|
@obj = obj
|
|
18
18
|
# TODO: how to do this with Webview relay? Proxy object to send a message, maybe?
|
|
19
|
-
@display = ::Shoes::DisplayService.display_service.
|
|
19
|
+
@display = ::Shoes::DisplayService.display_service.query_display_drawable_for(obj.linkable_id)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def method_missing(method, ...)
|
|
@@ -33,6 +33,35 @@ module Scarpe::Test
|
|
|
33
33
|
def respond_to_missing?(method_name, include_private = false)
|
|
34
34
|
@obj.respond_to_missing?(method_name, include_private)
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
def trigger(event_name, *args)
|
|
38
|
+
name = "#{@obj.linkable_id}-#{event_name}"
|
|
39
|
+
Scarpe::Webview::DisplayService.instance.app.handle_callback(name, *args)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
[:click, :hover, :leave, :change].each do |ev|
|
|
43
|
+
define_method "trigger_#{ev}" do |*args|
|
|
44
|
+
trigger(ev, *args)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module DrawableFinders
|
|
50
|
+
# What to do about TextDrawables? Link, code, em, strong?
|
|
51
|
+
# Also, wait, what's up with span? What *is* that?
|
|
52
|
+
Shoes::Drawable.drawable_classes.each do |drawable_class|
|
|
53
|
+
finder_name = drawable_class.dsl_name
|
|
54
|
+
|
|
55
|
+
define_method(finder_name) do |*args|
|
|
56
|
+
app = Shoes::App.instance
|
|
57
|
+
|
|
58
|
+
drawables = app.find_drawables_by(drawable_class, *args)
|
|
59
|
+
raise Shoes::Errors::MultipleDrawablesFoundError, "Found more than one #{finder_name} matching #{args.inspect}!" if drawables.size > 1
|
|
60
|
+
raise Shoes::Errors::NoDrawablesFoundError, "Found no #{finder_name} matching #{args.inspect}!" if drawables.empty?
|
|
61
|
+
|
|
62
|
+
CCProxy.new(drawables[0])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
36
65
|
end
|
|
37
66
|
|
|
38
67
|
# This class defines the CatsCradle DSL. It also holds a "bag of fibers"
|
|
@@ -41,6 +70,7 @@ module Scarpe::Test
|
|
|
41
70
|
include Shoes::Log
|
|
42
71
|
include Scarpe::Test::EventedAssertions
|
|
43
72
|
include Scarpe::Test::Helpers
|
|
73
|
+
include Scarpe::Test::DrawableFinders
|
|
44
74
|
|
|
45
75
|
def self.instance
|
|
46
76
|
@instance ||= CCInstance.new
|
|
@@ -49,9 +79,7 @@ module Scarpe::Test
|
|
|
49
79
|
def initialize
|
|
50
80
|
log_init("CatsCradle")
|
|
51
81
|
|
|
52
|
-
|
|
53
|
-
@assertions_passed = 0
|
|
54
|
-
@assertions_failed = []
|
|
82
|
+
evented_assertions_initialize
|
|
55
83
|
|
|
56
84
|
@waiting_fibers = []
|
|
57
85
|
@event_promises = {}
|
|
@@ -74,7 +102,7 @@ module Scarpe::Test
|
|
|
74
102
|
when ::Scarpe::Promise
|
|
75
103
|
fiber_data[:promise] = result
|
|
76
104
|
else
|
|
77
|
-
raise "Unexpected object returned from Fiber#transfer for still-living Fiber! #{result.inspect}"
|
|
105
|
+
raise Scarpe::UnexpectedFiberTransferError, "Unexpected object returned from Fiber#transfer for still-living Fiber! #{result.inspect}"
|
|
78
106
|
end
|
|
79
107
|
end
|
|
80
108
|
|
|
@@ -90,7 +118,7 @@ module Scarpe::Test
|
|
|
90
118
|
end
|
|
91
119
|
|
|
92
120
|
# If we add "every" events, that's likely to complicate timing and event_promise handling.
|
|
93
|
-
EVENT_TYPES = [:next_heartbeat, :next_redraw]
|
|
121
|
+
EVENT_TYPES = [:init, :next_heartbeat, :next_redraw, :every_heartbeat, :every_redraw]
|
|
94
122
|
|
|
95
123
|
# This needs to be called after the basic display service objects exist
|
|
96
124
|
# and we can find the control interface.
|
|
@@ -109,6 +137,9 @@ module Scarpe::Test
|
|
|
109
137
|
p = @event_promises.delete(:next_heartbeat)
|
|
110
138
|
p&.fulfilled!
|
|
111
139
|
|
|
140
|
+
p = @event_promises.delete(:every_heartbeat)
|
|
141
|
+
p&.fulfilled!
|
|
142
|
+
|
|
112
143
|
# Give every ready fiber a chance to run once.
|
|
113
144
|
@manager_fiber.resume
|
|
114
145
|
end
|
|
@@ -119,6 +150,9 @@ module Scarpe::Test
|
|
|
119
150
|
p = @event_promises.delete(:next_redraw)
|
|
120
151
|
p&.fulfilled!
|
|
121
152
|
|
|
153
|
+
p = @event_promises.delete(:every_redraw)
|
|
154
|
+
p&.fulfilled!
|
|
155
|
+
|
|
122
156
|
# Give every ready fiber a chance to run once.
|
|
123
157
|
@manager_fiber.resume
|
|
124
158
|
end
|
|
@@ -130,7 +164,7 @@ module Scarpe::Test
|
|
|
130
164
|
end
|
|
131
165
|
|
|
132
166
|
def on_event(event, &block)
|
|
133
|
-
raise "Unknown event type: #{event.inspect}!" unless EVENT_TYPES.include?(event)
|
|
167
|
+
raise Scarpe::UnknownEventTypeError, "Unknown event type: #{event.inspect}!" unless EVENT_TYPES.include?(event)
|
|
134
168
|
|
|
135
169
|
f = Fiber.new do
|
|
136
170
|
CCInstance.instance.instance_eval(&block)
|
|
@@ -138,28 +172,27 @@ module Scarpe::Test
|
|
|
138
172
|
@waiting_fibers << { promise: event_promise(event), fiber: f }
|
|
139
173
|
end
|
|
140
174
|
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
175
|
+
def proxy_for(shoes_drawable)
|
|
176
|
+
CCProxy.new(shoes_drawable)
|
|
155
177
|
end
|
|
156
178
|
|
|
157
|
-
def
|
|
158
|
-
|
|
179
|
+
def die_after(time)
|
|
180
|
+
t_start = Time.now
|
|
181
|
+
@die_after = [t_start, time]
|
|
182
|
+
|
|
183
|
+
@wrangler.periodic_code("scarpeTestTimeout") do |*_args|
|
|
184
|
+
t_delta = (Time.now - t_start).to_f
|
|
185
|
+
if t_delta > time
|
|
186
|
+
@did_time_out = true
|
|
187
|
+
@log.warn("die_after - timed out after #{t_delta.inspect} (threshold: #{time.inspect})")
|
|
188
|
+
return_results(false, "Timed out!")
|
|
189
|
+
::Shoes::DisplayService.dispatch_event("destroy", nil)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
159
192
|
end
|
|
160
193
|
|
|
161
194
|
def wait(promise)
|
|
162
|
-
raise("Must supply a promise to wait!") unless promise.is_a?(::Scarpe::Promise)
|
|
195
|
+
raise(Scarpe::InvalidPromiseError, "Must supply a promise to wait!") unless promise.is_a?(::Scarpe::Promise)
|
|
163
196
|
|
|
164
197
|
# Wait until this promise is complete before running again
|
|
165
198
|
@manager_fiber.transfer(promise)
|
|
@@ -181,42 +214,17 @@ module Scarpe::Test
|
|
|
181
214
|
@manager_fiber.transfer(js_promise)
|
|
182
215
|
end
|
|
183
216
|
|
|
184
|
-
def
|
|
185
|
-
|
|
186
|
-
@assertion_data << [value ? true : false, msg]
|
|
187
|
-
if value
|
|
188
|
-
@assertions_passed += 1
|
|
189
|
-
else
|
|
190
|
-
@assertions_failed << msg
|
|
191
|
-
end
|
|
217
|
+
def query_js_promise(js_code, timeout: 1.0)
|
|
218
|
+
@wrangler.eval_js_async(js_code, timeout:)
|
|
192
219
|
end
|
|
193
220
|
|
|
194
|
-
def
|
|
195
|
-
|
|
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
|
|
221
|
+
def test_finished(return_results: true)
|
|
222
|
+
return_assertion_data if return_results
|
|
207
223
|
|
|
208
|
-
|
|
209
|
-
{}
|
|
224
|
+
::Shoes::DisplayService.dispatch_event("destroy", nil)
|
|
210
225
|
end
|
|
211
226
|
|
|
212
|
-
def
|
|
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
|
|
227
|
+
def test_finished_no_results
|
|
220
228
|
::Shoes::DisplayService.dispatch_event("destroy", nil)
|
|
221
229
|
end
|
|
222
230
|
end
|
|
@@ -235,15 +243,29 @@ module Scarpe::Test
|
|
|
235
243
|
#
|
|
236
244
|
# Ruby Fiber basic docs: https://ruby-doc.org/core-3.0.0/Fiber.html
|
|
237
245
|
#
|
|
238
|
-
# This module is mixed into
|
|
246
|
+
# This module is mixed into a test object if we're running CatsCradle-based tests
|
|
239
247
|
module CatsCradle
|
|
248
|
+
attr_reader :cc_instance
|
|
249
|
+
|
|
240
250
|
def event_init
|
|
241
|
-
@cc_instance
|
|
251
|
+
@cc_instance ||= CCInstance.instance
|
|
242
252
|
@cc_instance.event_init
|
|
243
253
|
end
|
|
244
254
|
|
|
245
255
|
def on_heartbeat(&block)
|
|
246
256
|
@cc_instance.on_event(:next_heartbeat, &block)
|
|
247
257
|
end
|
|
258
|
+
|
|
259
|
+
def on_every_heartbeat(&block)
|
|
260
|
+
@cc_instance.on_event(:every_heartbeat, &block)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def on_init(&block)
|
|
264
|
+
@cc_instance.on_event(:init, &block)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def on_next_redraw(&block)
|
|
268
|
+
@cc_instance.on_event(:next_redraw, &block)
|
|
269
|
+
end
|
|
248
270
|
end
|
|
249
271
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Scarpe
|
|
4
|
+
class UnknownShoesEventAPIError < Scarpe::Error; end
|
|
5
|
+
|
|
6
|
+
class UnknownShapeCommandError < Scarpe::Error; end
|
|
7
|
+
|
|
8
|
+
class UnknownBuiltinCommandError < Scarpe::Error; end
|
|
9
|
+
|
|
10
|
+
class UnknownEventTypeError < Scarpe::Error; end
|
|
11
|
+
|
|
12
|
+
class UnexpectedFiberTransferError < Scarpe::Error; end
|
|
13
|
+
|
|
14
|
+
class InvalidPromiseError < Scarpe::Error; end
|
|
15
|
+
|
|
16
|
+
class MissingAppError < Scarpe::Error; end
|
|
17
|
+
|
|
18
|
+
class MissingDocRootError < Scarpe::Error; end
|
|
19
|
+
|
|
20
|
+
class MissingWranglerError < Scarpe::Error; end
|
|
21
|
+
|
|
22
|
+
class IllegalSubscribeEventError < Scarpe::Error; end
|
|
23
|
+
|
|
24
|
+
class IllegalDispatchEventError < Scarpe::Error; end
|
|
25
|
+
|
|
26
|
+
class MissingBlockError < Scarpe::Error; end
|
|
27
|
+
|
|
28
|
+
class DuplicateCallbackError < Scarpe::Error; end
|
|
29
|
+
|
|
30
|
+
class JSBindingError < Scarpe::Error; end
|
|
31
|
+
|
|
32
|
+
class JSInitError < Scarpe::Error; end
|
|
33
|
+
|
|
34
|
+
class PeriodicHandlerSetupError < Scarpe::Error; end
|
|
35
|
+
|
|
36
|
+
class WebWranglerNotRunningError < Scarpe::Error; end
|
|
37
|
+
|
|
38
|
+
class NonexistentEvalResultError < Scarpe::Error; end
|
|
39
|
+
|
|
40
|
+
class JSRedrawError < Scarpe::Error; end
|
|
41
|
+
|
|
42
|
+
class ConnectionError < Scarpe::Error; end
|
|
43
|
+
|
|
44
|
+
class DatagramSendError < Scarpe::Error; end
|
|
45
|
+
|
|
46
|
+
class InvalidOperationError < Scarpe::Error; end
|
|
47
|
+
|
|
48
|
+
class MissingAttributeError < Scarpe::Error; end
|
|
49
|
+
|
|
50
|
+
# This error indicates a problem when running ConfirmedEval
|
|
51
|
+
class JSEvalError < Scarpe::Error
|
|
52
|
+
def initialize(data)
|
|
53
|
+
@data = data
|
|
54
|
+
super(data[:msg] || (self.class.name + "!"))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# An error running the supplied JS code string in confirmed_eval
|
|
59
|
+
class JSRuntimeError < JSEvalError; end
|
|
60
|
+
|
|
61
|
+
# The code timed out for some reason
|
|
62
|
+
class JSTimeoutError < JSEvalError; end
|
|
63
|
+
|
|
64
|
+
# We got weird or nonsensical results that seem like an error on WebWrangler's part
|
|
65
|
+
class JSInternalError < JSEvalError; end
|
|
66
|
+
|
|
67
|
+
# An error occurred which would normally be handled by shutting down the app
|
|
68
|
+
class AppShutdownError < Scarpe::Error; end
|
|
69
|
+
|
|
70
|
+
class InvalidClassError < Scarpe::Error; end
|
|
71
|
+
|
|
72
|
+
class MissingClassError < Scarpe::Error; end
|
|
73
|
+
|
|
74
|
+
class EmptyPageNotSetError < Scarpe::Error; end
|
|
75
|
+
|
|
76
|
+
class BadDisplayClassType < Scarpe::Error; end
|
|
77
|
+
end
|
|
@@ -4,6 +4,8 @@ require "tempfile"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "fileutils"
|
|
6
6
|
|
|
7
|
+
require "scarpe/components/unit_test_helpers"
|
|
8
|
+
|
|
7
9
|
module Scarpe::Test; end
|
|
8
10
|
|
|
9
11
|
# We need a separate assertion system for the kind of Scarpe
|
|
@@ -11,6 +13,46 @@ module Scarpe::Test; end
|
|
|
11
13
|
# logic in the subprocess, and then run Minitest in the parent.
|
|
12
14
|
# It's an unusual setup.
|
|
13
15
|
module Scarpe::Test::EventedAssertions
|
|
16
|
+
include Scarpe::Test::HTMLAssertions
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def evented_assertions_initialize
|
|
21
|
+
@assertion_data = []
|
|
22
|
+
@assertions_passed = 0
|
|
23
|
+
@assertions_failed = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
public
|
|
27
|
+
|
|
28
|
+
def assert(value, msg = nil)
|
|
29
|
+
msg ||= "Assertion #{value ? "succeeded" : "failed"}"
|
|
30
|
+
@assertion_data << [(value ? true : false), msg]
|
|
31
|
+
if value
|
|
32
|
+
@assertions_passed += 1
|
|
33
|
+
else
|
|
34
|
+
@assertions_failed << msg
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def assert_equal(expected, actual, msg = nil)
|
|
39
|
+
msg ||= "Expected #{actual.inspect} to equal #{expected.inspect}!"
|
|
40
|
+
assert actual == expected, msg
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def assertion_data_as_a_struct
|
|
44
|
+
{
|
|
45
|
+
still_pending: 0,
|
|
46
|
+
succeeded: @assertions_passed,
|
|
47
|
+
failed: @assertions_failed.size,
|
|
48
|
+
failures: @assertions_failed,
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_metadata
|
|
53
|
+
{}
|
|
54
|
+
end
|
|
55
|
+
|
|
14
56
|
# Assert that `text` includes `subtext`.
|
|
15
57
|
#
|
|
16
58
|
# @param text [String] the longer text
|
|
@@ -33,27 +75,18 @@ module Scarpe::Test::EventedAssertions
|
|
|
33
75
|
assert !text.include?(subtext), msg
|
|
34
76
|
end
|
|
35
77
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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)
|
|
78
|
+
def return_assertion_data
|
|
79
|
+
if !@assertions_failed.empty?
|
|
80
|
+
return_results(false, "Assertions failed", assertion_data_as_a_struct)
|
|
81
|
+
elsif @assertions_passed > 0
|
|
82
|
+
return_results(true, "All assertions passed", assertion_data_as_a_struct)
|
|
83
|
+
else
|
|
84
|
+
return_results(true, "Test finished successfully")
|
|
50
85
|
end
|
|
51
|
-
|
|
52
|
-
assert_equal expected_html, actual_html
|
|
53
86
|
end
|
|
54
87
|
|
|
55
88
|
# This does a final return of results. If it gets called
|
|
56
|
-
# multiple times, the test fails because that's not allowed.
|
|
89
|
+
# multiple times with different results, the test fails because that's not allowed.
|
|
57
90
|
#
|
|
58
91
|
# @param result_bool [Boolean] true if the results are success, false if failure
|
|
59
92
|
# @param msg [String] the message included with the results
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest"
|
|
4
|
+
require "scarpe/cats_cradle"
|
|
5
|
+
require "scarpe/components/string_helpers"
|
|
6
|
+
|
|
7
|
+
require "scarpe/components/unit_test_helpers"
|
|
8
|
+
|
|
9
|
+
# Test framework code to allow Scarpe to execute Shoes-Spec test code.
|
|
10
|
+
# This will run inside the exe/scarpe child process, then send
|
|
11
|
+
# results back to the parent Minitest process.
|
|
12
|
+
|
|
13
|
+
module Scarpe::Test
|
|
14
|
+
# Is it at all reasonable to define more than one test to run in the same Shoes run? Probably not.
|
|
15
|
+
# They'll leave in-memory residue.
|
|
16
|
+
def self.run_shoes_spec_test_code(code, class_name: nil, test_name: nil)
|
|
17
|
+
if @shoes_spec_init
|
|
18
|
+
raise Shoes::Errors::MultipleShoesSpecRunsError, "Scarpe-Webview can only run a single Shoes spec per process!"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@shoes_spec_init = true
|
|
22
|
+
|
|
23
|
+
require "scarpe/components/minitest_export_reporter"
|
|
24
|
+
Minitest::Reporters::ShoesExportReporter.activate!
|
|
25
|
+
|
|
26
|
+
class_name ||= ENV["SHOES_MINITEST_CLASS_NAME"] || "TestShoesSpecCode"
|
|
27
|
+
test_name ||= ENV["SHOES_MINITEST_METHOD_NAME"] || "test_shoes_spec"
|
|
28
|
+
|
|
29
|
+
require_relative "cats_cradle"
|
|
30
|
+
|
|
31
|
+
# We want Minitest assertions available in the test code.
|
|
32
|
+
# But this will normally run in a subprocess. So we need
|
|
33
|
+
# to run Minitest tests and then export the results.
|
|
34
|
+
|
|
35
|
+
# We create a test object based on CatsCradle, which will
|
|
36
|
+
# run the test as straight-line code, wait for appropriate
|
|
37
|
+
# events and generally make things well-behaved. But the
|
|
38
|
+
# test DSL isn't CatsCradle. It's based on Minitest and
|
|
39
|
+
# ShoesSpecTest (see below).
|
|
40
|
+
#
|
|
41
|
+
# Note that that means that using CatsCradle to "bounce"
|
|
42
|
+
# control back and forth for evented tricks isn't really
|
|
43
|
+
# an option. We may need to revisit all of this later...
|
|
44
|
+
test_obj = Object.new
|
|
45
|
+
class << test_obj
|
|
46
|
+
include Scarpe::Test::CatsCradle
|
|
47
|
+
end
|
|
48
|
+
Scarpe::ShoesSpecTest.test_obj = test_obj
|
|
49
|
+
test_obj.instance_eval do
|
|
50
|
+
event_init
|
|
51
|
+
|
|
52
|
+
on_heartbeat do
|
|
53
|
+
Minitest.run ARGV
|
|
54
|
+
|
|
55
|
+
test_finished_no_results
|
|
56
|
+
Scarpe::ShoesSpecTest.test_obj = nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test_class = Class.new(Scarpe::ShoesSpecTest)
|
|
61
|
+
Object.const_set(Scarpe::Components::StringHelpers.camelize(class_name), test_class)
|
|
62
|
+
test_name = "test_" + test_name unless test_name.start_with?("test_")
|
|
63
|
+
test_class.define_method(test_name) do
|
|
64
|
+
eval(code)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# This is based on the CatsCradle proxies initially, but will diverge over time
|
|
70
|
+
class Scarpe::ShoesSpecProxy
|
|
71
|
+
attr_reader :obj
|
|
72
|
+
attr_reader :linkable_id
|
|
73
|
+
attr_reader :display
|
|
74
|
+
|
|
75
|
+
JS_EVENTS = [:click, :hover, :leave, :change]
|
|
76
|
+
|
|
77
|
+
def initialize(obj)
|
|
78
|
+
@obj = obj
|
|
79
|
+
@linkable_id = obj.linkable_id
|
|
80
|
+
@display = ::Shoes::DisplayService.display_service.query_display_drawable_for(obj.linkable_id)
|
|
81
|
+
|
|
82
|
+
unless @display
|
|
83
|
+
raise "Can't find display widget for #{obj.inspect}!"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def method_missing(method, ...)
|
|
88
|
+
if @obj.respond_to?(method)
|
|
89
|
+
self.singleton_class.define_method(method) do |*args, **kwargs, &block|
|
|
90
|
+
@obj.send(method, *args, **kwargs, &block)
|
|
91
|
+
end
|
|
92
|
+
send(method, ...)
|
|
93
|
+
else
|
|
94
|
+
super # raise an exception
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def trigger(event_name, *args)
|
|
99
|
+
name = "#{@linkable_id}-#{event_name}"
|
|
100
|
+
Scarpe::Webview::DisplayService.instance.app.handle_callback(name, *args)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
JS_EVENTS.each do |ev|
|
|
104
|
+
define_method "trigger_#{ev}" do |*args|
|
|
105
|
+
trigger(ev, *args)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
110
|
+
@obj.respond_to_missing?(method_name, include_private)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# When running ShoesSpec tests, we create a parent class for all of them
|
|
115
|
+
# with the appropriate convenience methods and accessors.
|
|
116
|
+
class Scarpe::ShoesSpecTest < Minitest::Test
|
|
117
|
+
include Scarpe::Test::HTMLAssertions
|
|
118
|
+
|
|
119
|
+
class << self
|
|
120
|
+
attr_accessor :test_obj
|
|
121
|
+
end
|
|
122
|
+
Shoes::Drawable.drawable_classes.each do |drawable_class|
|
|
123
|
+
finder_name = drawable_class.dsl_name
|
|
124
|
+
|
|
125
|
+
define_method(finder_name) do |*args|
|
|
126
|
+
app = Shoes::App.instance
|
|
127
|
+
|
|
128
|
+
drawables = app.find_drawables_by(drawable_class, *args)
|
|
129
|
+
raise Shoes::Errors::MultipleDrawablesFoundError, "Found more than one #{finder_name} matching #{args.inspect}!" if drawables.size > 1
|
|
130
|
+
raise Shoes::Errors::NoDrawablesFoundError, "Found no #{finder_name} matching #{args.inspect}!" if drawables.empty?
|
|
131
|
+
|
|
132
|
+
Scarpe::ShoesSpecProxy.new(drawables[0])
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
def drawable(*specs)
|
|
136
|
+
drawables = app.find_drawables_by(*specs)
|
|
137
|
+
raise Scarpe::MultipleDrawablesFoundError, "Found more than one #{finder_name} matching #{args.inspect}!" if drawables.size > 1
|
|
138
|
+
raise Scarpe::NoDrawablesFoundError, "Found no #{finder_name} matching #{args.inspect}!" if drawables.empty?
|
|
139
|
+
|
|
140
|
+
Scarpe::ShoesSpecProxy.new(drawables[0])
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def catscradle_dsl(&block)
|
|
144
|
+
Scarpe::Test::CCInstance.instance.instance_eval(&block)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def dom_html
|
|
148
|
+
catscradle_dsl do
|
|
149
|
+
wait fully_updated
|
|
150
|
+
dom_html
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# This isn't working. Neither is calling die_after. Are the other fibers not
|
|
155
|
+
# running or something like that? Should run a test from the command line
|
|
156
|
+
# and see what's happening... Or check logfiles?
|
|
157
|
+
def timeout(t_timeout = 5.0, exit_code: -1)
|
|
158
|
+
catscradle_dsl do
|
|
159
|
+
t0 = Time.now
|
|
160
|
+
on_event(:every_heartbeat) do
|
|
161
|
+
if Time.now - t0 >= t_timeout
|
|
162
|
+
if exit_code == 0
|
|
163
|
+
@log.info "Timed out after #{t_timeout} seconds!"
|
|
164
|
+
else
|
|
165
|
+
@log.error "Timed out after #{t_timeout} seconds!"
|
|
166
|
+
end
|
|
167
|
+
exit exit_code
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def exit_on_first_heartbeat(exit_code: 0)
|
|
174
|
+
catscradle_dsl do
|
|
175
|
+
on_event(:next_heartbeat) do
|
|
176
|
+
@log.info "Exiting on first heartbeat (exit code #{exit_code})"
|
|
177
|
+
exit exit_code
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
data/lib/scarpe/version.rb
CHANGED