Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Spree::Event's test interface to run only selected listeners #4204

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions core/lib/spree/event/adapters/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class Default
# @api private
attr_reader :listeners

def initialize
@listeners = []
def initialize(listeners = [])
@listeners = listeners
end

# @api private
Expand Down Expand Up @@ -57,6 +57,11 @@ def unsubscribe(subscriber_or_event_name)
end
end

# @api private
def with_listeners(listeners)
self.class.new(listeners)
end

private

def listeners_for_event(event_name)
Expand Down
4 changes: 2 additions & 2 deletions core/lib/spree/event/adapters/deprecation_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ module DeprecationHandler

CI_LEGACY_ADAPTER_ENV_KEY = 'CI_LEGACY_EVENT_BUS_ADAPTER'

def self.legacy_adapter?(adapter)
def self.legacy_adapter?(adapter = Spree::Config.events.adapter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is needed in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds other caller locations that have no direct access to the used adapter. It's easier to default it to the global adapter, instead of making the same operation from the caller each time. E.g.:

adapter == LEGACY_ADAPTER
end

def self.legacy_adapter_set_by_env
return LEGACY_ADAPTER if ENV[CI_LEGACY_ADAPTER_ENV_KEY].present?
end

def self.render_deprecation_message?(adapter)
def self.render_deprecation_message?(adapter = Spree::Config.events.adapter)
legacy_adapter?(adapter) && legacy_adapter_set_by_env.nil?
end
end
Expand Down
5 changes: 5 additions & 0 deletions core/lib/spree/event/listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ def unsubscribe(event_name)
@exclusions << event_name
end

# @api private
def listeners
[self]
end

private

def excludes?(event_name)
Expand Down
41 changes: 41 additions & 0 deletions core/lib/spree/event/subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,47 @@ def activate
def deactivate(event_action_name = nil)
Spree::Event.subscriber_registry.deactivate_subscriber(self, event_action_name)
end

# Returns the generated listeners for the subscriber
#
# This method is only available when using
# [Spree::Event::Adapters::Default] adapter
#
# When a {Subscriber} is registered, the corresponding listeners are
# automatically generated, i.e., the returning values for
# {Spree::Event.subscribe} that encapsulate the logic to be performed.
#
# The listeners to obtain can be restricted to only certain events by providing
# their names:
#
# @example
#
# module EmailSender
# include Spree::Event::Subscriber
#
# event_action :order_finalized
# event_action :confirm_reimbursement
#
# def order_finalized(event)
# # ...
# end
#
# def confirm_reimbursement(event)
# # ...
# end
# end
#
# EmailSender.activate
# EmailSender.listeners.count # => 2
# EmailSender.listeners("order_finalized").count # => 1
#
# @param event_names [Array<String, Symbol>]
# @return [Array<Spree::Event::Listener>]
# @raise [RuntimeError] When adapter is
# [Spree::Event::Adapters::ActiveSupportNotifications]
def listeners(*event_names)
Spree::Event.subscriber_registry.listeners(self, event_names: event_names)
end
end
end
end
16 changes: 16 additions & 0 deletions core/lib/spree/event/subscriber_registry.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'spree/event/adapters/deprecation_handler'

module Spree
module Event
class SubscriberRegistry
Expand Down Expand Up @@ -50,6 +52,20 @@ def deactivate_subscriber(subscriber, event_action_name = nil)
end
end

def listeners(subscriber, event_names: [])
raise <<~MSG if Adapters::DeprecationHandler.legacy_adapter?
This method is only available with the new adapter Spree::Event::Adapters::Default
MSG

registry[subscriber.name].values.yield_self do |listeners|
if event_names.empty?
listeners
else
listeners.select { |listener| event_names.map(&:to_s).include?(listener.pattern) }
end
end
end

private

attr_reader :registry
Expand Down
93 changes: 93 additions & 0 deletions core/lib/spree/event/test_interface.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

require 'spree/event'

module Spree
module Event
# Test helpers for {Spree::Event}
#
# This module defines test helpers methods for {Spree::Event}. They can be
# made available to {Spree::Event} when {Spree::Event.enable_test_interface}
# is called.
#
# If you prefer, you can directly call them from
# `Spree::Event::TestInterface}.
module TestInterface
# @see {Spree::Event::TestInterface}
module Methods
# Perform only given listeners for the duration of the block
#
# Temporarily deactivate all subscribed listeners and listen only to the
# provided ones for the duration of the block.
#
# @example
# Spree::Event.enable_test_interface
#
# listener1 = Spree::Event.subscribe('foo') { do_something }
# listener2 = Spree::Event.subscribe('foo') { do_something_else }
#
# Spree::Event.performing_only(listener1) do
# Spree::Event.fire('foo') # This will run only `listener1`
# end
#
# Spree::Event.fire('foo') # This will run both `listener1` & `listener2`
#
# {Spree::Event::Subscriber} modules can also be given to unsubscribe from
# all listeners generated from it:
#
# @example
# Spree::Event.performing_only(EmailSubscriber) {}
#
# You can gain more fine-grained control thanks to
# {Spree::Event::Subscribe#listeners}:
#
# @example
# Spree::Event.performing_only(EmailSubscriber.listeners('order_finalized')) {}
#
# You can mix different ways of specifying listeners without problems:
#
# @example
# Spree::Event.performing_only(EmailSubscriber, listener1) {}
#
# @param listeners_and_subscribers [Spree::Event::Listener,
# Array<Spree::Event::Listener>, Spree::Event::Subscriber]
# @yield While the block executes only provided listeners will run
def performing_only(*listeners_and_subscribers)
adapter_in_use = Spree::Event.default_adapter
listeners = listeners_and_subscribers.flatten.map(&:listeners)
Spree::Config.events.adapter = adapter_in_use.with_listeners(listeners.flatten)
yield
ensure
Spree::Config.events.adapter = adapter_in_use
end

# Perform no listeners for the duration of the block
#
# It's a specialized version of {#performing_only} that provides no
# listeners.
#
# @yield While the block executes no listeners will run
#
# @see Spree::Event::TestInterface#performing_only
def performing_nothing(&block)
performing_only(&block)
end
end

extend Methods
end

# Adds test methods to {Spree::Event}
#
# @raise [RuntimeError] when {Spree::Event::Configuration#adapter} is set to
# the legacy adapter {Spree::Event::Adapters::ActiveSupportNotifications}.
def enable_test_interface
raise <<~MSG if deprecation_handler.legacy_adapter?(default_adapter)
Spree::Event's test interface is not supported when using the deprecated
adapter 'Spree::Event::Adapters::ActiveSupportNotifications'.
MSG

extend(TestInterface::Methods)
end
end
end
19 changes: 19 additions & 0 deletions core/spec/lib/spree/event/adapters/default_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,25 @@ def inc
expect(dummy2.count).to be(1)
end
end

describe '#with_listeners' do
it 'returns a new instance with given listeners', :aggregate_failures do
bus = described_class.new
dummy1, dummy2, dummy3 = Array.new(3) { counter.new }
listener1 = bus.subscribe('foo') { dummy1.inc }
listener2 = bus.subscribe('foo') { dummy2.inc }
listener3 = bus.subscribe('foo') { dummy3.inc }

new_bus = bus.with_listeners([listener1, listener2])
new_bus.fire('foo')

expect(new_bus).not_to eq(bus)
expect(new_bus.listeners).to match_array([listener1, listener2])
expect(dummy1.count).to be(1)
expect(dummy2.count).to be(1)
expect(dummy3.count).to be(0)
end
end
end
end
end
Expand Down
8 changes: 8 additions & 0 deletions core/spec/lib/spree/event/listener_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,12 @@
end
end
end

describe '#listeners' do
it 'returns a list containing only itself' do
listener = described_class.new(pattern: 'foo', block: -> {})

expect(listener.listeners).to eq([listener])
end
end
end
43 changes: 43 additions & 0 deletions core/spec/lib/spree/event/subscriber_registry_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# frozen_string_literal: true
require 'rails_helper'
require 'active_support/all'
require 'spec_helper'
require 'spree/event'
require 'spree/event/adapters/deprecation_handler'
require 'spree/event/listener'

RSpec.describe Spree::Event::SubscriberRegistry do
module N
Expand Down Expand Up @@ -112,4 +115,44 @@ def other_event(event)
end
end
end

describe '#listeners' do
if Spree::Event::Adapters::DeprecationHandler.legacy_adapter?
it 'raises error' do
expect { subject.listeners(N) }.to raise_error /only available with the new adapter/
end
else
before do
subject.register(N)
subject.activate_subscriber(N)
end
after { subject.deactivate_subscriber(N) }

it 'returns all listeners that the subscriber generates', :aggregate_failures do
listeners = subject.listeners(N)

expect(listeners.count).to be(2)
expect(listeners).to all be_a(Spree::Event::Listener)
end

it 'can restrict by event names', :aggregate_failures do
listeners = subject.listeners(N, event_names: ['event_name'])

expect(listeners.count).to be(1)
expect(listeners.first.pattern).to eq('event_name')

listeners = subject.listeners(N, event_names: ['event_name', 'other_event'])

expect(listeners.count).to be(2)
expect(listeners.map(&:pattern)).to match(['event_name', 'other_event'])
end

it 'can event names as symbols', :aggregate_failures do
listeners = subject.listeners(N, event_names: [:event_name])

expect(listeners.count).to be(1)
expect(listeners.first.pattern).to eq('event_name')
end
end
end
end
34 changes: 34 additions & 0 deletions core/spec/lib/spree/event/subscriber_spec.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
# frozen_string_literal: true

require 'rails_helper'
require 'active_support/all'
require 'spec_helper'
require 'spree/event'
require 'spree/event/adapters/deprecation_handler'
require 'spree/event/listener'

RSpec.describe Spree::Event::Subscriber do
module M
include Spree::Event::Subscriber

event_action :event_name
event_action :for_event_foo, event_name: :foo

def event_name(event)
# code that handles the event
end

def for_event_foo(event)
# code that handles the event
end

def other_event(event)
# not registered via event_action
end
Expand Down Expand Up @@ -83,4 +91,30 @@ def other_event(event)
end
end
end

unless Spree::Event::Adapters::DeprecationHandler.legacy_adapter?
describe '::listeners' do
before { M.activate }
after { M.deactivate }

it 'returns all listeners that the subscriber generates when no arguments are given', :aggregate_failures do
listeners = M.listeners

expect(listeners.count).to be(2)
expect(listeners.first).to be_a(Spree::Event::Listener)
end

it 'can restrict by event names given as arguments', :aggregate_failures do
listeners = M.listeners('event_name')

expect(listeners.count).to be(1)
expect(listeners.first.pattern).to eq('event_name')

listeners = M.listeners('event_name', 'foo')

expect(listeners.count).to be(2)
expect(listeners.map(&:pattern)).to match(['event_name', 'foo'])
end
end
end
end
Loading