stimulus_reflex 3.5.0.pre2 → 3.5.0.pre6

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -11
  3. data/Gemfile.lock +71 -66
  4. data/README.md +4 -3
  5. data/app/channels/stimulus_reflex/channel.rb +11 -11
  6. data/lib/generators/stimulus_reflex/stimulus_reflex_generator.rb +5 -4
  7. data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +5 -0
  8. data/lib/stimulus_reflex/broadcasters/broadcaster.rb +18 -17
  9. data/lib/stimulus_reflex/broadcasters/nothing_broadcaster.rb +6 -1
  10. data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +1 -3
  11. data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +2 -6
  12. data/lib/stimulus_reflex/configuration.rb +2 -1
  13. data/lib/stimulus_reflex/dataset.rb +34 -0
  14. data/lib/stimulus_reflex/element.rb +12 -46
  15. data/lib/stimulus_reflex/reflex.rb +7 -2
  16. data/lib/stimulus_reflex/utils/attribute_builder.rb +17 -0
  17. data/lib/stimulus_reflex/utils/logger.rb +6 -2
  18. data/lib/stimulus_reflex/version.rb +1 -1
  19. data/lib/tasks/stimulus_reflex/install.rake +1 -1
  20. data/test/broadcasters/broadcaster_test.rb +0 -1
  21. data/test/broadcasters/broadcaster_test_case.rb +24 -0
  22. data/test/broadcasters/nothing_broadcaster_test.rb +14 -22
  23. data/test/broadcasters/page_broadcaster_test.rb +30 -32
  24. data/test/broadcasters/selector_broadcaster_test.rb +82 -88
  25. data/test/element_test.rb +73 -0
  26. data/test/generators/stimulus_reflex_generator_test.rb +8 -0
  27. data/test/reflex_test.rb +11 -0
  28. data/test/test_helper.rb +3 -1
  29. data/test/tmp/app/reflexes/demo_reflex.rb +0 -1
  30. metadata +36 -20
  31. data/package.json +0 -58
  32. data/stimulus_reflex.gemspec +0 -42
  33. data/yarn.lock +0 -4680
@@ -19,9 +19,7 @@ module StimulusReflex
19
19
  payload: payload,
20
20
  children_only: true,
21
21
  permanent_attribute_name: permanent_attribute_name,
22
- stimulus_reflex: data.merge({
23
- morph: to_sym
24
- })
22
+ stimulus_reflex: data.merge(morph: to_sym)
25
23
  )
26
24
  end
27
25
  cable_ready.broadcast
@@ -17,9 +17,7 @@ module StimulusReflex
17
17
  payload: payload,
18
18
  children_only: true,
19
19
  permanent_attribute_name: permanent_attribute_name,
20
- stimulus_reflex: data.merge({
21
- morph: to_sym
22
- })
20
+ stimulus_reflex: data.merge(morph: to_sym)
23
21
  )
24
22
  else
25
23
  operations << [update.selector, :inner_html]
@@ -27,9 +25,7 @@ module StimulusReflex
27
25
  selector: update.selector,
28
26
  html: fragment.to_html,
29
27
  payload: payload,
30
- stimulus_reflex: data.merge({
31
- morph: to_sym
32
- })
28
+ stimulus_reflex: data.merge(morph: to_sym)
33
29
  )
34
30
  end
35
31
  end
@@ -14,7 +14,7 @@ module StimulusReflex
14
14
  end
15
15
 
16
16
  class Configuration
17
- attr_accessor :on_failed_sanity_checks, :on_new_version_available, :on_missing_default_urls, :parent_channel, :logging, :middleware
17
+ attr_accessor :on_failed_sanity_checks, :on_new_version_available, :on_missing_default_urls, :parent_channel, :logging, :logger, :middleware
18
18
 
19
19
  DEFAULT_LOGGING = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
20
20
 
@@ -24,6 +24,7 @@ module StimulusReflex
24
24
  @on_missing_default_urls = :warn
25
25
  @parent_channel = "ApplicationCable::Channel"
26
26
  @logging = DEFAULT_LOGGING
27
+ @logger = Rails.logger
27
28
  @middleware = ActionDispatch::MiddlewareStack.new
