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
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe; end
3
+ module Scarpe; end
4
4
  module Scarpe::Components; end
5
- class Scarpe
5
+ module Scarpe
6
6
  # Scarpe::Promise is a promises library, but one with no form of built-in
7
7
  # concurrency. Instead, promise callbacks are executed synchronously.
8
8
  # Even execution is usually synchronous, but can also be handled manually
@@ -198,7 +198,7 @@ class Scarpe
198
198
  case @state
199
199
  when :fulfilled
200
200
  # Should this be a no-op instead?
201
- raise "Registering an executor on an already fulfilled promise means it will never run!"
201
+ raise Scarpe::NoOperationError, "Registering an executor on an already fulfilled promise means it will never run!"
202
202
  when :rejected
203
203
  return
204
204
  when :unscheduled
@@ -207,7 +207,7 @@ class Scarpe
207
207
  @executor = block
208
208
  call_executor
209
209
  else
210
- raise "Internal error, illegal state!"
210
+ raise Scarpe::InternalError, "Internal error, illegal state!"
211
211
  end
212
212
 
213
213
  self
@@ -222,16 +222,16 @@ class Scarpe
222
222
 
223
223
  # First, filter out illegal input
224
224
  unless PROMISE_STATES.include?(old_state)
225
- raise "Internal Promise error! Internal state was #{old_state.inspect}! Legal states: #{PROMISE_STATES.inspect}"
225
+ raise Scarpe::InternalError, "Internal Promise error! Internal state was #{old_state.inspect}! Legal states: #{PROMISE_STATES.inspect}"
226
226
  end
227
227
 
228
228
  unless PROMISE_STATES.include?(new_state)
229
- raise "Internal Promise error! Internal state was set to #{new_state.inspect}! " +
229
+ raise Scarpe::InternalError, "Internal Promise error! Internal state was set to #{new_state.inspect}! " +
230
230
  "Legal states: #{PROMISE_STATES.inspect}"
231
231
  end
232
232
 
233
233
  if new_state != :fulfilled && new_state != :rejected && !value_or_reason.nil?
234
- raise "Internal promise error! Non-completed state transitions should not specify a value or reason!"
234
+ raise Scarpe::InternalError, "Internal promise error! Non-completed state transitions should not specify a value or reason!"
235
235
  end
236
236
 
237
237
  # Here's our state-transition grid for what we're doing here.
@@ -254,11 +254,11 @@ class Scarpe
254
254
 
255
255
  # Transitioning to any *different* state after being fulfilled or rejected? Nope. Those states are final.
256
256
  if complete?
257
- raise "Internal Promise error! Trying to change state from #{old_state.inspect} to #{new_state.inspect}!"
257
+ raise Scarpe::InternalError, "Internal Promise error! Trying to change state from #{old_state.inspect} to #{new_state.inspect}!"
258
258
  end
259
259
 
260
260
  if old_state == :pending && new_state == :unscheduled
261
- raise "Can't change state from :pending to :unscheduled! Scheduling is not reversible!"
261
+ raise Shoes::Errors::InvalidAttributeValueError, "Can't change state from :pending to :unscheduled! Scheduling is not reversible!"
262
262
  end
263
263
 
264
264
  # The next three checks should all be followed by calling handlers for the newly-changed state.
@@ -341,7 +341,7 @@ class Scarpe
341
341
  @on_scheduled.each { |h| h.call(*@parents.map(&:returned_value)) }
342
342
  @on_scheduled = []
343
343
  else
344
- raise "Internal error! Trying to call handlers for #{state.inspect}!"
344
+ raise Scarpe::InternalError, "Internal error! Trying to call handlers for #{state.inspect}!"
345
345
  end
346
346
  end
347
347
 
@@ -367,7 +367,7 @@ class Scarpe
367
367
  end
368
368
 
369
369
  def call_executor
370
- raise("Internal error! Should not call_executor with no executor!") unless @executor
370
+ raise(Scarpe::InternalError, "Internal error! Should not call_executor with no executor!") unless @executor
371
371
 
372
372
  begin
373
373
  result = @executor.call(*@parents.map(&:returned_value))
@@ -389,7 +389,7 @@ class Scarpe
389
389
  # @return [Scarpe::Promise] self
390
390
  def on_fulfilled(&handler)
391
391
  unless handler
392
- raise "You must pass a block to on_fulfilled!"
392
+ raise Shoes::Errors::InvalidAttributeValueError, "You must pass a block to on_fulfilled!"
393
393
  end
