require 'test_helper' ## # The problem is as follows: When an object already contains a method # that has the same name as an actor in the context, then the object # defaults to its own method rather than calling the actor. # This is simulated in the two classes below, where HasNameCollision has # an empty attribute called will_collide (which is always nil). In the following # context, when the method collide is called on the actor will_collide, the # actor is ignored and the HasNameCollision#will_collide is called instead, # returning nil. ## class HasNameCollision include Surrounded attr_accessor :will_collide # should always return nil def assert_has_role true end def will_collide=(_); end end class ShouldCollide include Surrounded def collide return 'Method called in ShouldCollide' end end class ContextOverridesName extend Surrounded::Context # The specific behaviour we want to test is that when a role has a method that # has the same name as another role, then when that method is called strange # things happen. keyword_initialize :base, :will_collide on_name_collision :nothing trigger :check_setup do base.assert_has_role end trigger :induce_collision do base.name_collision end role :base do def name_collision will_collide.collide end def assert_has_role true end end end class ContextWithMultipleCollisions extend Surrounded::Context on_name_collision :warn keyword_initialize :first, :second, :third end class First def second;end def third;end end class Second def first;end def third;end end class Third def first;end def second;end end describe 'handling name collisions' do let(:new_context_with_collision){ ContextOverridesName.new(base: HasNameCollision.new, will_collide: ShouldCollide.new) } after do ContextOverridesName.instance_eval{ remove_instance_variable :@handler if defined?(@handler) } end def set_handler(handler) ContextOverridesName.instance_eval{ on_name_collision handler } end it 'is works properly without handling collisions' do assert new_context_with_collision.check_setup end it 'allows a name collision' do err = assert_raises(NoMethodError){ new_context_with_collision.induce_collision } assert_match(/undefined method \`collide' for nil:NilClass/, err.message) end it 'can raise an exception' do set_handler :raise assert_raises(ContextOverridesName::NameCollisionError){ new_context_with_collision } end it 'can print a warning' do set_handler :warn assert_output(nil, "base has name collisions with [:will_collide]\n") { new_context_with_collision } end it 'can ignore collisions' do set_handler :nothing assert_output(nil, nil) { new_context_with_collision } end it 'raises an error with an unknown handler' do set_handler :barf err = assert_raises(ArgumentError) { new_context_with_collision } expect(err.message).must_match(/your name collision handler was set to \`barf' but there is no instance nor class method of that name/) end let(:create_context_with_multiple_collisions){ ContextWithMultipleCollisions.new(first: First.new, second: Second.new, third: Third.new) } it 'can handle multiple collisions' do expected_message = <(message){ puts "message from a proc: #{message}"} assert_output("message from a proc: base has name collisions with [:will_collide]\n"){ new_context_with_collision } end end