Skip to content

Commit

Permalink
Setup a proxy object that wraps CableReady::Channels (#382)
Browse files Browse the repository at this point in the history
Setup a proxy object that wraps CableReady::Channels

This improves developer ergonimics for emitting CR broadcasts on the SR
channel within a reflex.
  • Loading branch information
hopsoft authored Nov 28, 2020
1 parent e27e790 commit 57f4553
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 184 deletions.
10 changes: 5 additions & 5 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
require "bundler/gem_tasks"
require "rails/test_unit/runner"

task default: [:test]

task :test do |task|
return 1 unless system("yarn run test")
Rails::TestUnit::Runner.run
task :test_javascript do |task|
system "yarn run test"
end

task :test_ruby do |task|
Rails::TestUnit::Runner.run
end

task test: [:test_javascript, :test_ruby]
task default: [:test]
1 change: 1 addition & 0 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ require "bundler/setup"
require "stimulus_reflex"
require "pry"

StimulusReflex.configuration.parent_channel = "ActionCable::Channel::Base"
Pry.start
1 change: 1 addition & 0 deletions lib/stimulus_reflex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
require "nokogiri"
require "cable_ready"
require "stimulus_reflex/version"
require "stimulus_reflex/cable_ready_channels"
require "stimulus_reflex/configuration"
require "stimulus_reflex/reflex"
require "stimulus_reflex/element"
Expand Down
6 changes: 2 additions & 4 deletions lib/stimulus_reflex/broadcasters/broadcaster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

module StimulusReflex
class Broadcaster
include CableReady::Broadcaster

attr_reader :reflex, :logger, :operations
delegate :permanent_attribute_name, :stream_name, to: :reflex
delegate :cable_ready, :permanent_attribute_name, to: :reflex

def initialize(reflex)
@reflex = reflex
Expand All @@ -28,7 +26,7 @@ def selector?
def broadcast_message(subject:, body: nil, data: {}, error: nil)
logger.error "\e[31m#{body}\e[0m" if subject == "error"
operations << ["document", :dispatch_event]
cable_ready[stream_name].dispatch_event(
cable_ready.dispatch_event(
name: "stimulus-reflex:server-message",
detail: {
reflexId: data["reflexId"],
Expand Down
2 changes: 1 addition & 1 deletion lib/stimulus_reflex/broadcasters/page_broadcaster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def broadcast(selectors, data)
selectors.each do |selector|
operations << [selector, :morph]
html = document.css(selector).inner_html
cable_ready[stream_name].morph(
cable_ready.morph(
selector: selector,
html: html,
children_only: true,
Expand Down
4 changes: 2 additions & 2 deletions lib/stimulus_reflex/broadcasters/selector_broadcaster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def broadcast(_, data = {})
match = fragment.at_css(selector)
if match.present?
operations << [selector, :morph]
cable_ready[stream_name].morph(
cable_ready.morph(
selector: selector,
html: match.inner_html,
children_only: true,
Expand All @@ -23,7 +23,7 @@ def broadcast(_, data = {})
)
else
operations << [selector, :inner_html]
cable_ready[stream_name].inner_html(
cable_ready.inner_html(
selector: selector,
html: fragment.to_html,
stimulus_reflex: data.merge({
Expand Down
18 changes: 18 additions & 0 deletions lib/stimulus_reflex/cable_ready_channels.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module StimulusReflex
class CableReadyChannels
def initialize(stream_name)
@cable_ready_channels = CableReady::Channels.instance
@stimulus_reflex_channel = @cable_ready_channels[stream_name]
end

def method_missing(name, *args)
return @stimulus_reflex_channel.send(name, *args) if @stimulus_reflex_channel.respond_to?(name)
@cable_ready_channels.send(name, *args)
end

def respond_to_missing?(name, include_all)
@stimulus_reflex_channel.respond_to?(name, include_all) ||
@cable_ready_channels.respond_to?(name, include_all)
end
end
end
4 changes: 2 additions & 2 deletions lib/stimulus_reflex/reflex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
class StimulusReflex::Reflex
include ActiveSupport::Rescuable
include ActiveSupport::Callbacks
include CableReady::Broadcaster

define_callbacks :process, skip_after_callbacks_if_terminated: true

Expand Down Expand Up @@ -45,7 +44,7 @@ def normalize_callback_option!(options, from, to)
end
end

attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger

alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name

Expand All @@ -64,6 +63,7 @@ def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil,
@broadcaster = StimulusReflex::PageBroadcaster.new(self)
@logger = StimulusReflex::Logger.new(self)
@client_attributes = ClientAttributes.new(client_attributes)
@cable_ready = StimulusReflex::CableReadyChannels.new(stream_name)
self.params
end

Expand Down
9 changes: 2 additions & 7 deletions test/broadcasters/broadcaster_test.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
require_relative "../test_helper"

class StimulusReflex::BroadcasterTest < ActiveSupport::TestCase
setup do
@reflex = Minitest::Mock.new
@reflex.expect :stream_name, "TestStream"
end
require_relative "broadcaster_test_case"

class StimulusReflex::BroadcasterTest < StimulusReflex::BroadcasterTestCase
test "raises a NotImplementedError if called directly" do
broadcaster = StimulusReflex::Broadcaster.new(@reflex)

Expand Down
13 changes: 13 additions & 0 deletions test/broadcasters/broadcaster_test_case.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require_relative "../test_helper"

class StimulusReflex::BroadcasterTestCase < ActionCable::Channel::TestCase
tests StimulusReflex::Channel

setup do
stub_connection(session_id: SecureRandom.uuid)
def connection.env
@env ||= {}
end
@reflex = StimulusReflex::Reflex.new(subscribe, url: "https://test.stimulusreflex.com")
end
end
53 changes: 26 additions & 27 deletions test/broadcasters/nothing_broadcaster_test.rb
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
require_relative "../test_helper"

class StimulusReflex::NothingBroadcasterTest < ActiveSupport::TestCase
setup do
@reflex = Minitest::Mock.new
@reflex.expect :stream_name, "TestStream"
end
require_relative "broadcaster_test_case"

class StimulusReflex::NothingBroadcasterTest < StimulusReflex::BroadcasterTestCase
test "broadcasts a server message when called" do
broadcaster = StimulusReflex::NothingBroadcaster.new(@reflex)

cable_ready_channels = Minitest::Mock.new
cable_ready_channel = Minitest::Mock.new
CableReady::Channels.stub :instance, cable_ready_channels do
cable_ready_channel.expect(:dispatch_event, nil, [{name: "stimulus-reflex:server-message",
detail: {
reflexId: nil,
stimulus_reflex: {
some: :data,
morph: :nothing,
server_message: {
subject: "nothing", body: nil
}
}
}}])
cable_ready_channels.expect(:[], cable_ready_channel, ["TestStream"])
cable_ready_channels.expect(:broadcast, nil)
broadcaster.broadcast(nil, {some: :data})
end
expected = {
"cableReady" => true,
"operations" => {
"dispatchEvent" => [
{
"name" => "stimulus-reflex:server-message",
"detail" => {
"reflexId" => nil,
"stimulusReflex" => {
"some" => :data,
"morph" => :nothing,
"serverMessage" => {
"subject" => "nothing",
"body" => nil
}
}
}
}
]
}
}

assert_mock cable_ready_channels
assert_mock cable_ready_channel
assert_broadcast_on @reflex.stream_name, expected do
broadcaster.broadcast nil, some: :data
end
end
end
106 changes: 55 additions & 51 deletions test/broadcasters/page_broadcaster_test.rb
Original file line number Diff line number Diff line change
@@ -1,69 +1,73 @@
require_relative "../test_helper"

class StimulusReflex::PageBroadcasterTest < ActiveSupport::TestCase
setup do
@reflex = Minitest::Mock.new
@reflex.expect :params, {action: "show"}
@reflex.expect :stream_name, "TestStream"
@reflex.expect :permanent_attribute_name, "some-attribute"
end
require_relative "broadcaster_test_case"

class StimulusReflex::PageBroadcasterTest < StimulusReflex::BroadcasterTestCase
test "returns if the response html is empty" do
controller = Minitest::Mock.new
controller.expect(:process, nil, ["show"])
@reflex.expect :controller, controller
@reflex.expect :controller, controller
broadcaster = StimulusReflex::PageBroadcaster.new(@reflex)
broadcaster.broadcast(["#foo"], {some: :data})
# TODO: figure out how to refute_broadcast_on
end

# stub the controller response with a struct responding to :body
controller.expect(:response, Struct.new(:body).new(nil))
test "performs a page morph on body" do
class << @reflex.controller.response
def body
"<html><head></head><body>New Content</body></html>"
end
end

broadcaster = StimulusReflex::PageBroadcaster.new(@reflex)

cable_ready_channels = Minitest::Mock.new
cable_ready_channels.expect(:broadcast, nil)

broadcaster.broadcast(["#foo"], {some: :data})
expected = {
"cableReady" => true,
"operations" => {
"morph" => [
{
"selector" => "body",
"html" => "New Content",
"childrenOnly" => true,
"permanentAttributeName" => nil,
"stimulusReflex" => {
"some" => :data,
"morph" => :page
}
}
]
}
}

assert_raises { cable_ready_channels.verify }
assert_broadcast_on @reflex.stream_name, expected do
broadcaster.broadcast(["body"], {some: :data})
end
end

test "performs a page morph given an array of reflex root selectors" do
controller = Minitest::Mock.new
controller.expect(:process, nil, ["show"])
@reflex.expect :controller, controller
@reflex.expect :controller, controller

# stub the controller response with a struct responding to :body
controller.expect(:response, Struct.new(:body).new("<html></html>"))
class << @reflex.controller.response
def body
"<html><head></head><body><div id=\"foo\">New Content</div></body></html>"
end
end

broadcaster = StimulusReflex::PageBroadcaster.new(@reflex)

cable_ready_channels = Minitest::Mock.new
cable_ready_channel = Minitest::Mock.new
document = Minitest::Mock.new
Nokogiri::HTML.stub :parse, document do
document.expect(:css, "something that is present", ["#foo"])
document.expect(:css, Struct.new(:inner_html).new("<span>bar</span>"), ["#foo"])

CableReady::Channels.stub :instance, cable_ready_channels do
cable_ready_channel.expect(:morph, nil, [{
selector: "#foo",
html: "<span>bar</span>",
children_only: true,
permanent_attribute_name: "some-attribute",
stimulus_reflex: {
some: :data,
morph: :page
expected = {
"cableReady" => true,
"operations" => {
"morph" => [
{
"selector" => "#foo",
"html" => "New Content",
"childrenOnly" => true,
"permanentAttributeName" => nil,
"stimulusReflex" => {
"some" => :data,
"morph" => :page
}
}
}])
cable_ready_channels.expect(:[], cable_ready_channel, ["TestStream"])
cable_ready_channels.expect(:broadcast, nil)
]
}
}

broadcaster.broadcast(["#foo"], {some: :data})
end
assert_broadcast_on @reflex.stream_name, expected do
broadcaster.broadcast(["#foo"], {some: :data})
end

assert_mock cable_ready_channels
assert_mock cable_ready_channel
end
end
Loading

0 comments on commit 57f4553

Please sign in to comment.