394
394
 
395
395
  case @state
@@ -411,7 +411,7 @@ class Scarpe
411
411
  # @return [Scarpe::Promise] self
412
412
  def on_rejected(&handler)
413
413
  unless handler
414
- raise "You must pass a block to on_rejected!"
414
+ raise Shoes::Errors::InvalidAttributeValueError, "You must pass a block to on_rejected!"
415
415
  end
416
416
 
417
417
  case @state
@@ -434,7 +434,7 @@ class Scarpe
434
434
  # @return [Scarpe::Promise] self
435
435
  def on_scheduled(&handler)
436
436
  unless handler
437
- raise "You must pass a block to on_scheduled!"
437
+ raise Shoes::Errors::InvalidAttributeValueError, "You must pass a block to on_scheduled!"
438
438
  end
439
439
 
440
440
  # Add a pending handler or call it now
@@ -14,7 +14,7 @@ module Scarpe::Components
14
14
  # @return <void>
15
15
  def add_segment_type(type, handler)
16
16
  if segment_type_hash.key?(type)
17
- raise "Segment type #{type.inspect} already exists!"
17
+ raise Shoes::Errors::InvalidAttributeValueError, "Segment type #{type.inspect} already exists!"
18
18
  end
19
19
 
20
20
  segment_type_hash[type] = handler
@@ -22,11 +22,23 @@ module Scarpe::Components
22
22
 
23
23
  # Return an Array of segment type labels, such as "code" and "app_test".
24
24
  #
25
- # @return Array<String> the segment type labels
25
+ # @return [Array<String>] the segment type labels
26
26
  def segment_types
27
27
  segment_type_hash.keys
28
28
  end
29
29
 
30
+ # Normally a Shoes application will want to keep the default segment types,
31
+ # which allow loading a Shoes app and running a test inside. But sometimes
32
+ # the default handler will be wrong and a library will want to register
33
+ # its own "shoes" and "app_test" segment handlers, or not have any at all.
34
+ # For those applications, it makes sense to clear all segment types before
35
+ # registering its own.
36
+ #
37
+ # @return <void>
38
+ def remove_all_segment_types!
39
+ @segment_type_hash = {}
40
+ end
41
+
30
42
  # Load a .sca file with an optional YAML frontmatter prefix and
31
43
  # multiple file sections which can be treated differently.
32
44
  #
@@ -38,7 +50,7 @@ module Scarpe::Components
38
50
  # @param path [String] the file or directory to treat as a Scarpe app
39
51
  # @return [Boolean] return true if the file is loaded as a segmented Scarpe app file
40
52
  def call(path)
41
- return false unless path.end_with?(".scas")
53
+ return false unless path.end_with?(".scas") || path.end_with?(".sspec")
42
54
 
43
55
  file_load(path)
44
56
  true
@@ -55,14 +67,12 @@ module Scarpe::Components
55
67
  @after_load << block
56
68
  end
57
69
 
58
- private
59
-
60
- def gen_name(segmap)
70
+ def self.gen_name(segmap)
61
71
  ctr = (1..10_000).detect { |i| !segmap.key?("%5d" % i) }
62
72
  "%5d" % ctr
63
73
  end
64
74
 
65
- def tokenize_segments(contents)
75
+ def self.front_matter_and_segments_from_file(contents)
66
76
  require "yaml" # Only load when needed
67
77
  require "English"
68
78
 
@@ -90,12 +100,16 @@ module Scarpe::Components
90
100
  segments.each do |segment|
91
101
  if segment =~ /\A-* +(.*?)\n/
92
102
  # named segment with separator
93
- segmap[::Regexp.last_match(1)] = ::Regexp.last_match.post_match
103
+ name = ::Regexp.last_match(1)
104
+
105
+ raise("Duplicate segment name: #{name.inspect}!") if segmap.key?(name)
106
+
107
+ segmap[name] = ::Regexp.last_match.post_match
94
108
  elsif segment =~ /\A-* *\n/
95
109
  # unnamed segment with separator
96
110
  segmap[gen_name(segmap)] = ::Regexp.last_match.post_match
97
111
  else
98
- raise "Internal error when parsing segments in segmented app file! seg: #{segment.inspect}"
112
+ raise Scarpe::InternalError, "Internal error when parsing segments in segmented app file! seg: #{segment.inspect}"
99
113
  end
