shoelace-rails 0.1.0

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