28
29
  end
29
30
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stimulus_reflex/utils/attribute_builder"
4
+
5
+ class StimulusReflex::Dataset < OpenStruct
6
+ include StimulusReflex::AttributeBuilder
7
+
8
+ attr_accessor :attrs, :data_attrs
9
+
10
+ def initialize(data = {})
11
+ datasets = data["dataset"] || {}
12
+ regular_dataset = datasets["dataset"] || {}
13
+ @attrs = build_data_attrs(regular_dataset, datasets["datasetAll"] || {})
14
+ @data_attrs = @attrs.transform_keys { |key| key.delete_prefix "data-" }
15
+
16
+ super build_underscored(@data_attrs)
17
+ end
18
+
19
+ def signed
20
+ @signed ||= ->(accessor) { GlobalID::Locator.locate_signed(self[accessor]) }
21
+ end
22
+
23
+ def unsigned
24
+ @unsigned ||= ->(accessor) { GlobalID::Locator.locate(self[accessor]) }
25
+ end
26
+
27
+ def boolean
28
+ @boolean ||= ->(accessor) { ActiveModel::Type::Boolean.new.cast(self[accessor]) || self[accessor].blank? }
29
+ end
30
+
31
+ def numeric
32
+ @numeric ||= ->(accessor) { Float(self[accessor]) }
33
+ end
34
+ end
@@ -1,66 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class StimulusReflex::Element < OpenStruct
4
- attr_reader :attrs, :data_attrs, :inner_html, :text_content
3
+ require "stimulus_reflex/dataset"
4
+ require "stimulus_reflex/utils/attribute_builder"
5
5
 
6
- def initialize(data = {})
7
- @attrs = HashWithIndifferentAccess.new(data["attrs"] || {})
8
- @inner_html = data["inner_html"]
9
- @text_content = data["text_content"]
6
+ class StimulusReflex::Element < OpenStruct
7
+ include StimulusReflex::AttributeBuilder
10
8
 
11
- datasets = data["dataset"] || {}
12
- regular_dataset = datasets["dataset"] || {}
13
- @data_attrs = build_data_attrs(regular_dataset, datasets["datasetAll"] || {})
9
+ attr_reader :attrs, :dataset
14
10
 
15
- super build_underscored(all_attributes)
11
+ alias_method :data_attributes, :dataset
16
12
 
17
- @data_attrs.transform_keys! { |key| key.delete_prefix "data-" }
18
- end
13
+ delegate :signed, :unsigned, :numeric, :boolean, :data_attrs, to: :dataset
19
14
 
20
- def signed
21
- @signed ||= ->(accessor) { GlobalID::Locator.locate_signed(dataset[accessor]) }
22
- end
15
+ def initialize(data = {})
16
+ @attrs = HashWithIndifferentAccess.new(data["attrs"] || {})
17
+ @dataset = StimulusReflex::Dataset.new(data)
23
18
 
24
- def unsigned
25
- @unsigned ||= ->(accessor) { GlobalID::Locator.locate(dataset[accessor]) }
19
+ all_attributes = @attrs.merge(@dataset.attrs)
20
+ super build_underscored(all_attributes)
26
21
  end
27
22
 
28
23
  def attributes
29
24
  @attributes ||= OpenStruct.new(build_underscored(attrs))
30
25
  end
31
26
 
32
- def dataset
33
- @dataset ||= OpenStruct.new(build_underscored(data_attrs))
34
- end
35
-
36
27
  def to_dom_id
37
28
  raise NoIDError.new "The element `morph` is called on must have a valid DOM ID" if id.blank?
38
29
 
39
30
  "##{id}"
40
31
  end
41
-
42
- alias_method :data_attributes, :dataset
43
-
44
- private
45
-
46
- def all_attributes
47
- @attrs.merge(@data_attrs)
48
- end
49
-
50
- def build_data_attrs(dataset, dataset_all)
51
- dataset_all.transform_keys! { |key| "data-#{key.delete_prefix("data-").pluralize}" }
52
-
53
- dataset.each { |key, value| dataset_all[key]&.prepend(value) }
54
-
55
- data_attrs = dataset.merge(dataset_all)
56
-
57
- HashWithIndifferentAccess.new(data_attrs || {})
58
- end
59
-
60
- def build_underscored(attrs)
61
- attrs.merge(attrs.transform_keys(&:underscore))
62
- end
63
-
64
- class NoIDError < StandardError
65
- end
66
32
  end