100
114
  end
101
115
 
@@ -105,19 +119,19 @@ module Scarpe::Components
105
119
  def file_load(path)
106
120
  contents = File.read(path)
107
121
 
108
- front_matter, segmap = tokenize_segments(contents)
122
+ front_matter, segmap = self.class.front_matter_and_segments_from_file(contents)
109
123
 
110
124
  if segmap.empty?
111
- raise "Illegal segmented Scarpe file: must have at least one code segment, not just front matter!"
125
+ raise Scarpe::FileContentError, "Illegal segmented Scarpe file: must have at least one code segment, not just front matter!"
112
126
  end
113
127
 
114
128
  if front_matter[:segments]
115
129
  if front_matter[:segments].size != segmap.size
116
- raise "Number of front matter :segments must equal number of file segments!"
130
+ raise Scarpe::FileContentError, "Number of front matter :segments must equal number of file segments!"
117
131
  end
118
132
  else
119
133
  if segmap.size > 2
120
- raise "Segmented files with more than two segments have to specify what they're for!"
134
+ raise Scarpe::FileContentError, "Segmented files with more than two segments have to specify what they're for!"
121
135
  end
122
136
 
123
137
  # Set to default of shoes code only or shoes code and app test code.
@@ -132,7 +146,7 @@ module Scarpe::Components
132
146
  tf_specs = []
133
147
  front_matter[:segments].each.with_index do |seg_type, idx|
134
148
  unless sth.key?(seg_type)
135
- raise "Unrecognized segment type #{seg_type.inspect}! No matching segment type available!"
149
+ raise Scarpe::FileContentError, "Unrecognized segment type #{seg_type.inspect}! No matching segment type available!"
136
150
  end
137
151
 
138
152
  tf_specs << ["scarpe_#{seg_type}_segment_contents", sv[idx]]
@@ -147,18 +161,23 @@ module Scarpe::Components
147
161
  # Need to call @after_load hooks while tempfiles still exist
148
162
  if @after_load && !@after_load.empty?
149
163
  @after_load.each(&:call)
164
+ @after_load = []
150
165
  end
151
166
  end
152
167
  end
153
168
 
154
169
  # The hash of segment type labels mapped to handlers which will be called.
155
- # Normal client code shouldn't ever call this.
170
+ # This could be called by a display service, a test framework or similar
171
+ # code that wants to define a non-Scarpe-standard file format.
156
172
  #
157
- # @return Hash<String, Object> the name/handler pairs
173
+ # @return [Hash<String, Object>] the name/handler pairs
158
174
  def segment_type_hash
159
175
  @segment_handlers ||= {
160
176
  "shoes" => proc { |seg_file| after_load { load seg_file } },
161
- "app_test" => proc { |seg_file| ENV["SCARPE_APP_TEST"] = seg_file },
177
+ "app_test" => proc do |seg_file|
178
+ ENV["SHOES_SPEC_TEST"] = seg_file
179
+ ENV["SHOES_MINITEST_EXPORT_FILE"] = "sspec.json"
180
+ end,
162
181
  }
163
182
  end
