stimulus_reflex 3.4.1 → 3.5.0.pre0

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.

Potentially problematic release.


This version of stimulus_reflex might be problematic. Click here for more details.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +587 -495
  3. data/Gemfile.lock +82 -86
  4. data/LATEST +1 -0
  5. data/README.md +10 -10
  6. data/app/channels/stimulus_reflex/channel.rb +40 -67
  7. data/lib/generators/USAGE +1 -1
  8. data/lib/generators/stimulus_reflex/{config_generator.rb → initializer_generator.rb} +3 -3
  9. data/lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt +3 -2
  10. data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +1 -1
  11. data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +6 -1
  12. data/lib/stimulus_reflex.rb +9 -2
  13. data/lib/stimulus_reflex/broadcasters/broadcaster.rb +7 -4
  14. data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +2 -2
  15. data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +12 -5
  16. data/lib/stimulus_reflex/cable_ready_channels.rb +6 -2
  17. data/lib/stimulus_reflex/callbacks.rb +55 -5
  18. data/lib/stimulus_reflex/concern_enhancer.rb +37 -0
  19. data/lib/stimulus_reflex/configuration.rb +2 -1
  20. data/lib/stimulus_reflex/element.rb +31 -7
  21. data/lib/stimulus_reflex/policies/reflex_invocation_policy.rb +28 -0
  22. data/lib/stimulus_reflex/reflex.rb +35 -20
  23. data/lib/stimulus_reflex/reflex_data.rb +79 -0
  24. data/lib/stimulus_reflex/reflex_factory.rb +31 -0
  25. data/lib/stimulus_reflex/request_parameters.rb +19 -0
  26. data/lib/stimulus_reflex/{logger.rb → utils/logger.rb} +0 -2
  27. data/lib/stimulus_reflex/{sanity_checker.rb → utils/sanity_checker.rb} +58 -10
  28. data/lib/stimulus_reflex/version.rb +1 -1
  29. data/lib/tasks/stimulus_reflex/install.rake +6 -4
  30. data/package.json +6 -5
  31. data/stimulus_reflex.gemspec +5 -5
  32. data/test/broadcasters/broadcaster_test_case.rb +1 -1
  33. data/test/broadcasters/nothing_broadcaster_test.rb +5 -3
  34. data/test/broadcasters/page_broadcaster_test.rb +8 -4
  35. data/test/broadcasters/selector_broadcaster_test.rb +171 -55
  36. data/test/callbacks_test.rb +652 -0
  37. data/test/concern_enhancer_test.rb +54 -0
  38. data/test/element_test.rb +181 -0
  39. data/test/reflex_test.rb +1 -1
  40. data/test/test_helper.rb +4 -0
  41. data/test/tmp/app/reflexes/application_reflex.rb +2 -2
  42. data/test/tmp/app/reflexes/user_reflex.rb +3 -2
  43. data/yarn.lock +1138 -919
  44. metadata +39 -28
  45. data/tags +0 -156
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ClientAttributes = Struct.new(:reflex_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, keyword_init: true)
3
+ ClientAttributes = Struct.new(:reflex_id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, keyword_init: true)
4
4
 
5
5
  class StimulusReflex::Reflex
6
6
  include ActiveSupport::Rescuable
7
7
  include StimulusReflex::Callbacks
8
+ include ActionView::Helpers::TagHelper
9
+ include CableReady::Identifiable
8
10
 
11
+ attr_accessor :payload
9
12
  attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
10
13
 
11
14
  alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
@@ -13,8 +16,7 @@ class StimulusReflex::Reflex
13
16
  delegate :connection, :stream_name, to: :channel
14
17
  delegate :controller_class, :flash, :session, to: :request
15
18
  delegate :broadcast, :broadcast_message, to: :broadcaster
16
- delegate :reflex_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, to: :client_attributes
17
- delegate :render, to: :controller_class
19
+ delegate :reflex_id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, to: :client_attributes
18
20
 