@@ -8,14 +8,14 @@ class StimulusReflex::Reflex
8
8
  include ActionView::Helpers::TagHelper
9
9
  include CableReady::Identifiable
10
10
 
11
- attr_accessor :payload
11
+ attr_accessor :payload, :headers
12
12
  attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
13
13
 
14
14
  alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
15
15
 
16
16
  delegate :connection, :stream_name, to: :channel
17
17
  delegate :controller_class, :flash, :session, to: :request
18
- delegate :broadcast, :broadcast_message, to: :broadcaster
18
+ delegate :broadcast, :halted, :error, to: :broadcaster
19
19
  delegate :reflex_id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, to: :client_attributes
20
20
 
21
21
  def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
@@ -41,6 +41,7 @@ class StimulusReflex::Reflex
41
41
  @client_attributes = ClientAttributes.new(client_attributes)
42
42
  @cable_ready = StimulusReflex::CableReadyChannels.new(stream_name, reflex_id)
43
43
  @payload = {}
44
+ @headers = {}
44
45
  self.params
45
46
  end
46
47
 
@@ -97,6 +98,7 @@ class StimulusReflex::Reflex
97
98
 
98
99
  def controller
99
100
  @controller ||= controller_class.new.tap do |c|
101
+ request.headers.merge!(headers)
100
102
  c.instance_variable_set :@stimulus_reflex, true
101
103
  c.set_request! request
102
104
  c.set_response! controller_class.make_response!(request)
@@ -111,6 +113,9 @@ class StimulusReflex::Reflex
111
113
  end
112
114
 
113
115
  def render(*args)
116
+ options = args.extract_options!
117
+ (options[:locals] ||= {}).reverse_merge!(params: params)
118
+ args << options.reverse_merge(layout: false)
114
119
  controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
115
120
  end
116
121
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusReflex::AttributeBuilder
4
+ def build_data_attrs(dataset, dataset_all)
5
+ dataset_all.transform_keys! { |key| "data-#{key.delete_prefix("data-").pluralize}" }
6
+
7
+ dataset.each { |key, value| dataset_all[key]&.prepend(value) }
8
+
9
+ data_attrs = dataset.merge(dataset_all)
10
+
11
+ HashWithIndifferentAccess.new(data_attrs || {})
12
+ end
13
+
14
+ def build_underscored(attrs)
15
+ attrs.merge(attrs.transform_keys(&:underscore))
16
+ end
17
+ end
@@ -2,18 +2,22 @@
2
2
 
3
3
  module StimulusReflex
4
4
  class Logger
5
+ attr_reader :logger
5
6
  attr_accessor :reflex, :current_operation
6
7
 
8
+ delegate :debug, :info, :warn, :error, :fatal, :unknown, to: :logger
9
+
7
10
  def initialize(reflex)
8
11
  @reflex = reflex
9
12
  @current_operation = 1
13
+ @logger = StimulusReflex.config.logger
10
14
  end
11
15
 
12
- def print
16
+ def log_all_operations
13
17
  return unless config_logging.instance_of?(Proc)
14
18
 
15
19
  reflex.broadcaster.operations.each do
16
- puts instance_eval(&config_logging) + "\e[0m"
20
+ logger.info instance_eval(&config_logging) + "\e[0m"
17
21
  @current_operation += 1
18
22
  end
19
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusReflex
4
- VERSION = "3.5.0.pre2"
4
+ VERSION = "3.5.0.pre6"
5
5
  end
@@ -96,7 +96,7 @@ namespace :stimulus_reflex do
96
96
  puts
97
97
  system "bundle exec rails generate stimulus_reflex:initializer"
98
98
  system "bundle exec rails generate cable_ready:initializer"
