scarpe 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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