19
21
  def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
20
22
  if is_a? CableReady::Broadcaster
@@ -37,7 +39,8 @@ class StimulusReflex::Reflex
37
39
  @broadcaster = StimulusReflex::PageBroadcaster.new(self)
38
40
  @logger = StimulusReflex::Logger.new(self)
39
41
  @client_attributes = ClientAttributes.new(client_attributes)
40
- @cable_ready = StimulusReflex::CableReadyChannels.new(stream_name)
42
+ @cable_ready = StimulusReflex::CableReadyChannels.new(stream_name, reflex_id)
43
+ @payload = {}
41
44
  self.params
42
45
  end
43
46
 
@@ -70,17 +73,15 @@ class StimulusReflex::Reflex
70
73
 
71
74
  req = ActionDispatch::Request.new(env)
72
75
 
73
- path_params = Rails.application.routes.recognize_path_with_request(req, url, req.env[:extras] || {})
74
- path_params[:controller] = path_params[:controller].force_encoding("UTF-8")
75
- path_params[:action] = path_params[:action].force_encoding("UTF-8")
76
+ # fetch path params (controller, action, ...) and apply them
77
+ request_params = StimulusReflex::RequestParameters.new(params: @params, req: req, url: url)
78
+ req = request_params.apply!
76
79
 
77
- req.env.merge(ActionDispatch::Http::Parameters::PARAMETERS_KEY => path_params)
78
- req.env["action_dispatch.request.parameters"] = req.parameters.merge(@params)
79
- req.tap { |r| r.session.send :load! }
80
+ req
80
81
  end
81
82
  end
82
83
 
83
- def morph(selectors, html = "")
84
+ def morph(selectors, html = nil)
84
85
  case selectors
85
86
  when :page
86
87
  raise StandardError.new("Cannot call :page morph after :#{broadcaster.to_sym} morph") unless broadcaster.page?
@@ -95,16 +96,25 @@ class StimulusReflex::Reflex
95
96
  end
96
97
 
97
98
  def controller
98
- @controller ||= begin
99
- controller_class.new.tap do |c|
100
- c.instance_variable_set :"@stimulus_reflex", true
101
- instance_variables.each { |name| c.instance_variable_set name, instance_variable_get(name) }
102
- c.set_request! request
103
- c.set_response! controller_class.make_response!(request)
104
- end
99
+ @controller ||= controller_class.new.tap do |c|
100
+ c.instance_variable_set :@stimulus_reflex, true
101
+ c.set_request! request
102
+ c.set_response! controller_class.make_response!(request)
105
103
  end
104
+
105
+ instance_variables.each { |name| @controller.instance_variable_set name, instance_variable_get(name) }
106
+ @controller
107
+ end
108
+
109
+ def controller?
110
+ !!defined? @controller
111
+ end
112
+
113
+ def render(*args)
114
+ controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
106
115
  end
107
116
 
117
+ # Invoke the reflex action specified by `name` and run all callbacks
108
118
  def process(name, *args)
109
119
  reflex_invoked = false