99
- system "bundle exec rails generate cable_ready:stream_from"
99
+ system "bundle exec rails generate cable_ready:helpers"
100
100
 
101
101
  puts
102
102
  puts "✨ Generating ApplicationReflex class and Stimulus controllers, plus an example Reflex class and controller"
@@ -7,6 +7,5 @@ class StimulusReflex::BroadcasterTest < StimulusReflex::BroadcasterTestCase
7
7
  broadcaster = StimulusReflex::Broadcaster.new(@reflex)
8
8
 
9
9
  assert_raises(NotImplementedError) { broadcaster.broadcast }
10
- assert_raises(NotImplementedError) { broadcaster.broadcast_message(subject: "Test") }
11
10
  end
12
11
  end
@@ -5,6 +5,30 @@ require_relative "../test_helper"
5
5
  class StimulusReflex::BroadcasterTestCase < ActionCable::Channel::TestCase
6
6
  tests StimulusReflex::Channel
7
7
 
8
+ def assert_broadcast_on(stream, data, &block)
9
+ serialized_msg = ActiveSupport::JSON.decode(ActiveSupport::JSON.encode(data))
10
+
11
+ new_messages = broadcasts(stream)
12
+ if block
13
+ old_messages = new_messages
14
+ clear_messages(stream)
15
+
16
+ yield
17
+ new_messages = broadcasts(stream)
18
+ clear_messages(stream)
19
+
20
+ (old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) }
21
+ end
22
+
23
+ message = new_messages.find { |msg| ActiveSupport::JSON.decode(msg) == serialized_msg }
24
+
25
+ unless message
26
+ puts "\n\n#{ActiveSupport::JSON.decode(new_messages.first)}\n#{data}\n\n"
27
+ end
28
+
29
+ assert message, "No messages sent with #{data} to #{stream}"
30
+ end
31
+
8
32
  setup do
9
33
  stub_connection(session_id: SecureRandom.uuid)
10
34
  def connection.env
@@ -3,35 +3,27 @@
3
3
  require_relative "broadcaster_test_case"
4
4
 
5
5
  class StimulusReflex::NothingBroadcasterTest < StimulusReflex::BroadcasterTestCase
6
- test "broadcasts a server message when called" do
6
+ test "broadcasts a nothing morph when called" do
7
7
  broadcaster = StimulusReflex::NothingBroadcaster.new(@reflex)
8
8
 
9
9
  expected = {
10
10
  "cableReady" => true,
11
- "operations" => {
12
- "dispatchEvent" => [
13
- {
14
- "name" => "stimulus-reflex:server-message",
15
- "detail" => {
16
- "reflexId" => "666",
17
- "payload" => {},
18
- "stimulusReflex" => {
19
- "some" => :data,
20
- "morph" => :nothing,
21
- "serverMessage" => {
22
- "subject" => "nothing",
23
- "body" => nil
24
- }
25
- }
26
- },
27
- "reflexId" => "666"
28
- }
29
- ]
30
- }
11
+ "operations" => [
12
+ {
13
+ "name" => "stimulus-reflex:morph-nothing",
14
+ "payload" => {},
15
+ "stimulusReflex" => {
16
+ "some" => "data",
17
+ "morph" => "nothing"
18
+ },
19
+ "reflexId" => "666",
20
+ "operation" => "dispatchEvent"
21
+ }
22
+ ]
31
23
  }
32
24
 
33
25
  assert_broadcast_on @reflex.stream_name, expected do
34
- broadcaster.broadcast nil, {:some => :data, "reflexId" => "666"}
26
+ broadcaster.broadcast nil, {some: :data}
35
27
  end
36
28
  end
37
29
  end
@@ -20,22 +20,21 @@ class StimulusReflex::PageBroadcasterTest < StimulusReflex::BroadcasterTestCase
20
20
 