164
183
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scarpe; module Components; end; end
4
+ module Scarpe::Components::StringHelpers
5
+ # Cut down from Rails camelize
6
+ def self.camelize(string)
7
+ string = string.sub(/^[a-z\d]*/, &:capitalize)
8
+ string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{::Regexp.last_match(1)}#{::Regexp.last_match(2).capitalize}" }
9
+ end
10
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ # In Italian, tiranti are bootstraps -- the literal pull-on-a-boot kind, not a step to something better.
4
+ # Tiranti.rb builds on calzini.rb, but renders a Bootstrap-decorated version of the HTML output.
5
+ # You would ordinarily set either Calzini or Tiranti as the top-level HTML renderer, not both.
6
+ # You'll include both if you use Tiranti, because it falls back to Calzini for a lot of its rendering.
7
+
8
+ require "scarpe/components/calzini"
9
+
10
+ # The Tiranti module expects to be included by a class defining
11
+ # the following methods:
12
+ #
13
+ # * html_id - the HTML ID for the specific rendered DOM object
14
+ # * handler_js_code(event_name) - the JS handler code for this DOM object and event name
15
+ # * (optional) display_properties - the display properties for this object, unless overridden in render()
16
+ module Scarpe::Components::Tiranti
17
+ include Scarpe::Components::Calzini
18
+ extend self
19
+
20
+ # Currently we're using Bootswatch 5
21
+ BOOTSWATCH_THEMES = [
22
+ "cerulean",
23
+ "cosmo",
24
+ "cyborg",
25
+ "darkly",
26
+ "flatly",
27
+ "journal",
28
+ "litera",
29
+ "lumen",
30
+ "lux",
31
+ "materia",
32
+ "minty",
33
+ "morph",
34
+ "pulse",
35
+ "quartz",
36
+ "sandstone",
37
+ "simplex",
38
+ "sketchy",
39
+ "slate",
40
+ "solar",
41
+ "spacelab",
42
+ "superhero",
43
+ "united",
44
+ "vapor",
45
+ "yeti",
46
+ "zephyr",
47
+ ]
48
+
49
+ BOOTSWATCH_THEME = ENV["SCARPE_BOOTSTRAP_THEME"] || "sketchy"
50
+
51
+ def empty_page_element
52
+ <<~HTML
53
+ <html>
54
+ <head id='head-wvroot'>
55
+ <meta charset="utf-8">
56
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
57
+ <link rel="stylesheet" href="https://bootswatch.com/5/#{BOOTSWATCH_THEME}/bootstrap.css">
58
+ <link rel="stylesheet" href="https://bootswatch.com/_vendor/bootstrap-icons/font/bootstrap-icons.min.css">
59
+ <style id='style-wvroot'>
60
+ /** Style resets **/
61
+ body {
62
+ height: 100%;
63
+ overflow: hidden;
64
+ }
65
+ </style>
66
+ </head>
67
+ <body id='body-wvroot'>
68
+ <div id='wrapper-wvroot'></div>
69
+
70
+ <script src="https://bootswatch.com/_vendor/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
71
+ </body>
72
+ </html>
73
+ HTML
74
+ end
75
+
76
+ # def render_stack
77
+ # end
78
+ # def render_flow
79
+ # end
80
+
81
+ # How do we want to handle theme-specific colours and primary/secondary buttons in Bootstrap?
82
+ # "Disabled" could be checked in properties. Is there any way we can/should use "outline" buttons?
83
+ def button_element(props)
84
+ HTML.render do |h|
85
+ h.button(id: html_id, type: "button", class: "btn btn-primary", onclick: handler_js_code("click"), style: button_style(props)) do
86
+ props["text"]
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def button_style(props)
94
+ styles = drawable_style(props)
95
+
96
+ styles[:"background-color"] = props["color"] if props["color"]
97
+ styles[:"padding-top"] = props["padding_top"] if props["padding_top"]
98
+ styles[:"padding-bottom"] = props["padding_bottom"] if props["padding_bottom"]
99
+ styles[:color] = props["text_color"] if props["text_color"]
100
+ styles[:width] = dimensions_length(props["width"]) if props["width"]
101
+ styles[:height] = dimensions_length(props["height"]) if props["height"]
102
+ styles[:"font-size"] = props["font_size"] if props["font_size"]
103
+
104
+ styles[:top] = dimensions_length(props["top"]) if props["top"]
105
+ styles[:left] = dimensions_length(props["left"]) if props["left"]
106
+ styles[:position] = "absolute" if props["top"] || props["left"]
107
+ styles[:"font-size"] = dimensions_length(text_size(props["size"])) if props["size"]
108
+ styles[:"font-family"] = props["font"] if props["font"]
109
+
110
+ styles
111
+ end
112
+
113
+ public
114
+
115
+ def alert_element(props)
116
+ onclick = handler_js_code(props["event_name"] || "click")
117
+
118
+ HTML.render do |h|
119
+ h.div(id: html_id, class: "modal", tabindex: -1, role: "dialog", style: alert_overlay_style(props)) do
120
+ h.div(class: "modal-dialog", role: "document") do
121
+ h.div(class: "modal-content", style: alert_modal_style) do
122
+ h.div(class: "modal-header") do
123
+ h.h5(class: "modal-title") { "Alert" }
124
+ h.button(type: "button", class: "close", data_dismiss: "modal", aria_label: "Close") do
125
+ h.span(aria_hidden: "true") { "&times;" }
126
+ end
127
+ end
128
+ h.div(class: "modal-body") do
129
+ h.p { props["text"] }
130
+ end
131
+ h.div(class: "modal-footer") do
132
+ h.button(type: "button", onclick:, class: "btn btn-primary") { "OK" }
133
+ #h.button(type: "button", class: "btn btn-secondary") { "Close" }
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def check_element(props)
142
+ HTML.render do |h|
143
+ h.div class: "form-check" do
144
+ h.input type: :checkbox,
145
+ id: html_id,
146
+ class: "form-check-input",
147
+ onclick: handler_js_code("click"),
148
+ value: props["text"],
149
+ checked: props["checked"],
150
+ style: drawable_style(props)
151
+ end
152
+ end
153
+ end
154
+
155
+ def progress_element(props)
156
+ HTML.render do |h|
157
+ h.div(class: "progress", style: "width: 90%") do
158
+ pct = "%.1f" % ((props["fraction"] || 0.0) * 100.0)
159
+ h.div(
160
+ class: "progress-bar progress-bar-striped progress-bar-animated",
161
+ role: "progressbar",
162
+ "aria-valuenow": pct,
163
+ "aria-valuemin": 0,
164
+ "aria-valuemax": 100,
165
+ style: "width: #{pct}%",
166
+ )
167
+ end
168
+ end
169
+ end
170
+
171
+ # para_element is a bit of a hard one, since it does not-entirely-trivial
172
+ # mapping between display objects and IDs. But we don't want Calzini
173
+ # messing with the display service or display objects.
174
+ def para_element(props, &block)
175
+ tag, opts = para_elt_and_opts(props)
176
+
177
+ HTML.render do |h|
178
+ h.send(tag, **opts, &block)
179
+ end
180
+ end
181
+
182
+ private
183
+
184
+ ELT_AND_SIZE = {
185
+ inscription: [:p, 10],
186
+ ins: [:p, 10],
187
+ para: [:p, 12],
188
+ caption: [:p, 14],
189
+ tagline: [:p, 18],
190
+ subtitle: [:h3, 26],
191
+ title: [:h2, 34],
192
+ banner: [:h1, 48],
193
+ }.freeze
194
+
195
+ def para_elt_and_opts(props)
196
+ elt, size = para_elt_and_size(props)
197
+ size = dimensions_length(size)
198
+
199
+ para_style = drawable_style(props).merge({
200
+ color: rgb_to_hex(props["stroke"]),
201
+ "font-size": para_font_size(props),
202
+ "font-family": props["font"],
203
+ }.compact)
204
+
205
+ opts = (props["html_attributes"] || {}).merge(id: html_id, style: para_style)
206
+
207
+ [elt, opts]
208
+ end
209
+
210
+ def para_elt_and_size(props)
211
+ return [:p, nil] unless props["size"]
212
+
213
+ ps = props["size"].to_s.to_sym
214
+ if ELT_AND_SIZE.key?(ps)
215
+ ELT_AND_SIZE[ps]
216
+ else
217
+ sz = props["size"].to_i
218
+ if sz > 18
219
+ [:h2, sz]
220
+ else
221
+ [:p, sz]
222
+ end
223
+ end
224
+ end
225
+ end
@@ -125,7 +125,7 @@ module Scarpe::Test::LoggedTest
125
125
  def log_config_for_test