110
120
  result = run_callbacks(:process) {
@@ -129,7 +139,12 @@ class StimulusReflex::Reflex
129
139
  @_params ||= ActionController::Parameters.new(request.parameters)
130
140
  end
131
141
 
132
- def dom_id(record_or_class, prefix = nil)
133
- "#" + ActionView::RecordIdentifier.dom_id(record_or_class, prefix).to_s
142
+ # morphdom needs content to be wrapped in an element with the same id when children_only: true
143
+ # Oddly, it doesn't matter if the target element is a div! See: https://docs.stimulusreflex.com/appendices/troubleshooting#different-element-type-altogether-who-cares-so-long-as-the-css-selector-matches
144
+ # Used internally to allow automatic partial collection rendering, but also useful to library users
145
+ # eg. `morph dom_id(@posts), render_collection(@posts)`
146
+ def render_collection(resource, content = nil)
147
+ content ||= render(resource)
148
+ tag.div(content.html_safe, id: dom_id(resource).from(1))
134
149
  end
135
150
  end
@@ -0,0 +1,79 @@
1
+ class StimulusReflex::ReflexData
2
+ attr_reader :data
3
+
4
+ def initialize(data)
5
+ @data = data
6
+ end
7
+
8
+ def reflex_name
9
+ reflex_name = target.split("#").first
10
+ reflex_name = reflex_name.camelize
11
+ reflex_name.end_with?("Reflex") ? reflex_name : "#{reflex_name}Reflex"
12
+ end
13
+
14
+ def selectors
15
+ selectors = (data["selectors"] || []).select(&:present?)
16
+ selectors = data["selectors"] = ["body"] if selectors.blank?
17
+ selectors
18
+ end
19
+
20
+ def target
21
+ data["target"].to_s
22
+ end
23
+
24
+ def method_name
25
+ target.split("#").second
26
+ end
27
+
28
+ def arguments
29
+ (data["args"] || []).map { |arg| object_with_indifferent_access arg } || []
30
+ end
31
+
32
+ def url
33
+ data["url"].to_s
34
+ end
35
+
36
+ def element
37
+ StimulusReflex::Element.new(data)
38
+ end
39
+
40
+ def permanent_attribute_name
41
+ data["permanentAttributeName"]
42
+ end
43
+
44
+ def form_data
45
+ Rack::Utils.parse_nested_query(data["formData"])
46
+ end
47
+
48
+ def form_params
49
+ form_data.deep_merge(data["params"] || {})
50
+ end
51
+
52
+ def reflex_id
53
+ data["reflexId"]
54
+ end
55
+
56
+ def tab_id
57
+ data["tabId"]
58
+ end
59
+
60
+ def xpath_controller
61
+ data["xpathController"]
62
+ end
63
+
64
+ def xpath_element
65
+ data["xpathElement"]
66
+ end
67
+
68
+ def reflex_controller
69
+ data["reflexController"]
70
+ end
71
+
72
+ private
73
+
74
+ def object_with_indifferent_access(object)
75
+ return object.with_indifferent_access if object.respond_to?(:with_indifferent_access)
76
+ object.map! { |obj| object_with_indifferent_access obj } if object.is_a?(Array)
77
+ object
78
+ end
79
+ end
@@ -0,0 +1,31 @@
1
+ class StimulusReflex::ReflexFactory
2
+ class << self
3
+ attr_reader :reflex_data
4
+
5
+ def create_reflex_from_data(channel, reflex_data)
6
+ @reflex_data = reflex_data
7
+ reflex_class.new(channel,
8
+ url: reflex_data.url,
9
+ element: reflex_data.element,
10
+ selectors: reflex_data.selectors,
11
+ method_name: reflex_data.method_name,
12
+ params: reflex_data.form_params,
13
+ client_attributes: {
14
+ reflex_id: reflex_data.reflex_id,
15
+ tab_id: reflex_data.tab_id,
16
+ xpath_controller: reflex_data.xpath_controller,
17
+ xpath_element: reflex_data.xpath_element,
18
+ reflex_controller: reflex_data.reflex_controller,
19
+ permanent_attribute_name: reflex_data.permanent_attribute_name
20
+ })
21
+ end
22
+
23
+ def reflex_class
24
+ reflex_data.reflex_name.constantize.tap { |klass| raise ArgumentError.new("#{reflex_name} is not a StimulusReflex::Reflex") unless is_reflex?(klass) }
25
+ end
26
+
27
+ def is_reflex?(klass)
28
+ klass.ancestors.include? StimulusReflex::Reflex
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module StimulusReflex
2
+ class RequestParameters
3
+ def initialize(params:, req:, url:)
4
+ @params = params
5
+ @req = req
6
+ @url = url
7
+ end
8
+
9
+ def apply!
10
+ path_params = Rails.application.routes.recognize_path_with_request(@req, @url, @req.env[:extras] || {})
11
+ path_params[:controller] = path_params[:controller].force_encoding("UTF-8")
12
+ path_params[:action] = path_params[:action].force_encoding("UTF-8")
13
+
14
+ @req.env.merge(ActionDispatch::Http::Parameters::PARAMETERS_KEY => path_params)
15
+ @req.env["action_dispatch.request.parameters"] = @req.parameters.merge(@params)
16
+ @req.tap { |r| r.session.send :load! }
17
+ end
18
+ end
19
+ end
@@ -12,12 +12,10 @@ module StimulusReflex
12
12
  def print
13
13
  return unless config_logging.instance_of?(Proc)
14
14
 
15
- puts
16
15
  reflex.broadcaster.operations.each do
17
16
  puts instance_eval(&config_logging) + "\e[0m"
18
17
  @current_operation += 1
19
18
  end
20
- puts
21
19
  end
22
20
 
23
21
  private
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StimulusReflex::SanityChecker
4
+ LATEST_VERSION_FORMAT = /^(\d+\.\d+\.\d+)$/
4
5
  NODE_VERSION_FORMAT = /(\d+\.\d+\.\d+.*):/
5
6
  JSON_VERSION_FORMAT = /(\d+\.\d+\.\d+.*)"/
6
7
 
@@ -13,6 +14,8 @@ class StimulusReflex::SanityChecker
13
14
  instance = new
14
15
  instance.check_caching_enabled
15
16
  instance.check_javascript_package_version
17
+ instance.check_default_url_config
18
+ instance.check_new_version_available
16
19
  end
17
20
 
18
21
  private
@@ -24,44 +27,80 @@ class StimulusReflex::SanityChecker
24
27
  end
25
28
 
26
29
  def called_by_generate_config?
27
- ARGV.include? "stimulus_reflex:config"
30
+ ARGV.include? "stimulus_reflex:initializer"
28
31
  end
29
32
  end
30
33
 
31
34
  def check_caching_enabled
32
35
  unless caching_enabled?
33
36
  warn_and_exit <<~WARN
34
- Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
37
+ StimulusReflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
35
38
  To enable caching in development, run:
36
- rails dev:cache
39
+ rails dev:cache
37
40
  WARN
38
41
  end
39
42
 
40
43
  unless not_null_store?
41
44
  warn_and_exit <<~WARN
42
- Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
45
+ StimulusReflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
43
46
  But your config.cache_store is set to :null_store, so it won't work.
44
47
  WARN
45
48
  end
46
49
  end
47
50
 
51
+ def check_default_url_config
52
+ unless default_url_config_set?
53
+ warn_and_exit <<~WARN
54
+ StimulusReflex strongly suggests that you set default_url_options in your environment files.
55
+ Otherwise, ActionController and ActionMailer will default to example.com when rendering route helpers.
56
+ You can set your URL options in config/environments/#{Rails.env}.rb
57
+ config.action_controller.default_url_options = {host: "localhost", port: 3000}
58
+ config.action_mailer.default_url_options = {host: "localhost", port: 3000}
59
+ Please update every environment with the appropriate URL. Typically, no port is necessary in production.
60
+ WARN
61
+ end
62
+ end
63
+
48
64
  def check_javascript_package_version
49
65
  if javascript_package_version.nil?
50
66
  warn_and_exit <<~WARN
51
- Can't locate the stimulus_reflex NPM package.
67
+ Can't locate the stimulus_reflex npm package.
52
68
  Either add it to your package.json as a dependency or use "yarn link stimulus_reflex" if you are doing development.
53
69
  WARN
54
70
  end
55
71
 
56
72
  unless javascript_version_matches?
57
73
  warn_and_exit <<~WARN
58
- The Stimulus Reflex javascript package version (#{javascript_package_version}) does not match the Rubygem version (#{gem_version}).
59
- To update the Stimulus Reflex npm package:
60
- yarn upgrade stimulus_reflex@#{gem_version}
74
+ The stimulus_reflex npm package version (#{javascript_package_version}) does not match the Rubygem version (#{gem_version}).
75
+ To update the stimulus_reflex npm package:
76
+ yarn upgrade stimulus_reflex@#{gem_version}
61
77
  WARN
62
78
  end
63
79
  end
64
80
 
81
+ def check_new_version_available
82
+ return unless Rails.env.development?
83
+ return if StimulusReflex.config.on_new_version_available == :ignore
84
+ return unless using_stable_release
85
+ begin
86
+ latest_version = URI.open("https://raw.githubusercontent.com/stimulusreflex/stimulus_reflex/master/LATEST", open_timeout: 1, read_timeout: 1).read.strip
87
+ if latest_version != StimulusReflex::VERSION
88
+ puts <<~WARN
89
+
90
+ There is a new version of StimulusReflex available!
91
+ Current: #{StimulusReflex::VERSION} Latest: #{latest_version}
92
+
93
+ If you upgrade, it is very important that you update BOTH Gemfile and package.json
94
+ Then, run `bundle install && yarn install` to update to #{latest_version}.
95
+
96
+ WARN
97
+ exit if StimulusReflex.config.on_new_version_available == :exit
98
+ end
99
+ rescue
100
+ puts "StimulusReflex #{StimulusReflex::VERSION} update check skipped: connection timeout"
101
+ end
102
+ end
103
+
65
104
  private
66
105
 
67
106
  def caching_enabled?
@@ -72,10 +111,20 @@ class StimulusReflex::SanityChecker
72
111
  Rails.application.config.cache_store != :null_store
73
112
  end
74
113
 
114
+ def default_url_config_set?
115
+ Rails.application.config.action_controller.default_url_options
116
+ end
117
+
75
118
  def javascript_version_matches?
76
119
  javascript_package_version == gem_version
77
120
  end
78
121
 
122
+ def using_stable_release
123
+ stable = StimulusReflex::VERSION.match?(LATEST_VERSION_FORMAT)
124
+ puts "StimulusReflex #{StimulusReflex::VERSION} update check skipped: pre-release build" unless stable
125
+ stable
126
+ end
127
+
79
128
  def gem_version
80
129
  @_gem_version ||= StimulusReflex::VERSION.gsub(".pre", "-pre")
81
130
  end
@@ -118,7 +167,6 @@ class StimulusReflex::SanityChecker
118
167
  def exit_with_info
119
168
  puts
120
169
 
121
- # bundle exec rails generate stimulus_reflex:config
122
170
  if File.exist?(initializer_path)
123
171
  puts <<~INFO
124
172
  If you know what you are doing and you want to start the application anyway,
@@ -139,7 +187,7 @@ class StimulusReflex::SanityChecker
139
187
 
140
188
  Then open your initializer at
141
189
 
142
- <RAILS_ROOT>/config/initializers/stimulus_reflex.rb
190
+ #{initializer_path}
143
191
 
144
192
  and then add the following directive:
145
193
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusReflex
4
- VERSION = "3.4.1"
4
+ VERSION = "3.5.0.pre0"
5
5
  end
@@ -44,14 +44,16 @@ namespace :stimulus_reflex do
44
44
  end
45
45
 
46
46
  initialize_line = lines.find { |line| line.start_with?("StimulusReflex.initialize") }
47
- lines << "StimulusReflex.initialize(application, { consumer, controller, isolate: true })\n" unless initialize_line
47
+ lines << "application.consumer = consumer\n"
48
+ lines << "StimulusReflex.initialize(application, { controller, isolate: true })\n" unless initialize_line
48
49
  lines << "StimulusReflex.debug = process.env.RAILS_ENV === 'development'\n" unless initialize_line
49
50
  File.open(filepath, "w") { |f| f.write lines.join }
50
51
 
51
52
  filepath = Rails.root.join("config/environments/development.rb")
52
53
  lines = File.open(filepath, "r") { |f| f.readlines }
53
54
  unless lines.find { |line| line.include?("config.session_store") }
54
- lines.insert 3, " config.session_store :cache_store\n\n"
55
+ matches = lines.select { |line| line =~ /\A(Rails.application.configure do)/ }
56
+ lines.insert lines.index(matches.last).to_i + 1, " config.session_store :cache_store\n\n"
55
57
  File.open(filepath, "w") { |f| f.write lines.join }
56
58
  end
57
59
 
@@ -61,7 +63,7 @@ namespace :stimulus_reflex do
61
63
  lines.delete_at 1
62
64
  lines.insert 1, " adapter: redis\n"
63
65
  lines.insert 2, " url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n"
64
- lines.insert 3, " channel_prefix: " + File.basename(Rails.root.to_s).underscore + "_development\n"
66
+ lines.insert 3, " channel_prefix: " + File.basename(Rails.root.to_s).tr("\\", "").tr("-. ", "_").underscore + "_development\n"
65
67
  File.open(filepath, "w") { |f| f.write lines.join }
66
68
  end
67
69
 
@@ -71,7 +73,7 @@ namespace :stimulus_reflex do
71
73
 
72
74
  puts
73
75
  puts "StimulusReflex and CableReady have been successfully installed!"
74
- puts "Go to https://docs.stimulusreflex.com/quickstart if you need help getting started."
76
+ puts "Go to https://docs.stimulusreflex.com/hello-world/quickstart if you need help getting started."
75
77
  puts
76
78
  end
77
79
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stimulus_reflex",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "description": "Build reactive applications with the Rails tooling you already know and love.",
5
5
  "keywords": [
6
6
  "ruby",
@@ -20,11 +20,11 @@
20
20
  ],
21
21
  "homepage": "https://docs.stimulusreflex.com/",
22
22
  "bugs": {
23
- "url": "https://github.com/hopsoft/stimulus_reflex/issues"
23
+ "url": "https://github.com/stimulusreflex/stimulus_reflex/issues"
24
24
  },
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "git+https://github.com:hopsoft/stimulus_reflex.git"
27
+ "url": "git+https://github.com:stimulusreflex/stimulus_reflex.git"
28
28
  },
29
29
  "license": "MIT",
30
30
  "author": "Nathan Hopkins <natehop@gmail.com>",
@@ -34,14 +34,14 @@
34
34
  "postinstall": "node ./javascript/scripts/post_install.js",
35
35
  "prettier-standard:check": "yarn run prettier-standard --check ./javascript/*.js ./javascript/**/*.js",
36
36
  "prettier-standard:format": "yarn run prettier-standard ./javascript/*.js ./javascript/**/*.js",
37
- "test": "yarn run mocha --require @babel/register --require esm ./javascript/test"
37
+ "test": "yarn run mocha --require @babel/register --require jsdom-global/register --require esm ./javascript/test"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "stimulus": ">= 1.1"
41
41
  },
42
42
  "dependencies": {
43
43
  "@rails/actioncable": ">= 6.0",
44
- "cable_ready": ">= 4.5.0"
44
+ "cable_ready": "5.0.0-pre0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "^7.6.2",
@@ -50,6 +50,7 @@
50
50
  "assert": "^2.0.0",
51
51
  "esm": "^3.2.25",
52
52
  "jsdom": "^16.0.1",
53
+ "jsdom-global": "^3.0.2",
53
54
  "mocha": "^8.0.1",
54
55
  "prettier-standard": "^16.1.0",
55
56
  "stimulus": ">= 1.1"