21
21
  expected = {
22
22
  "cableReady" => true,
23
- "operations" => {
24
- "morph" => [
25
- {
26
- "selector" => "body",
27
- "html" => "<div>New Content</div><div>Another Content</div>",
28
- "payload" => {},
29
- "childrenOnly" => true,
30
- "permanentAttributeName" => nil,
31
- "stimulusReflex" => {
32
- "some" => :data,
33
- "morph" => :page
34
- },
35
- "reflexId" => "666"
36
- }
37
- ]
38
- }
23
+ "operations" => [
24
+ {
25
+ "selector" => "body",
26
+ "html" => "<div>New Content</div><div>Another Content</div>",
27
+ "payload" => {},
28
+ "childrenOnly" => true,
29
+ "permanentAttributeName" => nil,
30
+ "stimulusReflex" => {
31
+ "some" => :data,
32
+ "morph" => :page
33
+ },
34
+ "reflexId" => "666",
35
+ "operation" => "morph"
36
+ }
37
+ ]
39
38
  }
40
39
 
41
40
  assert_broadcast_on @reflex.stream_name, expected do
@@ -54,22 +53,21 @@ class StimulusReflex::PageBroadcasterTest < StimulusReflex::BroadcasterTestCase
54
53
 
55
54
  expected = {
56
55
  "cableReady" => true,
57
- "operations" => {
58
- "morph" => [
59
- {
60
- "selector" => "#foo",
61
- "html" => "New Content",
62
- "payload" => {},
63
- "childrenOnly" => true,
64
- "permanentAttributeName" => nil,
65
- "stimulusReflex" => {
66
- "some" => :data,
67
- "morph" => :page
68
- },
69
- "reflexId" => "666"
70
- }
71
- ]
72
- }
56
+ "operations" => [
57
+ {
58
+ "selector" => "#foo",
59
+ "html" => "New Content",
60
+ "payload" => {},
61
+ "childrenOnly" => true,
62
+ "permanentAttributeName" => nil,
63
+ "stimulusReflex" => {
64
+ "some" => :data,
65
+ "morph" => :page
66
+ },
67
+ "reflexId" => "666",
68
+ "operation" => "morph"
69
+ }
70
+ ]
73
71
  }
74
72
 
75
73
  assert_broadcast_on @reflex.stream_name, expected do
@@ -10,22 +10,21 @@ module StimulusReflex
10
10
 
11
11
  expected = {
12
12
  "cableReady" => true,
13
- "operations" => {
14
- "morph" => [
15
- {
16
- "selector" => "#foo",
17
- "html" => "<div>bar</div><div>baz</div>",
18
- "payload" => {},
19
- "childrenOnly" => true,
20
- "permanentAttributeName" => nil,
21
- "stimulusReflex" => {
22
- "some" => "data",
23
- "morph" => "selector"
24
- },
25
- "reflexId" => "666"
26
- }
27
- ]
28
- }
13
+ "operations" => [
14
+ {
15
+ "selector" => "#foo",
16
+ "html" => "<div>bar</div><div>baz</div>",
17
+ "payload" => {},
18
+ "childrenOnly" => true,
19
+ "permanentAttributeName" => nil,
20
+ "stimulusReflex" => {
21
+ "some" => "data",
22
+ "morph" => "selector"
23
+ },
24
+ "reflexId" => "666",
25
+ "operation" => "morph"
26
+ }
27
+ ]
29
28
  }
30
29
 
31
30
  assert_broadcast_on @reflex.stream_name, expected do
@@ -39,20 +38,19 @@ module StimulusReflex
39
38
 
40
39
  expected = {
41
40
  "cableReady" => true,
42
- "operations" => {
43
- "innerHtml" => [
44
- {
45
- "selector" => "#foo",
46
- "html" => '<div id="baz"><span>bar</span></div>',
47
- "payload" => {},
48
- "stimulusReflex" => {
49
- "some" => "data",
50
- "morph" => "selector"
51
- },
52
- "reflexId" => "666"
53
- }
54
- ]
55
- }
41
+ "operations" => [
42
+ {
43
+ "selector" => "#foo",
44
+ "html" => '<div id="baz"><span>bar</span></div>',
45
+ "payload" => {},
46
+ "stimulusReflex" => {
47
+ "some" => "data",
48
+ "morph" => "selector"
49
+ },
50
+ "reflexId" => "666",
51
+ "operation" => "innerHtml"
52
+ }
53
+ ]
56
54
  }
57
55
 