126
126
  {
127
127
  "default" => ["debug", "logger/test_failure_#{file_id}.log"],
128
- "DisplayService" => ["debug", "logger/test_failure_events_#{file_id}.log"],
128
+ "DisplayService" => ["debug", "logger/test_failure_display_service_#{file_id}.log"],
129
129
  }.merge(@additional_log_config || {})
130
130
  end
131
131
 
@@ -150,8 +150,8 @@ module Scarpe::Test::LoggedTest
150
150
  return if ALREADY_SET_UP_LOGGED_TEST_FAILURES[:setup]
151
151
 
152
152
  log_dir = self.class.logger_dir
153
- raise("Must set logger directory!") unless log_dir
154
- raise("Can't find logger directory!") unless File.directory?(log_dir)
153
+ raise(Scarpe::MustOverrideMethod, "Must set logger directory!") unless log_dir
154
+ raise(Scarpe::NoSuchFile, "Can't find logger directory!") unless File.directory?(log_dir)
155
155
 
156
156
  ALREADY_SET_UP_LOGGED_TEST_FAILURES[:setup] = true
157
157
  # Delete stale test failures, if any, before starting the first failure-logged test
@@ -181,11 +181,11 @@ module Scarpe::Test::LoggedTest
181
181
  out_loc = filepath.gsub(%r{.log\Z}, ".out.log")
