shoelace-rails 0.1.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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +85 -0
  3. data/.gitignore +20 -0
  4. data/Appraisals +25 -0
  5. data/CHANGELOG.md +3 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +11 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +178 -0
  10. data/Rakefile +27 -0
  11. data/app/helpers/shoelace/form_helper.rb +451 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/dist/.keep +0 -0
  15. data/dist/types/.keep +0 -0
  16. data/gemfiles/rails_50.gemfile +12 -0
  17. data/gemfiles/rails_51.gemfile +11 -0
  18. data/gemfiles/rails_52.gemfile +11 -0
  19. data/gemfiles/rails_60.gemfile +11 -0
  20. data/gemfiles/rails_61.gemfile +11 -0
  21. data/gemfiles/rails_70.gemfile +11 -0
  22. data/gemfiles/rails_edge.gemfile +14 -0
  23. data/lib/shoelace/engine.rb +8 -0
  24. data/lib/shoelace/rails/version.rb +7 -0
  25. data/lib/shoelace/rails.rb +10 -0
  26. data/lib/shoelace/testing.rb +40 -0
  27. data/package.json +50 -0
  28. data/rollup.config.js +49 -0
  29. data/shoelace-rails.gemspec +35 -0
  30. data/src/index.ts +2 -0
  31. data/src/turbo/index.ts +6 -0
  32. data/src/turbo/polyfills/formdata-event.js +27 -0
  33. data/src/turbo/sl-turbo-form.ts +110 -0
  34. data/src/turbolinks/features/confirm.ts +42 -0
  35. data/src/turbolinks/features/disable.ts +94 -0
  36. data/src/turbolinks/features/remote.ts +107 -0
  37. data/src/turbolinks/index.ts +6 -0
  38. data/src/turbolinks/selectors.ts +38 -0
  39. data/src/turbolinks/start.ts +38 -0
  40. data/src/turbolinks/turbolinks.ts +78 -0
  41. data/src/turbolinks/utils/ajax.ts +146 -0
  42. data/src/turbolinks/utils/csp.ts +20 -0
  43. data/src/turbolinks/utils/csrf.ts +33 -0
  44. data/src/turbolinks/utils/dom.ts +40 -0
  45. data/src/turbolinks/utils/event.ts +57 -0
  46. data/src/turbolinks/utils/form.ts +58 -0
  47. data/test/dummy_app/Gemfile +19 -0
  48. data/test/dummy_app/Rakefile +6 -0
  49. data/test/dummy_app/app/controllers/hotwire_forms_controller.rb +46 -0
  50. data/test/dummy_app/app/controllers/turbolinks_forms_controller.rb +37 -0
  51. data/test/dummy_app/app/models/user.rb +16 -0
  52. data/test/dummy_app/app/packs/entrypoints/hotwire.js +1 -0
  53. data/test/dummy_app/app/packs/entrypoints/turbolinks.js +5 -0
  54. data/test/dummy_app/app/views/hotwire_forms/form.html.erb +45 -0
  55. data/test/dummy_app/app/views/hotwire_forms/show.html.erb +5 -0
  56. data/test/dummy_app/app/views/layouts/application.html.erb +39 -0
  57. data/test/dummy_app/app/views/turbolinks_forms/form.html.erb +44 -0
  58. data/test/dummy_app/app/views/turbolinks_forms/show.html.erb +5 -0
  59. data/test/dummy_app/bin/rails +5 -0
  60. data/test/dummy_app/bin/webpack +18 -0
  61. data/test/dummy_app/bin/yarn +18 -0
  62. data/test/dummy_app/config/application.rb +16 -0
  63. data/test/dummy_app/config/boot.rb +4 -0
  64. data/test/dummy_app/config/environment.rb +2 -0
  65. data/test/dummy_app/config/environments/development.rb +10 -0
  66. data/test/dummy_app/config/environments/test.rb +18 -0
  67. data/test/dummy_app/config/routes.rb +4 -0
  68. data/test/dummy_app/config/webpack/development.js +5 -0
  69. data/test/dummy_app/config/webpack/production.js +1 -0
  70. data/test/dummy_app/config/webpack/test.js +5 -0
  71. data/test/dummy_app/config/webpacker.yml +33 -0
  72. data/test/dummy_app/config.ru +6 -0
  73. data/test/dummy_app/package.json +24 -0
  74. data/test/dummy_app/test/system/hotwire_form_test.rb +65 -0
  75. data/test/dummy_app/test/system/turbolinks_form_test.rb +39 -0
  76. data/test/dummy_app/test/test_helper.rb +68 -0
  77. data/test/helpers/form_helper_test.rb +397 -0
  78. data/test/test_helper.rb +18 -0
  79. data/tsconfig.json +19 -0
  80. data/yarn.lock +249 -0
  81. metadata +196 -0