58
56
  assert_broadcast_on @reflex.stream_name, expected do
@@ -66,20 +64,19 @@ module StimulusReflex
66
64
 
67
65
  expected = {
68
66
  "cableReady" => true,
69
- "operations" => {
70
- "innerHtml" => [
71
- {
72
- "selector" => "#foo",
73
- "html" => "",
74
- "payload" => {},
75
- "stimulusReflex" => {
76
- "some" => "data",
77
- "morph" => "selector"
78
- },
79
- "reflexId" => "666"
80
- }
81
- ]
82
- }
67
+ "operations" => [
68
+ {
69
+ "selector" => "#foo",
70
+ "html" => "",
71
+ "payload" => {},
72
+ "stimulusReflex" => {
73
+ "some" => "data",
74
+ "morph" => "selector"
75
+ },
76
+ "reflexId" => "666",
77
+ "operation" => "innerHtml"
78
+ }
79
+ ]
83
80
  }
84
81
 
85
82
  assert_broadcast_on @reflex.stream_name, expected do
@@ -93,20 +90,19 @@ module StimulusReflex
93
90
 
94
91
  expected = {
95
92
  "cableReady" => true,
96
- "operations" => {
97
- "innerHtml" => [
98
- {
99
- "selector" => "#foo",
100
- "html" => "",
101
- "payload" => {},
102
- "stimulusReflex" => {
103
- "some" => "data",
104
- "morph" => "selector"
105
- },
106
- "reflexId" => "666"
107
- }
108
- ]
109
- }
93
+ "operations" => [
94
+ {
95
+ "selector" => "#foo",
96
+ "html" => "",
97
+ "payload" => {},
98
+ "stimulusReflex" => {
99
+ "some" => "data",
100
+ "morph" => "selector"
101
+ },
102
+ "reflexId" => "666",
103
+ "operation" => "innerHtml"
104
+ }
105
+ ]
110
106
  }
111
107
 
112
108
  assert_broadcast_on @reflex.stream_name, expected do
@@ -120,20 +116,19 @@ module StimulusReflex
120
116
 
121
117
  expected = {
122
118
  "cableReady" => true,
123
- "operations" => {
124
- "innerHtml" => [
125
- {
126
- "selector" => "#foo",
127
- "html" => "",
128
- "payload" => {},
129
- "stimulusReflex" => {
130
- "some" => "data",
131
- "morph" => "selector"
132
- },
133
- "reflexId" => "666"
134
- }
135
- ]
136
- }
119
+ "operations" => [
120
+ {
121
+ "selector" => "#foo",
122
+ "html" => "",
123
+ "payload" => {},
124
+ "stimulusReflex" => {
125
+ "some" => "data",
126
+ "morph" => "selector"
127
+ },
128
+ "reflexId" => "666",
129
+ "operation" => "innerHtml"
130
+ }
131
+ ]
137
132
  }
138
133
 
139
134
  assert_broadcast_on @reflex.stream_name, expected do
@@ -147,22 +142,21 @@ module StimulusReflex
147
142
 
148
143
  expected = {
149
144
  "cableReady" => true,
150
- "operations" => {
151
- "morph" => [
152
- {
153
- "selector" => "#foo",
154
- "html" => "<div>bar</div><div>baz</div>",
155
- "payload" => {},
156
- "childrenOnly" => true,
157
- "permanentAttributeName" => nil,
158
- "stimulusReflex" => {
159
- "some" => "data",
160
- "morph" => "selector"
161
- },
162
- "reflexId" => "666"
163
- }
164
- ]
165
- }
145
+ "operations" => [
146
+ {
147
+ "selector" => "#foo",
148
+ "html" => "<div>bar</div><div>baz</div>",
149
+ "payload" => {},
150
+ "childrenOnly" => true,
151
+ "permanentAttributeName" => nil,
152
+ "stimulusReflex" => {
153
+ "some" => "data",
154
+ "morph" => "selector"
155
+ },
156
+ "reflexId" => "666",
157
+ "operation" => "morph"
158
+ }
159
+ ]
166
160
  }
167
161
 
168
162
  assert_broadcast_on @reflex.stream_name, expected do