182
182
 
183
183
  if out_loc == filepath
184
- raise "Something is wrong! Could not figure out failure-log output path for #{filepath.inspect}!"
184
+ raise Shoes::Errors::InvalidAttributeValueError, "Something is wrong! Could not figure out failure-log output path for #{filepath.inspect}!"
185
185
  end
186
186
 
187
187
  if File.exist?(out_loc)
188
- raise "Duplicate test file #{out_loc.inspect}? This file should *not* already exist!"
188
+ raise Scarpe::DuplicateFileError, "Duplicate test file #{out_loc.inspect}? This file should *not* already exist!"
189
189
  end
190
190
 
191
191
  out_loc
@@ -215,3 +215,43 @@ module Scarpe::Test::LoggedTest
215
215
  end
216
216
  end
217
217
  end
218
+
219
+ module Scarpe::Test::HTMLAssertions
220
+ # Assert that `actual_html` is the same as `expected_tag` with `opts`.
221
+ # This uses Scarpe's HTML tag-based renderer to render the tag and options
222
+ # into text, and valides that the text is the same.
223
+ #
224
+ # @see Scarpe::Components::HTML.render
225
+ #
226
+ # @param actual_html [String] the html to compare to
227
+ # @param expected_tag [String,Symbol] the HTML tag, used to send a method call
228
+ # @param opts keyword options passed to the tag method call
229
+ # @yield block passed to the tag method call.
230
+ # @return [void]
231
+ def assert_html(actual_html, expected_tag, **opts, &block)
232
+ expected_html = Scarpe::Components::HTML.render do |h|
233
+ h.public_send(expected_tag, opts, &block)
234
+ end
235
+
236
+ assert_equal expected_html, actual_html
237
+ end
238
+
239
+ # Assert that `actual_html` includes `expected_tag` with `opts`.
240
+ # This uses Scarpe's HTML tag-based renderer to render the tag and options
241
+ # into text, and valides that the full HTML contains that tag.
242
+ #
243
+ # @see Scarpe::Components::HTML.render
244
+ #
245
+ # @param actual_html [String] the html to compare to
246
+ # @param expected_tag [String,Symbol] the HTML tag, used to send a method call
247
+ # @param opts keyword options passed to the tag method call
248
+ # @yield block passed to the tag method call.
249
+ # @return [void]
250
+ def assert_contains_html(actual_html, expected_tag, **opts, &block)
251
+ expected_html = Scarpe::Components::HTML.render do |h|
252
+ h.public_send(expected_tag, opts, &block)
253
+ end
254
+
255
+ assert actual_html.include?(expected_html), "Expected #{actual_html.inspect} to include #{expected_html.inspect}!"
256
+ end
257
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
3
+ module Scarpe
4
4
  module Components
5
- VERSION = "0.2.2"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+
5
+ class TestCalziniAlert < Minitest::Test
6
+ def setup
7
+ @calzini = CalziniRenderer.new
8
+ end
9
+
10
+ def test_alert_defaults
11
+ assert_equal %{<div id="elt-1" style="position:fixed;top:0;left:0;width:100%;height:100%;} +
12
+ %{overflow:auto;z-index:1;background:rgba(0,0,0,0.4);display:flex;align-items:center;} +
13
+ %{justify-content:center">} +
14
+ %{<div style="min-width:200px;min-height:50px;padding:10px;display:flex;background:#fefefe;} +
15
+ %{flex-direction:column;justify-content:space-between;border-radius:9px"><div></div>} +
16
+ %{<button onclick="handle('click')">OK</button></div></div>},
17
+ @calzini.render("alert", {})
18
+ end
19
+
20
+ def test_alert_hidden
21
+ # A hidden alert is display:none for the outer div
22
+ assert_equal %{<div id="elt-1" style="position:fixed;top:0;left:0;width:100%;height:100%;} +
23
+ %{overflow:auto;z-index:1;background:rgba(0,0,0,0.4);display:none;align-items:center;} +
24
+ %{justify-content:center">} +
25
+ %{<div style="min-width:200px;min-height:50px;padding:10px;display:flex;background:#fefefe;} +
26
+ %{flex-direction:column;justify-content:space-between;border-radius:9px"><div></div>} +
27
+ %{<button onclick="handle('click')">OK</button></div></div>},
28
+ @calzini.render("alert", { "hidden" => true })
29
+ end
30
+ end