@@ -0,0 +1,451 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoelace
4
+ module FormHelper
5
+ mattr_accessor :use_sl_form_tag
6
+ self.use_sl_form_tag = false
7
+
8
+ mattr_accessor :remote_form
9
+ self.remote_form = false
10
+
11
+ class ShoelaceInputField < ActionView::Helpers::Tags::TextField #:nodoc:
12
+ attr_reader :field_type
13
+
14
+ def initialize(field_type, *args)
15
+ super(*args)
16
+ @field_type = field_type
17
+ end
18
+
19
+ def render(&block)
20
+ options = @options.stringify_keys
21
+
22
+ value = options.fetch("value") { value_before_type_cast }
23
+ options["value"] = value if value.present?
24
+
25
+ options["size"] = options["maxlength"] unless options.key?("size")
26
+ options["type"] ||= field_type
27
+ options["invalid"] = options.fetch("invalid") { @object.errors[@method_name].presence }
28
+ add_default_name_and_id(options)
29
+
30
+ @template_object.content_tag('sl-input', '', options, &block)
31
+ end
32
+ end
33
+
34
+ class ShoelaceColorPicker < ActionView::Helpers::Tags::ColorField #:nodoc:
35
+ RGB_VALUE_REGEX = /#[0-9a-fA-F]{6}/
36
+
37
+ def field_type; nil; end
38
+
39
+ def tag(tag_name, *args, &block)
40
+ tag_name.to_s == 'input' ? content_tag('sl-color-picker', '', *args, &block) : super
41
+ end
42
+
43
+ private
44
+
45
+ def validate_color_string(string)
46
+ string.downcase if RGB_VALUE_REGEX.match?(string)
47
+ end
48
+ end
49
+
50
+ class ShoelaceRange < ActionView::Helpers::Tags::NumberField #:nodoc:
51
+ def field_type; nil; end
52
+
53
+ def tag(tag_name, *args, &block)
54
+ tag_name.to_s == 'input' ? content_tag('sl-range', '', *args, &block) : super
55
+ end
56
+ end
57
+
58
+ class ShoelaceSwitch < ActionView::Helpers::Tags::TextField #:nodoc:
59
+ def field_type; nil; end
60
+
61
+ def render(&block)
62
+ options = @options.stringify_keys
63
+ options["value"] = options.fetch("value") { value_before_type_cast }
64
+ add_default_name_and_id(options)
65
+
66
+ @template_object.content_tag('sl-switch', @method_name.to_s.humanize, options, &block)
67
+ end
68
+ end
69
+
70
+ class ShoelaceTextArea < ActionView::Helpers::Tags::TextArea #:nodoc:
71
+ def content_tag(tag_name, content, options)
72
+ options[:value] = content if content.present?
73
+
74
+ tag_name.to_s == 'textarea' ? super('sl-textarea', '', options) : super
75
+ end
76
+ end
77
+
78
+ class ShoelaceSelect < ActionView::Helpers::Tags::Select #:nodoc:
79
+ def grouped_options_for_select(grouped_options, options)
80
+ @template_object.grouped_sl_options_for_select(grouped_options, options)
81
+ end
82
+
83
+ def options_for_select(container, options = nil)
84
+ @template_object.sl_options_for_select(container, options)
85
+ end
86
+
87
+ def select_content_tag(option_tags, _options, html_options)
88
+ html_options = html_options.stringify_keys
89
+ html_options['value']= value
90
+ add_default_name_and_id(html_options)
91
+
92
+ @template_object.content_tag("sl-select", option_tags, html_options)
93
+ end
94
+ end
95
+
96
+ class ShoelaceCollectionSelect < ActionView::Helpers::Tags::CollectionSelect #:nodoc:
97
+ def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
98
+ @template_object.sl_options_from_collection_for_select(collection, value_method, text_method, selected)
99
+ end
100
+
101
+ def select_content_tag(option_tags, _options, html_options)
102
+ html_options = html_options.stringify_keys
103
+ html_options['value']= value
104
+ add_default_name_and_id(html_options)
105
+
106
+ @template_object.content_tag("sl-select", option_tags, html_options)
107
+ end
108
+ end
109
+
110
+ class ShoelaceCheckBox < ActionView::Helpers::Tags::CheckBox #:nodoc:
111
+ def render(&block)
112
+ options = @options.stringify_keys
113
+ options["value"] = @checked_value
114
+ options["checked"] = true if input_checked?(options)
115
+
116
+ if options["multiple"]
117
+ add_default_name_and_id_for_value(@checked_value, options)
118
+ options.delete("multiple")
119
+ else
120
+ add_default_name_and_id(options)
121
+ end
122
+
123
+ if block_given?
124
+ @template_object.content_tag('sl-checkbox', '', options, &block)
125
+ else
126
+ @template_object.content_tag('sl-checkbox', @method_name.to_s.humanize, options)
127
+ end
128
+ end
129
+ end
130
+
131
+ class ShoelaceRadioButton < ActionView::Helpers::Tags::RadioButton #:nodoc:
132
+ def render(&block)
133
+ options = @options.stringify_keys
134
+ options["value"] = @tag_value
135
+ options["checked"] = "checked" if input_checked?(options)
136
+ add_default_name_and_id_for_value(@tag_value, options)
137
+
138
+ @template_object.content_tag('sl-radio', '', options.except("type"), &block)
139
+ end
140
+ end
141
+
142
+ class ShoelaceCollectionRadioButtons < ActionView::Helpers::Tags::CollectionRadioButtons #:nodoc:
143
+ class RadioButtonBuilder < Builder # :nodoc:
144
+ def label(*)
145
+ text
146
+ end
147
+
148
+ def radio_button(extra_html_options = {}, &block)
149
+ html_options = extra_html_options.merge(@input_html_options)
150
+ html_options[:skip_default_ids] = false
151
+ @template_object.sl_radio_button(@object_name, @method_name, @value, html_options, &block)
152
+ end
153
+ end
154
+
155
+ def render(&block)
156
+ render_collection_for(RadioButtonBuilder, &block)
157
+ end
158
+
159
+ private
160
+
161
+ def render_collection(&block)
162
+ @template_object.content_tag('sl-radio-group', 'label' => @method_name.to_s.humanize) { super(&block) }
163
+ end
164
+
165
+ def hidden_field
166
+ ''.html_safe
167
+ end
168
+
169
+ def render_component(builder)
170
+ builder.radio_button { builder.label }
171
+ end
172
+ end
173
+
174
+ class ShoelaceFormBuilder < ActionView::Helpers::FormBuilder #:nodoc:
175
+ {
176
+ email: :email,
177
+ number: :number,
178
+ password: :password,
179
+ search: :search,
180
+ telephone: :tel,
181
+ phone: :tel,
182
+ text: :text,
183
+ url: :url
184
+ }.each do |field_type, field_class|
185
+ # def email_field(method, **options, &block)
186
+ # ShoelaceInputField.new(:email, object_name, method, @template, options.with_defaults(label: method.to_s.humanize)).render(&block)
187
+ # end
188
+ eval <<-RUBY, nil, __FILE__, __LINE__ + 1
189
+ def #{field_type}_field(method, **options, &block)
190
+ ShoelaceInputField.new(:#{field_class}, object_name, method, @template, options.with_defaults(object: @object, label: method.to_s.humanize)).render(&block)
191
+ end
192
+ RUBY
193
+ end
194
+
195
+ def color_field(method, **options)
196
+ ShoelaceColorPicker.new(object_name, method, @template, options.with_defaults(object: @object)).render
197
+ end
198
+ alias color_picker color_field
199
+
200
+ def range_field(method, **options)
201
+ ShoelaceRange.new(object_name, method, @template, options.with_defaults(object: @object)).render
202
+ end
203
+ alias range range_field
204
+
205
+ def switch_field(method, **options, &block)
206
+ ShoelaceSwitch.new(object_name, method, @template, options.with_defaults(object: @object)).render(&block)
207
+ end
208
+ alias switch switch_field
209
+
210
+ def text_area(method, **options)
211
+ ShoelaceTextArea.new(object_name, method, @template, options.with_defaults(object: @object, resize: 'auto')).render
212
+ end
213
+
214
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0", &block)
215
+ ShoelaceCheckBox.new(object_name, method, @template, checked_value, unchecked_value, options.merge(object: @object)).render(&block)
216
+ end
217
+
218
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
219
+ ShoelaceSelect.new(object_name, method, @template, choices, options.with_defaults(object: @object), html_options, &block).render
220
+ end
221
+
222
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
223
+ ShoelaceCollectionSelect.new(object_name, method, @template, collection, value_method, text_method, options.with_defaults(object: @object), html_options, &block).render
224
+ end
225
+
226
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
227
+ ShoelaceCollectionRadioButtons.new(object_name, method, @template, collection, value_method, text_method, options.with_defaults(object: @object), html_options).render(&block)
228
+ end
229
+
230
+ def submit(value = nil, options = {})
231
+ value, options = nil, value if value.is_a?(Hash)
232
+
233
+ @template.sl_submit_tag(value || submit_default_value, **options)
234
+ end
235
+ end
236
+
237
+ DEFAULT_FORM_PARAMETERS = {
238
+ builder: ShoelaceFormBuilder,
239
+ data: {
240
+ remote: true,
241
+ }
242
+ }
243
+
244
+ DEFAULT_TURBO_FORM_PARAMETERS = {
245
+ builder: ShoelaceFormBuilder,
246
+ }
247
+
248
+ DIVIDER_TAG = "<sl-divider></sl-divider>".html_safe
249
+ OPENING_SL_FORM_TAG = '<sl-form'.html_safe
250
+ CLOSING_SL_FORM_TAG = '</sl-form>'.html_safe
251
+ OPENING_SL_TURBO_FORM_TAG = '<sl-turbo-form'.html_safe
252
+ CLOSING_SL_TURBO_FORM_TAG = '</sl-turbo-form>'.html_safe
253
+
254
+ private_constant :DEFAULT_FORM_PARAMETERS, :DIVIDER_TAG, :OPENING_SL_FORM_TAG, :CLOSING_SL_FORM_TAG, :OPENING_SL_TURBO_FORM_TAG, :CLOSING_SL_TURBO_FORM_TAG
255
+
256
+ def sl_form_for(*args, **options, &block)
257
+ form_params = if ::Shoelace::FormHelper.remote_form
258
+ DEFAULT_FORM_PARAMETERS.deep_merge(options)
259
+ else
260
+ DEFAULT_FORM_PARAMETERS.without(:data).merge(options)
261
+ end
262
+
263
+ content = form_for(*args, **form_params, &block)
264
+
265
+ if ::Shoelace::FormHelper.use_sl_form_tag
266
+ content[0, 5] = OPENING_SL_FORM_TAG
267
+ content[-7, 7] = CLOSING_SL_FORM_TAG
268
+ end
269
+
270
+ content
271
+ end
272
+
273
+ def sl_form_with(**args, &block)
274
+ content = form_with(**args, **DEFAULT_FORM_PARAMETERS.except(:data), &block)
275
+
276
+ if ::Shoelace::FormHelper.use_sl_form_tag
277
+ content[0, 5] = OPENING_SL_FORM_TAG
278
+ content[-7, 7] = CLOSING_SL_FORM_TAG
279
+ end
280
+
281
+ content
282
+ end
283
+
284
+ def sl_form_tag(url_for_options = {}, options = {}, &block)
285
+ content = form_tag(url_for_options, options.with_defaults(DEFAULT_FORM_PARAMETERS.except(:builder)), &block)
286
+
287
+ if ::Shoelace::FormHelper.use_sl_form_tag
288
+ content[0, 5] = OPENING_SL_FORM_TAG
289
+ content[-7, 7] = CLOSING_SL_FORM_TAG
290
+ end
291
+
292
+ content
293
+ end
294
+
295
+ def sl_turbo_form_for(*args, **options, &block)
296
+ content = form_for(*args, **DEFAULT_TURBO_FORM_PARAMETERS.merge(options), &block)
297
+
298
+ if ::Shoelace::FormHelper.use_sl_form_tag
299
+ content[0, 5] = OPENING_SL_TURBO_FORM_TAG
300
+ content[-7, 7] = CLOSING_SL_TURBO_FORM_TAG
301
+ end
302
+
303
+ content
304
+ end
305
+
306
+ def sl_turbo_form_with(**args, &block)
307
+ content = form_with(**args, **DEFAULT_TURBO_FORM_PARAMETERS, &block)
308
+
309
+ if ::Shoelace::FormHelper.use_sl_form_tag
310
+ content[0, 5] = OPENING_SL_TURBO_FORM_TAG
311
+ content[-7, 7] = CLOSING_SL_TURBO_FORM_TAG
312
+ end
313
+
314
+ content
315
+ end
316
+
317
+ def sl_turbo_form_tag(url_for_options = {}, options = {}, &block)
318
+ content = form_tag(url_for_options, options.with_defaults(DEFAULT_TURBO_FORM_PARAMETERS.except(:builder)), &block)
319
+
320
+ if ::Shoelace::FormHelper.use_sl_form_tag
321
+ content[0, 5] = OPENING_SL_TURBO_FORM_TAG
322
+ content[-7, 7] = CLOSING_SL_TURBO_FORM_TAG
323
+ end
324
+
325
+ content
326
+ end
327
+
328
+ # Creates a generic +<sl-button>+ element.
329
+ def sl_button_tag(**attrs, &block)
330
+ content_tag("sl-button", **attrs, &block)
331
+ end
332
+
333
+ # Not providing this helper for now due to potentially untraceable HTML. E.g.
334
+ #
335
+ # <a href="...">
336
+ # <sl-button>...</sl-button>
337
+ # </a>
338
+ #
339
+ # may be trackable and traceable by search bots and scrapers, but:
340
+ #
341
+ # <sl-button href="...">...</sl-button>
342
+ #
343
+ # may not be. In the mean time, it is advisable to wrap a <sl-button> tag with an <a> tag.
344
+ #
345
+ # def sl_button_to(href, **attrs, &block)
346
+ # sl_button_tag(href: href, **attrs, &block)
347
+ # end
348
+
349
+ # Creates a submit button with the text value as the caption, with the +submit+ attribute.
350
+ def sl_submit_tag(value = 'Save changes', **options)
351
+ options = options.deep_stringify_keys
352
+ tag_options = { "type" => "submit", "variant" => "primary" }.update(options)
353
+ set_default_disable_with(value, tag_options)
354
+
355
+ content_tag('sl-button', value, tag_options)
356
+ end
357
+
358
+ # Creates a shoelace text field; use these text fields to input smaller chunks of text like a username or a search
359
+ # query.
360
+ #
361
+ # For the properties available on this tag, please refer to the official documentation:
362
+ # https://shoelace.style/components/input?id=properties
363
+ #
364
+ def sl_text_field_tag(name, value = nil, **options, &block)
365
+ content_tag('sl-input', '', { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys), &block)
366
+ end
367
+
368
+ # Returns a string of +<sl-menu-item>+ tags, like +options_for_select+, but prepends a +<sl-menu-label>+ tag to
369
+ # each group.
370
+ def grouped_sl_options_for_select(grouped_options, options)
371
+ body = "".html_safe
372
+
373
+ grouped_options.each_with_index do |container, index|
374
+ label, values = container
375
+
376
+ body.safe_concat(DIVIDER_TAG) if index > 0
377
+ body.safe_concat(content_tag("sl-menu-label", label)) if label.present?
378
+ body.safe_concat(sl_options_for_select(values, options))
379
+ end
380
+
381
+ body
382
+ end
383
+
384
+ # Accepts an enumerable (hash, array, enumerable, your type) and returns a string of +sl-menu-item+ tags. Given
385
+ # an enumerable where the elements respond to +first+ and +last+ (such as a two-element array), the “lasts” serve
386
+ # as option values and the “firsts” as option text.
387
+ def sl_options_for_select(enumerable, options = nil)
388
+ return enumerable if String === enumerable
389
+
390
+ selected, disabled = extract_selected_and_disabled(options).map { |r| Array(r).map(&:to_s) }
391
+
392
+ enumerable.map do |element|
393
+ html_attributes = option_html_attributes(element)
394
+ text, value = option_text_and_value(element).map(&:to_s)
395
+
396
+ html_attributes[:checked] ||= selected.include?(value)
397
+ html_attributes[:disabled] ||= disabled.include?(value)
398
+ html_attributes[:value] = value
399
+
400
+ tag_builder.content_tag_string('sl-menu-item', text, html_attributes)
401
+ end.join("\n").html_safe
402
+ end
403
+
404
+ # Returns a string of +<sl-menu-item>+ tags compiled by iterating over the collection and assigning the result of
405
+ # a call to the +value_method+ as the option value and the +text_method+ as the option text.
406
+ def sl_options_from_collection_for_select(collection, value_method, text_method, selected = nil)
407
+ options = collection.map do |element|
408
+ [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
409
+ end
410
+
411
+ selected, disabled = extract_selected_and_disabled(selected)
412
+
413
+ select_deselect = {
414
+ selected: extract_values_from_collection(collection, value_method, selected),
415
+ disabled: extract_values_from_collection(collection, value_method, disabled)
416
+ }
417
+
418
+ sl_options_for_select(options, select_deselect)
419
+ end
420
+
421
+ # Returns a +<sl-radio>+ tag for accessing a specified attribute (identified by method) on an object assigned to
422
+ # the template (identified by object). If the current value of method is +tag_value+ the radio button will be
423
+ # checked.
424
+ #
425
+ # To force the radio button to be checked pass checked: true in the options hash. You may pass HTML options there
426
+ # as well.
427
+ def sl_radio_button(object_name, method, tag_value, options = {}, &block)
428
+ ShoelaceRadioButton.new(object_name, method, self, tag_value, options).render(&block)
429
+ end
430
+
431
+ {
432
+ email: :email,
433
+ number: :number,
434
+ password: :password,
435
+ search: :search,
436
+ telephone: :tel,
437
+ phone: :tel,
438
+ url: :url
439
+ }.each do |field_type, field_class|
440
+ # def sl_email_field_tag(method, **options, &block)
441
+ # sl_text_field_tag(name, value, options.merge(type: :email))
442
+ # end
443
+ eval <<-RUBY, nil, __FILE__, __LINE__ + 1
444
+ # Creates a text field of type “#{field_type}”.
445
+ def sl_#{field_type}_field_tag(method, **options, &block)
446
+ sl_text_field_tag(name, value, options.merge(type: :#{field_class}))
447
+ end
448
+ RUBY
449
+ end
450
+ end
451
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "shoelace/rails"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/dist/.keep ADDED
File without changes
data/dist/types/.keep ADDED
File without changes
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
7
+ gem "rails", "~> 5.0.0"
8
+ gem "railties", "~> 5.0.0"
9
+ gem "activesupport", "~> 5.0.0"
10
+ gem "minitest", "5.10.3"
11
+
12
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
7
+ gem "rails", "~> 5.1.0"
8
+ gem "railties", "~> 5.1.0"
9
+ gem "activesupport", "~> 5.1.0"
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
7
+ gem "rails", "~> 5.2.0"
8
+ gem "railties", "~> 5.2.0"
9
+ gem "activesupport", "~> 5.2.0"
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
7
+ gem "rails", "~> 6.0.0"
8
+ gem "railties", "~> 6.0.0"
9
+ gem "activesupport", "~> 6.0.0"
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
7
+ gem "rails", "~> 6.1.0"
8
+ gem "railties", "~> 6.1.0"
9
+ gem "activesupport", "~> 6.1.0"
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
7
+ gem "rails", "~> 6.1.0"
8
+ gem "railties", "~> 6.1.0"
9
+ gem "activesupport", "~> 6.1.0"
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git "https://github.com/rails/rails.git" do
6
+ gem "rails"
7
+ gem "railties"
8
+ gem "activesupport"
9
+ end
10
+
11
+ gem "rake", "~> 13.0"
12
+ gem "rails-dom-testing", git: "https://github.com/rails/rails-dom-testing.git", ref: "8f5acdfc"
13
+
14
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoelace
4
+ class Engine < ::Rails::Engine #:nodoc:
5
+ config.shoelace = ActiveSupport::OrderedOptions.new
6
+ config.shoelace.use_sl_form_tag = false
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoelace
4
+ module Rails
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails/version"
4
+ require_relative "engine" if defined?(::Rails::Railtie)
5
+
6
+ module Shoelace
7
+ module Rails
8
+ class Error < StandardError; end
9
+ end
10
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoelace
4
+ module Testing
5
+ def sl_select(option_text, from: )
6
+ select_element = find("sl-select[placeholder=\"#{from}\"]")
7
+ select_element.click
8
+
9
+ within select_element do
10
+ find('sl-menu-item', text: option_text).click
11
+ end
12
+
13
+ select_element
14
+ end
15
+
16
+ def sl_multi_select(*options_to_select, from: )
17
+ select_element = find("sl-select[placeholder=\"#{from}\"]")
18
+
19
+ select_element.click
20
+ within select_element do
21
+ options_to_select.each do |option_text|
22
+ find('sl-menu-item', text: option_text).click
23
+ end
24
+ end
25
+
26
+ # The multi select does not close automatically, so need to click to close it.
27
+ select_element.click if options_to_select.size > 1
28
+
29
+ select_element
30
+ end
31
+
32
+ def sl_check(label)
33
+ find("sl-checkbox", text: label).click
34
+ end
35
+
36
+ def sl_toggle(label)
37
+ find("sl-switch", text: label).click
38
+ end
39
+ end
40
+ end