├── .editorconfig ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── emittr.gemspec ├── lib ├── emittr.rb └── emittr │ ├── callback.rb │ ├── emitter.rb │ ├── events.rb │ ├── listeners.rb │ └── version.rb └── spec ├── emittr ├── callback_spec.rb └── listeners_spec.rb ├── emittr_spec.rb └── spec_helper.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.gem 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'lib/emittr/version.rb' 4 | 5 | Documentation: 6 | Enabled: false 7 | 8 | Style/BlockDelimiters: 9 | Enabled: false 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.3.1 5 | before_install: gem install bundler -v 1.12.5 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'pry', group: :development 6 | 7 | gem 'simplecov', require: false, group: :test 8 | gem 'coveralls', require: false 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Talysson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emittr 2 | 3 | Emittr is a event emitter for Ruby. 4 | 5 | [![Build Status](https://travis-ci.org/talyssonoc/emittr.svg?branch=master)](https://travis-ci.org/talyssonoc/emittr) [![Coverage Status](https://coveralls.io/repos/github/talyssonoc/emittr/badge.svg?branch=master)](https://coveralls.io/github/talyssonoc/emittr?branch=master) 6 | 7 | Installation 8 | ------------ 9 | 10 | Add this line to your Gemfile: 11 | 12 | ```ruby 13 | gem 'emittr' 14 | ``` 15 | 16 | Or install it yourself as: 17 | 18 | $ gem install emittr 19 | 20 | Usage 21 | ----- 22 | 23 | Emittr can be used in two ways. You can just create an `Emittr::Emitter` instance and use it to emit and listen to events, like this: 24 | 25 | ```ruby 26 | emitter = Emittr::Emitter.new 27 | 28 | emitter.on :user_connected do |user_name| 29 | puts "Hello, #{user_name}" 30 | end 31 | 32 | emitter.emit :user_connected, 'Mr. Anderson' 33 | ``` 34 | 35 | Or you can include the `Emittr::Events` module into an existent class to make it an event emitter: 36 | 37 | ```ruby 38 | class Server 39 | include Emittr::Events 40 | 41 | def initialize 42 | on(:user_connected) do |user_data| 43 | handle_new_connection(user_data) 44 | end 45 | end 46 | 47 | def handle_new_connection(user_data) 48 | # do something clever here 49 | end 50 | end 51 | 52 | server = Server.new 53 | 54 | server.emit :user_connected, { name: 'Somebody', ip: '127.0.0.1' } 55 | ``` 56 | 57 | Config 58 | ------ 59 | 60 | ### Limiting listeners 61 | 62 | You can set a limit to prevent adding listeners by using: 63 | ```ruby 64 | class Server 65 | include Emittr::Events 66 | 67 | max_listeners 20 68 | end 69 | ``` 70 | or 71 | 72 | ```ruby 73 | emitter = Emittr::Emitter.new max_listeners: 20 74 | ``` 75 | 76 | This value can be get later from `#max_listeners_value`. 77 | 78 | **NOTE:** You can't overwrite `#max_listeners` value. If you try to do it, a `RuntimeError` will be raised. 79 | 80 | ## Add event listeners 81 | 82 | * `#on(event, callback)` - Call `callback` when `event` is emitted. 83 | 84 | * `#once(event, callback)` - Call `callback` when `event` is emitted for the 85 | first time, then the `callback` is removed from the listeners list of the given `event`. 86 | 87 | * `#on_any(callback)` - Call `callback` whenever an event is emitted. 88 | 89 | * `#once_any(callback)` - Call `callback` the first time any event is emitted then removes it from list. 90 | 91 | * `#on_many_times(event, times, callback)` - Acts like `#once`, but the listener 92 | will be removed from the list only after emitting the event as many times as 93 | provided. It accepts only positive Integer number as argument. 94 | 95 | ## Remove event listeners 96 | 97 | * `#off(event, callback)` - Remove `callback` from `event` callbacks list. 98 | 99 | * `#off(event)` - Removes all callbacks for `event`. 100 | 101 | * `#off` - Removes all callbacks for all events. 102 | 103 | * `#off_any(callback)` - Remove `callback` from list to be run after any event is emitted. 104 | 105 | ## Emitting events 106 | 107 | * `#emit(type, *args)` - Emit an `event` with all passed `args` as params. 108 | 109 | ## Retrieving added events 110 | 111 | * `#listeners_for(event)` - Return all callbacks for `event`. Callbacks added 112 | with `#on_any` or `#once_any` will not be included. 113 | 114 | * `#listeners_for_any` - Return all callbacks added with `#on_any` or `#once_any` 115 | 116 | ## Contributing 117 | 118 | 1. Fork it 119 | 2. Create your feature branch (`git checkout -b my-new-feature`) 120 | 3. Commit your changes (`git commit -am 'Add some feature'`) 121 | 4. Push to the branch (`git push origin my-new-feature`) 122 | 5. Create new Pull Request 123 | 124 | 125 | ## License 126 | 127 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 128 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'emittr' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /emittr.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'emittr/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'emittr' 8 | spec.version = Emittr::VERSION 9 | spec.authors = %w(Reynaldo Talysson) 10 | spec.email = ['r.scardinho@gmail.com', 'talyssonoc@gmail.com'] 11 | 12 | spec.summary = 'A Ruby event emitter implementation.' 13 | spec.homepage = 'https://github.com/talyssonoc/emittr' 14 | spec.license = 'MIT' 15 | 16 | if spec.respond_to?(:metadata) 17 | spec.metadata['allowed_push_host'] = 'https://rubygems.org' 18 | else 19 | raise 'RubyGems >= 2.0 is required to protect against public gem pushes.' 20 | end 21 | 22 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^test/}) } 23 | spec.bindir = 'exe' 24 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 25 | spec.require_paths = ['lib'] 26 | 27 | spec.add_development_dependency 'bundler', '~> 1.12' 28 | spec.add_development_dependency 'rake', '~> 10.0' 29 | spec.add_development_dependency 'rspec', '~> 3.0' 30 | end 31 | -------------------------------------------------------------------------------- /lib/emittr.rb: -------------------------------------------------------------------------------- 1 | require 'emittr/version' 2 | require 'emittr/listeners' 3 | require 'emittr/events' 4 | require 'emittr/emitter' 5 | require 'emittr/callback' 6 | 7 | module Emittr 8 | end 9 | -------------------------------------------------------------------------------- /lib/emittr/callback.rb: -------------------------------------------------------------------------------- 1 | module Emittr 2 | class Callback 3 | attr_accessor :wrapper, :callback 4 | 5 | def initialize(&callback) 6 | @callback = callback 7 | end 8 | 9 | def call(*args) 10 | callback.call(*args) 11 | end 12 | 13 | def ==(other) 14 | callback == other || wrapper == other 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/emittr/emitter.rb: -------------------------------------------------------------------------------- 1 | module Emittr 2 | class Emitter 3 | include Emittr::Events 4 | 5 | def initialize(max_listeners: nil) 6 | @max_listeners_value = max_listeners 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/emittr/events.rb: -------------------------------------------------------------------------------- 1 | module Emittr 2 | module Events 3 | def self.included(klass) 4 | klass.__send__ :include, InstanceMethods 5 | end 6 | 7 | module InstanceMethods 8 | extend Forwardable 9 | 10 | def_delegators :listeners, :max_listeners, :max_listeners 11 | def_delegators :listeners, :max_listeners_value, :max_listeners_value 12 | 13 | def on(event, &block) 14 | raise_no_block_error unless block_given? 15 | listeners.add_listener event.to_sym, ::Emittr::Callback.new(&block) 16 | self 17 | end 18 | 19 | def off(event = nil, &block) 20 | unless event 21 | listeners.clear 22 | return self 23 | end 24 | 25 | if block_given? 26 | listeners[event].reject! { |l| l == block } 27 | else 28 | listeners.delete event 29 | end 30 | 31 | self 32 | end 33 | 34 | def once(event, &block) 35 | raise_no_block_error unless block_given? 36 | 37 | callback = ::Emittr::Callback.new(&block) 38 | 39 | off_block = proc do |args| 40 | callback.call(*args) 41 | block_to_send = callback.wrapper || callback.callback 42 | 43 | off(event, &block_to_send) 44 | end 45 | 46 | callback.wrapper = off_block 47 | 48 | on(event, &off_block) 49 | end 50 | 51 | def on_any(&block) 52 | raise_no_block_error unless block_given? 53 | on(:*, &block) 54 | end 55 | 56 | def off_any(&block) 57 | raise_no_block_error unless block_given? 58 | off(:*, &block) 59 | end 60 | 61 | def once_any(&block) 62 | raise_no_block_error unless block_given? 63 | once(:*, &block) 64 | end 65 | 66 | def on_many_times(event, times, &block) 67 | raise_no_block_error unless block_given? 68 | raise ArgumentError, "#{times} must be an integer" unless times.is_a? Integer 69 | raise ArgumentError, "#{times} can't be negative" if times < 0 70 | 71 | callback = ::Emittr::Callback.new(&block) 72 | 73 | off_block = proc do |args| 74 | callback.call(*args) 75 | block_to_send = callback.wrapper || callback.callback 76 | 77 | times -= 1 78 | off(event, &block_to_send) if times.zero? 79 | end 80 | 81 | callback.wrapper = off_block 82 | on(event, &off_block) 83 | end 84 | 85 | def emit(event, *payload) 86 | emit_any(event, *payload) 87 | 88 | return unless listeners.key? event 89 | 90 | listeners[event].each do |l| 91 | l.call(*payload) 92 | end 93 | 94 | self 95 | end 96 | 97 | def listeners_for(event) 98 | listeners.for event.to_sym 99 | end 100 | 101 | def listeners_for_any 102 | listeners.for :* 103 | end 104 | 105 | private 106 | 107 | def listeners 108 | @listeners ||= Listeners.new(self) 109 | end 110 | 111 | def emit_any(event, *payload) 112 | any_listeners = listeners_for_any 113 | any_listeners.each { |l| l.call(event, *payload) } if any_listeners.any? 114 | end 115 | 116 | def raise_no_block_error 117 | raise ArgumentError, 'required block not passed' 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/emittr/listeners.rb: -------------------------------------------------------------------------------- 1 | module Emittr 2 | class Listeners < Hash 3 | attr_reader :max_listeners_value 4 | 5 | def initialize(emitter) 6 | @max_listeners_value = emitter.instance_variable_get(:@max_listeners_value) 7 | super() { |h, k| h[k] = [] } 8 | end 9 | 10 | def max_listeners(limit) 11 | raise RuntimeError, "can't overwrite max listeners value" if @max_listeners_value 12 | 13 | @max_listeners_value = limit 14 | end 15 | 16 | def add_listener(event, listener) 17 | raise ArgumentError, "must be an Emittr::Callback object" unless listener.is_a? ::Emittr::Callback 18 | raise RuntimeError, "can't add more listeners" if unable_to_add_listerners? 19 | 20 | self[event.to_sym] << listener 21 | end 22 | 23 | def for(event) 24 | self[event.to_sym].dup 25 | end 26 | 27 | private 28 | 29 | def unable_to_add_listerners? 30 | @max_listeners_value && self.values.flatten.count >= @max_listeners_value 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/emittr/version.rb: -------------------------------------------------------------------------------- 1 | module Emittr 2 | VERSION = '0.1.0' 3 | end 4 | -------------------------------------------------------------------------------- /spec/emittr/callback_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Emittr::Callback do 4 | let(:callback) { proc {} } 5 | 6 | it { is_expected.to respond_to :wrapper } 7 | 8 | describe '#new' do 9 | it 'sets callback' do 10 | cb = Emittr::Callback.new(&callback) 11 | expect(cb.callback).to eq callback 12 | end 13 | end 14 | 15 | describe '#call' do 16 | it 'calls #call on callback' do 17 | expect(callback).to receive(:call) 18 | cb = Emittr::Callback.new(&callback) 19 | cb.call 20 | end 21 | end 22 | 23 | describe '#==' do 24 | context 'when same callback is passed' do 25 | it 'returns true' do 26 | cb = Emittr::Callback.new(&callback) 27 | expect(cb == callback).to be true 28 | end 29 | end 30 | 31 | context 'when different callback is passed' do 32 | context 'and wrapper is also different' do 33 | it 'returns false' do 34 | wrapper_callback = proc {} 35 | unset_callback = proc {} 36 | 37 | cb = Emittr::Callback.new(&callback) 38 | cb.wrapper = wrapper_callback 39 | 40 | expect(cb == unset_callback).to be false 41 | end 42 | end 43 | 44 | context 'and wrapper is the same' do 45 | it 'returns true' do 46 | diff_callback = proc {} 47 | cb = Emittr::Callback.new(&callback) 48 | cb.wrapper = diff_callback 49 | 50 | expect(cb == diff_callback).to be true 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/emittr/listeners_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Emittr::Listeners do 4 | let(:emitter) { Emittr::Emitter.new } 5 | let(:listeners) { Emittr::Listeners.new emitter } 6 | 7 | describe '#add_listener' do 8 | it 'adds listener on passed event' do 9 | callback = ::Emittr::Callback.new { } 10 | listeners.add_listener :event, callback 11 | 12 | expect(listeners).to eq({ event: [callback] }) 13 | end 14 | 15 | context "when trying to add a callback that isn't an Emittr::Callback" do 16 | it 'raises ArgumentError with proper message' do 17 | expect { 18 | callback = proc {} 19 | listeners.add_listener :event, callback 20 | }.to raise_error ArgumentError, 'must be an Emittr::Callback object' 21 | end 22 | end 23 | end 24 | 25 | describe '#for' do 26 | let(:callback) { ::Emittr::Callback.new { } } 27 | 28 | it 'returns a list of callbacks for passed event' do 29 | listeners.add_listener :event, callback 30 | 31 | expect(listeners.for(:event)).to eq [callback] 32 | end 33 | 34 | context 'when trying to add listeners via #for' do 35 | it "doesn't add new callbacks to the list" do 36 | expect { 37 | listeners.for(:event) << callback 38 | }.not_to change(listeners.for(:event), :count) 39 | end 40 | end 41 | end 42 | 43 | describe '#max_listeners' do 44 | let(:callback) { ::Emittr::Callback.new {} } 45 | let(:other_callback) { ::Emittr::Callback.new {} } 46 | 47 | context 'when adding new listeners when #max_listeners is set' do 48 | it "doesn't add new listeners when limit is reached" do 49 | listeners.max_listeners 1 50 | listeners.add_listener :max, callback 51 | 52 | expect { 53 | listeners.add_listener :max, other_callback rescue RuntimeError 54 | }.not_to change(listeners.for(:max), :count) 55 | end 56 | 57 | it 'raises MaxListenersLimit error when adding new listeners' do 58 | listeners.max_listeners 1 59 | listeners.add_listener :max, callback 60 | 61 | expect { 62 | listeners.add_listener :max, other_callback 63 | }.to raise_error RuntimeError, "can't add more listeners" 64 | end 65 | end 66 | 67 | context 'when trying to overwrite max_listeners value' do 68 | it 'raises RuntimeError' do 69 | listeners.max_listeners 1 70 | 71 | expect { 72 | listeners.max_listeners 2 73 | }.to raise_error RuntimeError, "can't overwrite max listeners value" 74 | end 75 | 76 | it "doesn't change max_listeners value" do 77 | listeners.max_listeners 1 78 | 79 | expect { 80 | listeners.max_listeners 2 rescue RuntimeError 81 | }.not_to change { listeners.max_listeners_value } 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/emittr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Emittr do 4 | let(:emitter) { Emittr::Emitter.new } 5 | 6 | shared_examples_for 'no_block_passed' do |emitter_method| 7 | context 'when no block is passed' do 8 | it 'should throw an argument error' do 9 | expect { 10 | emitter.send(emitter_method, :no_block_test) 11 | }.to raise_error ArgumentError 12 | end 13 | end 14 | end 15 | 16 | describe '#on' do 17 | include_examples 'no_block_passed', :on 18 | 19 | it 'adds callback to listeners list' do 20 | callback = proc {} 21 | callback_inst = Emittr::Callback.new(&callback) 22 | 23 | allow(Emittr::Callback).to receive(:new).and_return(callback_inst) 24 | 25 | emitter.on :on_test, &callback 26 | 27 | listeners = emitter.listeners_for(:on_test) 28 | expect(listeners).to eq [callback_inst] 29 | end 30 | end 31 | 32 | describe '#off' do 33 | describe 'without callback' do 34 | context 'when there are no listeners to the given event' do 35 | it "doesn't throw an error" do 36 | expect { 37 | emitter.off :off_test 38 | }.to_not raise_error 39 | end 40 | end 41 | 42 | context 'when there are listeners to given event' do 43 | it 'removes all listeners for event' do 44 | callback = proc {} 45 | allow(callback).to receive(:call) 46 | emitter.on :off_test, &callback 47 | 48 | emitter.off :off_test 49 | emitter.emit :off_test 50 | 51 | listeners = emitter.listeners_for(:off_test) 52 | expect(listeners).to be_empty 53 | expect(callback).not_to have_received(:call) 54 | end 55 | end 56 | end 57 | 58 | describe 'with callback' do 59 | context 'when there are no listeners to the given event' do 60 | it "doesn't throw an error" do 61 | expect { 62 | emitter.off :off_test 63 | }.to_not raise_error 64 | end 65 | end 66 | 67 | context 'when there are listeners to given event' do 68 | it 'removes listeners for event' do 69 | callback = proc {} 70 | allow(callback).to receive(:call) 71 | emitter.on :off_test, &callback 72 | 73 | emitter.off :off_test, &callback 74 | emitter.emit :off_test 75 | 76 | listeners = emitter.listeners_for(:off_test) 77 | expect(listeners).to be_empty 78 | expect(callback).not_to have_received(:call) 79 | end 80 | end 81 | end 82 | 83 | describe 'when no event is passed' do 84 | let(:block) { proc {} } 85 | 86 | it 'empty listeners list' do 87 | emitter.on :first_clear_test, &block 88 | emitter.on :second_clear_test, &block 89 | 90 | expect(emitter.send(:listeners).count).to eq 2 91 | emitter.off 92 | expect(emitter.send(:listeners).count).to eq 0 93 | end 94 | end 95 | end 96 | 97 | describe '#once' do 98 | include_examples 'no_block_passed', :once 99 | 100 | let(:block) { proc {} } 101 | 102 | it 'adds listener to listeners list' do 103 | expect { 104 | emitter.once :once_test, &block 105 | }.to change { emitter.listeners_for(:once_test).count }.by 1 106 | end 107 | 108 | context 'when event is emitted' do 109 | before { emitter.once :once_test, &block } 110 | 111 | it 'calls callback only once' do 112 | expect(block).to receive(:call).once 113 | 2.times { emitter.emit :once_test } 114 | end 115 | 116 | it 'removes listener from listeners list when emitted' do 117 | emitter.emit :once_test 118 | expect(emitter.listeners_for(:once_test)).to be_empty 119 | end 120 | end 121 | end 122 | 123 | describe '#on_any' do 124 | include_examples 'no_block_passed', :on_any 125 | 126 | let(:any_block) { proc {} } 127 | 128 | context 'when listeners for a specific event are set' do 129 | it 'calls #on_any blocks' do 130 | block = proc {} 131 | 132 | emitter.on_any(&any_block) 133 | 134 | emitter.on :first_any_test, &block 135 | emitter.on :second_any_test, &block 136 | 137 | expect(any_block).to receive(:call).with(:first_any_test).twice 138 | expect(any_block).to receive(:call).with(:second_any_test).twice 139 | 140 | 2.times { emitter.emit :first_any_test } 141 | 2.times { emitter.emit :second_any_test } 142 | end 143 | end 144 | 145 | context 'when no events are set' do 146 | it 'calls #on_any blocks' do 147 | emitter.on_any(&any_block) 148 | 149 | expect(any_block).to receive(:call).with(:on_any_test).twice 150 | 151 | 2.times { emitter.emit :on_any_test } 152 | end 153 | end 154 | end 155 | 156 | describe '#off_any' do 157 | include_examples 'no_block_passed', :off_any 158 | 159 | let(:block) { proc {} } 160 | 161 | it 'removes listener from #any list' do 162 | block_for_on = proc {} 163 | 164 | emitter.on :off_any_test, &block_for_on 165 | 166 | emitter.on_any(&block) 167 | 168 | expect(block).to receive(:call).with(:off_any_test) 169 | emitter.emit :off_any_test 170 | expect(emitter.listeners_for_any).not_to be_empty 171 | 172 | emitter.off_any(&block) 173 | 174 | expect(block).not_to receive(:call).with(:off_any_test) 175 | emitter.emit :off_any_test 176 | expect(emitter.listeners_for_any).to be_empty 177 | end 178 | 179 | it "doesn't call block" do 180 | emitter.on(:off_any_test) { proc {} } 181 | 182 | emitter.on_any(&block) 183 | expect(block).to receive(:call) 184 | emitter.emit :off_any_test 185 | 186 | emitter.off_any(&block) 187 | expect(block).not_to receive(:call) 188 | emitter.emit :off_any_test 189 | end 190 | end 191 | 192 | describe '#once_any' do 193 | include_examples 'no_block_passed', :once_any 194 | 195 | let(:block) { proc {} } 196 | let(:on_block) { proc {} } 197 | 198 | it 'adds listener to #any listeners list' do 199 | expect { 200 | emitter.once_any(&block) 201 | }.to change(emitter.send(:listeners)[:*], :count).by 1 202 | end 203 | 204 | context 'when any event is emitted' do 205 | it 'calls block added to any list only once' do 206 | emitter.once_any(&block) 207 | emitter.on :once_any_test, &on_block 208 | 209 | expect(block).to receive(:call).once 210 | 2.times { emitter.emit :once_any_test } 211 | end 212 | 213 | it 'removes listener from listeners list after emitted' do 214 | emitter.once_any(&block) 215 | emitter.on :once_any_test, &on_block 216 | 217 | emitter.emit :once_any_test 218 | 219 | expect(emitter.listeners_for_any).to be_empty 220 | end 221 | end 222 | end 223 | 224 | describe '#on_many_times' do 225 | include_examples 'no_block_passed', :on_many_times 226 | 227 | let(:callback) { proc {} } 228 | 229 | it "raises an ArgumentError when first param isn't an Integer" do 230 | times = 'A' 231 | 232 | expect { 233 | emitter.on_many_times :many, times, &callback 234 | }.to raise_error ArgumentError, "#{times} must be an integer" 235 | end 236 | 237 | it "raises an ArgumentError when first param is a negative number" do 238 | times = -1 239 | 240 | expect { 241 | emitter.on_many_times :many, times, &callback 242 | }.to raise_error ArgumentError, "#{times} can't be negative" 243 | end 244 | 245 | it 'calls callback as many times as provided' do 246 | emitter.on_many_times :many, 3, &callback 247 | 248 | expect(callback).to receive(:call).exactly(3).times 249 | 4.times { emitter.emit :many } 250 | end 251 | 252 | it 'removes listener when reaching how many times the callback can run' do 253 | emitter.on_many_times :many, 1, &callback 254 | 2.times { emitter.emit :many } 255 | 256 | expect(emitter.listeners_for(:many)).to be_empty 257 | end 258 | end 259 | 260 | describe '#emit' do 261 | context "when events doesn't have payload" do 262 | it 'calls the callbacks' do 263 | callback = proc {} 264 | callback_2 = proc {} 265 | allow(callback).to receive(:call) 266 | allow(callback_2).to receive(:call) 267 | emitter.on :emit_test, &callback 268 | emitter.on :emit_test, &callback_2 269 | 270 | emitter.emit :emit_test 271 | 272 | expect(callback).to have_received(:call) 273 | expect(callback_2).to have_received(:call) 274 | end 275 | end 276 | 277 | context 'when events have payload' do 278 | it 'calls the callbacks with the payload' do 279 | callback = proc {} 280 | callback_2 = proc { |a, b| } 281 | allow(callback).to receive(:call) 282 | allow(callback_2).to receive(:call) 283 | emitter.on :emit_test, &callback 284 | emitter.on :emit_test, &callback_2 285 | 286 | emitter.emit :emit_test, 'A', 'B' 287 | 288 | expect(callback).to have_received(:call).with('A', 'B') 289 | expect(callback_2).to have_received(:call).with('A', 'B') 290 | end 291 | end 292 | 293 | context 'when there are no listeners to given event' do 294 | it "doesn't throw an error" do 295 | expect { 296 | emitter.emit :emit_test 297 | }.not_to raise_error 298 | end 299 | end 300 | end 301 | 302 | describe '#listeners_for' do 303 | let(:block) { proc {} } 304 | 305 | it 'retrieve listeners for provided event' do 306 | event = :listener 307 | emitter.on event, &block 308 | expect(emitter.listeners_for(event)).to eq [block] 309 | end 310 | 311 | it "can't be changed externally" do 312 | event = :listener 313 | emitter.on event, &block 314 | 315 | expect { 316 | emitter.listeners_for(event) << proc {} 317 | }.not_to change { emitter.listeners_for(event).count } 318 | end 319 | end 320 | 321 | describe '#listeners_for_any' do 322 | let(:block) { proc {} } 323 | 324 | it 'retrieve listeners for "any" list' do 325 | emitter.on_any(&block) 326 | expect(emitter.listeners_for_any).to eq [block] 327 | end 328 | 329 | it "can't be changed externally" do 330 | emitter.on_any(&block) 331 | 332 | expect { 333 | emitter.listeners_for_any << proc {} 334 | }.not_to change { emitter.listeners_for_any.count } 335 | end 336 | end 337 | 338 | describe '#max_listeners' do 339 | let(:callback) { proc {} } 340 | let(:other_callback) { proc {} } 341 | 342 | shared_examples_for 'regular usage' do |emitter_method| 343 | context 'adding new listeners when #max_listeners is set' do 344 | context ":#{emitter_method}" do 345 | it "doesn't add new listeners when limit is reached" do 346 | emitter.max_listeners 1 347 | emitter.send emitter_method, :max, &callback 348 | 349 | expect { 350 | emitter.send emitter_method, :max, &other_callback rescue RuntimeError 351 | }.not_to change(emitter.listeners_for(:max), :count) 352 | end 353 | 354 | it 'raises MaxListenersLimit error when adding new listeners' do 355 | emitter.max_listeners 1 356 | emitter.send emitter_method, :max, &callback 357 | 358 | expect { 359 | emitter.send emitter_method, :max, &other_callback 360 | }.to raise_error RuntimeError, "can't add more listeners" 361 | end 362 | end 363 | end 364 | end 365 | 366 | context 'behaves as it should' do 367 | include_examples 'regular usage', :on 368 | include_examples 'regular usage', :once 369 | end 370 | 371 | context 'when trying to overwrite max_listeners value' do 372 | it 'raises RuntimeError' do 373 | emitter.max_listeners 1 374 | 375 | expect { 376 | emitter.max_listeners 2 377 | }.to raise_error RuntimeError, "can't overwrite max listeners value" 378 | end 379 | 380 | it "doesn't change max_listeners value" do 381 | emitter.max_listeners 1 382 | 383 | expect { 384 | emitter.max_listeners 2 rescue RuntimeError 385 | }.not_to change { emitter.max_listeners_value } 386 | end 387 | end 388 | 389 | context 'when providing max_listeners value on Emittr::Emitter build' do 390 | let(:listeners) { Emittr::Listeners.new emitter } 391 | 392 | before { allow(emitter).to receive(:listeners).and_return(listeners) } 393 | 394 | it 'sets max_listeners_value properly' do 395 | emitter = Emittr::Emitter.new(max_listeners: 2) 396 | listeners = Emittr::Listeners.new emitter 397 | 398 | allow(emitter).to receive(:listeners).and_return(listeners) 399 | 400 | expect(listeners.max_listeners_value).to eq 2 401 | end 402 | 403 | include_examples 'regular usage', :on 404 | include_examples 'regular usage', :once 405 | end 406 | end 407 | end 408 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'coveralls' 3 | 4 | SimpleCov.formatter = Coveralls::SimpleCov::Formatter 5 | SimpleCov.start 6 | 7 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 8 | require 'pry' 9 | require 'emittr' 10 | --------------------------------------------------------------------------------