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.
Files changed (223) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/CHANGELOG.md +16 -2
  4. data/Gemfile.lock +7 -3
  5. data/README.md +24 -8
  6. data/Rakefile +1 -1
  7. data/examples/animate.rb +20 -0
  8. data/examples/arrow.rb +10 -0
  9. data/examples/btn_tooltip.rb +7 -0
  10. data/examples/button_style_changed.rb +7 -0
  11. data/examples/button_styles_default.rb +6 -0
  12. data/examples/gen.rb +8 -8
  13. data/examples/highlander.rb +3 -3
  14. data/examples/legacy/README.md +6 -0
  15. data/examples/legacy/not_checked/shoes-contrib/basic/shoes-notes.rb +1 -1
  16. data/examples/legacy/not_checked/simple/anim-shapes.rb +1 -1
  17. data/examples/legacy/not_checked/speedometer_app.rb +55 -0
  18. data/examples/legacy/working/simple/image-icon.rb +3 -0
  19. data/examples/legacy/{not_checked → working}/simple/image.rb +1 -1
  20. data/examples/list_box_choose.rb +17 -0
  21. data/examples/local_assets/local_file_server.rb +82 -0
  22. data/examples/local_assets/sample.gif +0 -0
  23. data/examples/local_assets/sample.mp4 +0 -0
  24. data/examples/local_fonts.rb +2 -2
  25. data/examples/local_images.rb +2 -3
  26. data/examples/para/para_text.rb +14 -0
  27. data/examples/progress.rb +31 -0
  28. data/examples/radio/radio_groups.rb +2 -2
  29. data/examples/rect.rb +4 -0
  30. data/examples/rotate_shapes.rb +17 -0
  31. data/examples/simpler-menu.rb +21 -0
  32. data/exe/scarpe +2 -1
  33. data/lacci/Gemfile +2 -0
  34. data/lacci/Gemfile.lock +8 -1
  35. data/lacci/lacci.gemspec +1 -1
  36. data/lacci/lib/lacci/scarpe_cli.rb +2 -1
  37. data/lacci/lib/lacci/scarpe_core.rb +2 -1
  38. data/lacci/lib/lacci/version.rb +1 -1
  39. data/lacci/lib/scarpe/niente/app.rb +23 -0
  40. data/lacci/lib/scarpe/niente/display_service.rb +62 -0
  41. data/lacci/lib/scarpe/niente/drawable.rb +57 -0
  42. data/lacci/lib/scarpe/niente/logger.rb +29 -0
  43. data/lacci/lib/scarpe/niente/shoes_spec.rb +87 -0
  44. data/lacci/lib/scarpe/niente.rb +20 -0
  45. data/lacci/lib/shoes/app.rb +88 -43
  46. data/lacci/lib/shoes/background.rb +2 -2
  47. data/lacci/lib/shoes/border.rb +2 -2
  48. data/lacci/lib/shoes/builtins.rb +63 -0
  49. data/lacci/lib/shoes/changelog.rb +52 -0
  50. data/lacci/lib/shoes/colors.rb +3 -1
  51. data/lacci/lib/shoes/constants.rb +19 -1
  52. data/lacci/lib/shoes/display_service.rb +39 -16
  53. data/lacci/lib/shoes/download.rb +2 -2
  54. data/lacci/lib/shoes/drawable.rb +380 -0
  55. data/lacci/lib/shoes/drawables/arc.rb +49 -0
  56. data/lacci/lib/shoes/drawables/arrow.rb +41 -0
  57. data/lacci/lib/shoes/drawables/button.rb +73 -0
  58. data/lacci/lib/shoes/{widgets → drawables}/check.rb +5 -4
  59. data/lacci/lib/shoes/{widgets → drawables}/document_root.rb +3 -3
  60. data/lacci/lib/shoes/{widgets → drawables}/edit_box.rb +6 -6
  61. data/lacci/lib/shoes/{widgets → drawables}/edit_line.rb +6 -6
  62. data/lacci/lib/shoes/{widgets → drawables}/flow.rb +6 -6
  63. data/lacci/lib/shoes/{widgets → drawables}/image.rb +6 -6
  64. data/lacci/lib/shoes/{widgets → drawables}/line.rb +7 -5
  65. data/lacci/lib/shoes/drawables/link.rb +34 -0
  66. data/lacci/lib/shoes/drawables/list_box.rb +56 -0
  67. data/lacci/lib/shoes/drawables/para.rb +118 -0
  68. data/lacci/lib/shoes/drawables/progress.rb +14 -0
  69. data/lacci/lib/shoes/drawables/radio.rb +33 -0
  70. data/lacci/lib/shoes/drawables/rect.rb +17 -0
  71. data/lacci/lib/shoes/{widgets → drawables}/shape.rb +6 -7
  72. data/lacci/lib/shoes/{widgets → drawables}/slot.rb +32 -20
  73. data/lacci/lib/shoes/{widgets → drawables}/span.rb +8 -7
  74. data/lacci/lib/shoes/{widgets → drawables}/stack.rb +6 -4
  75. data/lacci/lib/shoes/drawables/star.rb +50 -0
  76. data/lacci/lib/shoes/drawables/subscription_item.rb +93 -0
  77. data/lacci/lib/shoes/drawables/text_drawable.rb +63 -0
  78. data/lacci/lib/shoes/drawables/video.rb +16 -0
  79. data/lacci/lib/shoes/drawables/widget.rb +69 -0
  80. data/lacci/lib/shoes/drawables.rb +31 -0
  81. data/lacci/lib/shoes/errors.rb +28 -0
  82. data/lacci/lib/shoes/log.rb +2 -2
  83. data/lacci/lib/shoes/ruby_extensions.rb +15 -0
  84. data/lacci/lib/shoes/spacing.rb +2 -2
  85. data/lacci/lib/shoes-spec.rb +93 -0
  86. data/lacci/lib/shoes.rb +27 -7
  87. data/lacci/test/test_helper.rb +54 -0
  88. data/lacci/test/test_lacci.rb +12 -3
  89. data/lacci/test/test_shoes_errors.rb +49 -0
  90. data/lib/scarpe/cats_cradle.rb +81 -59
  91. data/lib/scarpe/errors.rb +77 -0
  92. data/lib/scarpe/evented_assertions.rb +50 -17
  93. data/lib/scarpe/shoes_spec.rb +181 -0
  94. data/lib/scarpe/version.rb +2 -2
  95. data/lib/scarpe/wv/app.rb +20 -20
  96. data/lib/scarpe/wv/arc.rb +4 -47
  97. data/lib/scarpe/wv/arrow.rb +9 -0
  98. data/lib/scarpe/wv/button.rb +7 -35
  99. data/lib/scarpe/wv/check.rb +3 -5
  100. data/lib/scarpe/wv/control_interface.rb +18 -20
  101. data/lib/scarpe/wv/document_root.rb +81 -4
  102. data/lib/scarpe/wv/{widget.rb → drawable.rb} +66 -43
  103. data/lib/scarpe/wv/edit_box.rb +4 -17
  104. data/lib/scarpe/wv/edit_line.rb +4 -18
  105. data/lib/scarpe/wv/flow.rb +2 -18
  106. data/lib/scarpe/wv/image.rb +8 -28
  107. data/lib/scarpe/wv/line.rb +3 -25
  108. data/lib/scarpe/wv/link.rb +3 -16
  109. data/lib/scarpe/wv/list_box.rb +6 -29
  110. data/lib/scarpe/wv/para.rb +11 -30
  111. data/lib/scarpe/wv/progress.rb +19 -0
  112. data/lib/scarpe/wv/radio.rb +9 -10
  113. data/lib/scarpe/wv/rect.rb +13 -0
  114. data/lib/scarpe/wv/shape.rb +3 -8
  115. data/lib/scarpe/wv/slot.rb +8 -25
  116. data/lib/scarpe/wv/span.rb +3 -27
  117. data/lib/scarpe/wv/stack.rb +2 -18
  118. data/lib/scarpe/wv/star.rb +3 -53
  119. data/lib/scarpe/wv/subscription_item.rb +38 -4
  120. data/lib/scarpe/wv/text_drawable.rb +32 -0
  121. data/lib/scarpe/wv/video.rb +15 -15
  122. data/lib/scarpe/wv/web_wrangler.rb +299 -329
  123. data/lib/scarpe/wv/webview_local_display.rb +48 -33
  124. data/lib/scarpe/wv/webview_relay_display.rb +12 -12
  125. data/lib/scarpe/wv/webview_relay_util.rb +7 -10
  126. data/lib/scarpe/wv/wv_display_worker.rb +2 -2
  127. data/lib/scarpe/wv.rb +45 -12
  128. data/lib/scarpe/wv_local.rb +1 -1
  129. data/lib/scarpe/wv_relay.rb +1 -1
  130. data/lib/scarpe.rb +1 -0
  131. data/logger/debug_web_wrangler.json +1 -1
  132. data/logger/scarpe_wv_test.json +1 -1
  133. data/scarpe-components/Gemfile.lock +86 -0
  134. data/scarpe-components/lib/scarpe/components/base64.rb +3 -7
  135. data/scarpe-components/lib/scarpe/components/calzini/alert.rb +49 -0
  136. data/scarpe-components/lib/scarpe/components/calzini/art_widgets.rb +203 -0
  137. data/scarpe-components/lib/scarpe/components/calzini/button.rb +39 -0
  138. data/scarpe-components/lib/scarpe/components/calzini/misc.rb +146 -0
  139. data/scarpe-components/lib/scarpe/components/calzini/para.rb +35 -0
  140. data/scarpe-components/lib/scarpe/components/calzini/slots.rb +155 -0
  141. data/scarpe-components/lib/scarpe/components/calzini/text_widgets.rb +65 -0
  142. data/scarpe-components/lib/scarpe/components/calzini.rb +149 -0
  143. data/scarpe-components/lib/scarpe/components/errors.rb +20 -0
  144. data/scarpe-components/lib/scarpe/components/file_helpers.rb +1 -0
  145. data/scarpe-components/lib/scarpe/components/html.rb +131 -0
  146. data/scarpe-components/lib/scarpe/components/minitest_export_reporter.rb +75 -0
  147. data/scarpe-components/lib/scarpe/components/minitest_import_runnable.rb +98 -0
  148. data/scarpe-components/lib/scarpe/components/minitest_result.rb +86 -0
  149. data/scarpe-components/lib/scarpe/components/modular_logger.rb +5 -5
  150. data/scarpe-components/lib/scarpe/components/print_logger.rb +9 -5
  151. data/scarpe-components/lib/scarpe/components/promises.rb +14 -14
  152. data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +36 -17
  153. data/scarpe-components/lib/scarpe/components/string_helpers.rb +10 -0
  154. data/scarpe-components/lib/scarpe/components/tiranti.rb +225 -0
  155. data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +45 -5
  156. data/scarpe-components/lib/scarpe/components/version.rb +2 -2
  157. data/scarpe-components/test/calzini/test_calzini_alert.rb +30 -0
  158. data/scarpe-components/test/calzini/test_calzini_art_drawables.rb +105 -0
  159. data/scarpe-components/test/calzini/test_calzini_button.rb +52 -0
  160. data/scarpe-components/test/calzini/test_calzini_misc.rb +115 -0
  161. data/scarpe-components/test/calzini/test_calzini_para.rb +37 -0
  162. data/scarpe-components/test/calzini/test_calzini_slots.rb +130 -0
  163. data/scarpe-components/test/calzini/test_calzini_text_drawables.rb +41 -0
  164. data/scarpe-components/test/mtr_data/exception.json +1 -0
  165. data/scarpe-components/test/mtr_data/fail_with_message.json +1 -0
  166. data/scarpe-components/test/mtr_data/skipped_no_message.json +1 -0
  167. data/scarpe-components/test/mtr_data/skipped_w_msg.json +1 -0
  168. data/scarpe-components/test/mtr_data/succeed_2_asserts.json +1 -0
  169. data/scarpe-components/test/test_dimensions.rb +26 -0
  170. data/scarpe-components/test/test_helper.rb +20 -0
  171. data/scarpe-components/test/test_html.rb +65 -0
  172. data/scarpe-components/test/test_minitest_result.rb +61 -0
  173. data/scarpe-components/test/test_promises.rb +5 -4
  174. data/scarpe-components/test/test_segmented_app_files.rb +8 -6
  175. data/scarpegen.rb +14 -14
  176. data/sig/scarpe.rbs +1 -1
  177. data/templates/basic_class_template.erb +13 -14
  178. data/templates/class_template_with_event_bind.erb +4 -4
  179. data/templates/class_template_with_shapes.erb +8 -17
  180. data/templates/example_template.erb +1 -1
  181. data/templates/module_template.erb +4 -4
  182. data/templates/webview_template.erb +3 -2
  183. metadata +113 -55
  184. data/examples/legacy/not_checked/shoes-contrib/elements/image-icon.rb +0 -3
  185. data/lacci/lib/shoes/widget.rb +0 -218
  186. data/lacci/lib/shoes/widgets/alert.rb +0 -19
  187. data/lacci/lib/shoes/widgets/arc.rb +0 -51
  188. data/lacci/lib/shoes/widgets/button.rb +0 -35
  189. data/lacci/lib/shoes/widgets/font.rb +0 -14
  190. data/lacci/lib/shoes/widgets/link.rb +0 -25
  191. data/lacci/lib/shoes/widgets/list_box.rb +0 -25
  192. data/lacci/lib/shoes/widgets/para.rb +0 -68
  193. data/lacci/lib/shoes/widgets/radio.rb +0 -35
  194. data/lacci/lib/shoes/widgets/star.rb +0 -44
  195. data/lacci/lib/shoes/widgets/subscription_item.rb +0 -60
  196. data/lacci/lib/shoes/widgets/text_widget.rb +0 -51
  197. data/lacci/lib/shoes/widgets/video.rb +0 -15
  198. data/lacci/lib/shoes/widgets.rb +0 -29
  199. data/lib/scarpe/wv/alert.rb +0 -66
  200. data/lib/scarpe/wv/background.rb +0 -27
  201. data/lib/scarpe/wv/border.rb +0 -24
  202. data/lib/scarpe/wv/control_interface_test.rb +0 -238
  203. data/lib/scarpe/wv/dimensions.rb +0 -22
  204. data/lib/scarpe/wv/font.rb +0 -36
  205. data/lib/scarpe/wv/html.rb +0 -108
  206. data/lib/scarpe/wv/spacing.rb +0 -41
  207. data/lib/scarpe/wv/text_widget.rb +0 -30
  208. /data/examples/legacy/not_checked/{expert → shoes-contrib/basic}/definr.rb +0 -0
  209. /data/examples/legacy/not_checked/{expert → shoes-contrib/basic}/funnies.rb +0 -0
  210. /data/examples/legacy/not_checked/shoes-contrib/{elements → basic}/list_box-select-class.rb +0 -0
  211. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/basic-edit-box.rb +0 -0
  212. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/basic-fps.rb +0 -0
  213. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/border-cat.rb +0 -0
  214. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/check-mate.rb +0 -0
  215. /data/examples/legacy/{not_checked/shoes-contrib/manipulation → working/simple}/clear-slot.rb +0 -0
  216. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/clock.rb +0 -0
  217. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/gradient-shoes.rb +0 -0
  218. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/list_box-shape-report.rb +0 -0
  219. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/list_box.rb +0 -0
  220. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/phat-button.rb +0 -0
  221. /data/examples/legacy/{not_checked/shoes-contrib → working}/simple/simple-calc.rb +0 -0
  222. /data/examples/legacy/{not_checked/shoes-contrib/position → working/simple}/stack-width.rb +0 -0
  223. /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
@@ -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 widget methods on, such as para.replace.
10
- # But we'd also like to be able to grab the corresponding display widget and
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.query_display_widget_for(obj.linkable_id)
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
- @assertion_data = []
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
- # 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
175
+ def proxy_for(shoes_drawable)
176
+ CCProxy.new(shoes_drawable)
155
177
  end
156
178
 
157
- def proxy_for(shoes_widget)
158
- CCProxy.new(shoes_widget)
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 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
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 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
221
+ def test_finished(return_results: true)
222
+ return_assertion_data if return_results
207
223
 
208
- def test_metadata
209
- {}
224
+ ::Shoes::DisplayService.dispatch_event("destroy", nil)
210
225
  end
211
226
 
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
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 Shoes::App if we're running CatsCradle-based tests
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 = CCInstance.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
- # 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)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- VERSION = "0.2.2"
3
+ module Scarpe
4
+ VERSION = "0.3.0"
5
5
  end