diff options
Diffstat (limited to 'spec/ruby/core/module/include_spec.rb')
| -rw-r--r-- | spec/ruby/core/module/include_spec.rb | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/spec/ruby/core/module/include_spec.rb b/spec/ruby/core/module/include_spec.rb new file mode 100644 index 0000000000..210918b2e7 --- /dev/null +++ b/spec/ruby/core/module/include_spec.rb @@ -0,0 +1,628 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#include" do + it "is a public method" do + Module.should have_public_instance_method(:include, false) + end + + it "calls #append_features(self) in reversed order on each module" do + $appended_modules = [] + + m = Module.new do + def self.append_features(mod) + $appended_modules << [ self, mod ] + end + end + + m2 = Module.new do + def self.append_features(mod) + $appended_modules << [ self, mod ] + end + end + + m3 = Module.new do + def self.append_features(mod) + $appended_modules << [ self, mod ] + end + end + + c = Class.new { include(m, m2, m3) } + + $appended_modules.should == [ [ m3, c], [ m2, c ], [ m, c ] ] + end + + it "adds all ancestor modules when a previously included module is included again" do + ModuleSpecs::MultipleIncludes.ancestors.should include(ModuleSpecs::MA, ModuleSpecs::MB) + ModuleSpecs::MB.include(ModuleSpecs::MC) + ModuleSpecs::MultipleIncludes.include(ModuleSpecs::MB) + ModuleSpecs::MultipleIncludes.ancestors.should include(ModuleSpecs::MA, ModuleSpecs::MB, ModuleSpecs::MC) + end + + it "raises a TypeError when the argument is not a Module" do + -> { ModuleSpecs::Basic.include(Class.new) }.should raise_error(TypeError) + end + + it "does not raise a TypeError when the argument is an instance of a subclass of Module" do + class ModuleSpecs::SubclassSpec::AClass + end + -> { ModuleSpecs::SubclassSpec::AClass.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + ensure + ModuleSpecs::SubclassSpec.send(:remove_const, :AClass) + end + + it "raises a TypeError when the argument is a refinement" do + refinement = nil + + Module.new do + refine String do + refinement = self + end + end + + -> { ModuleSpecs::Basic.include(refinement) }.should raise_error(TypeError, "Cannot include refinement") + end + + it "imports constants to modules and classes" do + ModuleSpecs::A.constants.should include(:CONSTANT_A) + ModuleSpecs::B.constants.should include(:CONSTANT_A, :CONSTANT_B) + ModuleSpecs::C.constants.should include(:CONSTANT_A, :CONSTANT_B) + end + + it "shadows constants from ancestors" do + klass = Class.new + klass.include ModuleSpecs::ShadowingOuter::M + klass::SHADOW.should == 123 + klass.include ModuleSpecs::ShadowingOuter::N + klass::SHADOW.should == 456 + end + + it "does not override existing constants in modules and classes" do + ModuleSpecs::A::OVERRIDE.should == :a + ModuleSpecs::B::OVERRIDE.should == :b + ModuleSpecs::C::OVERRIDE.should == :c + end + + it "imports instance methods to modules and classes" do + ModuleSpecs::A.instance_methods.should include(:ma) + ModuleSpecs::B.instance_methods.should include(:ma,:mb) + ModuleSpecs::C.instance_methods.should include(:ma,:mb) + end + + it "does not import methods to modules and classes" do + ModuleSpecs::A.methods.include?(:cma).should == true + ModuleSpecs::B.methods.include?(:cma).should == false + ModuleSpecs::B.methods.include?(:cmb).should == true + ModuleSpecs::C.methods.include?(:cma).should == false + ModuleSpecs::C.methods.include?(:cmb).should == false + end + + it "attaches the module as the caller's immediate ancestor" do + module IncludeSpecsTop + def value; 5; end + end + + module IncludeSpecsMiddle + include IncludeSpecsTop + def value; 6; end + end + + class IncludeSpecsClass + include IncludeSpecsMiddle + end + + IncludeSpecsClass.new.value.should == 6 + end + + it "doesn't include module if it is included in a super class" do + module ModuleSpecs::M1 + module M; end + class A; include M; end + class B < A; include M; end + + all = [A, B, M] + + (B.ancestors.filter { |a| all.include?(a) }).should == [B, A, M] + end + end + + it "recursively includes new mixins" do + module ModuleSpecs::M1 + module U; end + module V; end + module W; end + module X; end + module Y; end + class A; include X; end; + class B < A; include U, V, W; end; + + # update V + module V; include X, U, Y; end + + # This code used to use Array#& and then compare 2 arrays, but + # the ordering from Array#& is undefined, as it uses Hash internally. + # + # Loop is more verbose, but more explicit in what we're testing. + + anc = B.ancestors + [B, U, V, W, A, X].each do |i| + anc.include?(i).should be_true + end + + class B; include V; end + + # the only new module is Y, it is added after U since it follows U in V mixin list: + anc = B.ancestors + [B, U, Y, V, W, A, X].each do |i| + anc.include?(i).should be_true + end + end + end + + it "preserves ancestor order" do + module ModuleSpecs::M2 + module M1; end + module M2; end + module M3; include M2; end + + module M2; include M1; end + module M3; include M2; end + + M3.ancestors.should == [M3, M2, M1] + + end + end + + it "detects cyclic includes" do + -> { + module ModuleSpecs::M + include ModuleSpecs::M + end + }.should raise_error(ArgumentError) + end + + it "doesn't accept no-arguments" do + -> { + Module.new do + include + end + }.should raise_error(ArgumentError) + end + + it "returns the class it's included into" do + m = Module.new + r = nil + c = Class.new { r = include m } + r.should == c + end + + it "ignores modules it has already included via module mutual inclusion" do + module ModuleSpecs::AlreadyInc + module M0 + end + + module M + include M0 + end + + class K + include M + include M + end + + K.ancestors[0].should == K + K.ancestors[1].should == M + K.ancestors[2].should == M0 + end + end + + it "clears any caches" do + module ModuleSpecs::M3 + module M1 + def foo + :m1 + end + end + + module M2 + def foo + :m2 + end + end + + class C + include M1 + + def get + foo + end + end + + c = C.new + c.get.should == :m1 + + class C + include M2 + end + + c.get.should == :m2 + + remove_const :C + end + end + + it "updates the method when an included module is updated" do + a_class = Class.new do + def foo + 'a' + end + end + + m_module = Module.new + + b_class = Class.new(a_class) do + include m_module + end + + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + m_module.module_eval do + def foo + 'm' + end + end + + foo.call.should == 'm' + end + + + it "updates the method when a module included after a call is later updated" do + m_module = Module.new + a_class = Class.new do + def foo + 'a' + end + end + b_class = Class.new(a_class) + b = b_class.new + foo = -> { b.foo } + foo.call.should == 'a' + + b_class.include m_module + foo.call.should == 'a' + + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the method when a nested included module is updated" do + a_class = Class.new do + def foo + 'a' + end + end + + n_module = Module.new + + m_module = Module.new do + include n_module + end + + b_class = Class.new(a_class) do + include m_module + end + + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + n_module.module_eval do + def foo + 'n' + end + end + + foo.call.should == 'n' + end + + it "updates the method when a new module is included" do + a_class = Class.new do + def foo + 'a' + end + end + + m_module = Module.new do + def foo + 'm' + end + end + + b_class = Class.new(a_class) + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + b_class.class_eval do + include m_module + end + + foo.call.should == 'm' + end + + it "updates the method when a new module with nested module is included" do + a_class = Class.new do + def foo + 'a' + end + end + + n_module = Module.new do + def foo + 'n' + end + end + + m_module = Module.new do + include n_module + end + + b_class = Class.new(a_class) + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + b_class.class_eval do + include m_module + end + + foo.call.should == 'n' + end + + it "updates the constant when an included module is updated" do + module ModuleSpecs::ConstUpdated + module A + FOO = 'a' + end + + module M + end + + module B + include A + include M + def self.foo + FOO + end + end + + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdated) + end + + it "updates the constant when a module included after a call is later updated" do + module ModuleSpecs::ConstLaterUpdated + module A + FOO = 'a' + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + module M + end + B.include M + + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstLaterUpdated) + end + + it "updates the constant when a module included in another module after a call is later updated" do + module ModuleSpecs::ConstModuleLaterUpdated + module A + FOO = 'a' + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + module M + end + B.include M + + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstModuleLaterUpdated) + end + + it "updates the constant when a nested included module is updated" do + module ModuleSpecs::ConstUpdatedNestedIncludeUpdated + module A + FOO = 'a' + end + + module N + end + + module M + include N + end + + module B + include A + include M + def self.foo + FOO + end + end + + B.foo.should == 'a' + + N.const_set(:FOO, 'n') + B.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNestedIncludeUpdated) + end + + it "updates the constant when a new module is included" do + module ModuleSpecs::ConstUpdatedNewInclude + module A + FOO = 'a' + end + + module M + FOO = 'm' + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + B.include(M) + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNewInclude) + end + + it "updates the constant when a new module with nested module is included" do + module ModuleSpecs::ConstUpdatedNestedIncluded + module A + FOO = 'a' + end + + module N + FOO = 'n' + end + + module M + include N + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + B.include M + B.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNestedIncluded) + end + + it "overrides a previous super method call" do + c1 = Class.new do + def foo + [:c1] + end + end + c2 = Class.new(c1) do + def foo + [:c2] + super + end + end + c2.new.foo.should == [:c2, :c1] + m = Module.new do + def foo + [:m1] + end + end + c2.include(m) + c2.new.foo.should == [:c2, :m1] + end + + it "update a module when a nested module is updated and includes a module on its own" do + m1 = Module.new + m2 = Module.new do + def m2; [:m2]; end + end + m3 = Module.new do + def m3; [:m3]; end + end + m4 = Module.new do + def m4; [:m4]; end + end + c = Class.new + + c.include(m1) + m1.include(m2) + m2.include(m3) + m3.include(m4) + + c.new.m2.should == [:m2] + c.new.m3.should == [:m3] + c.new.m4.should == [:m4] + end +end + +describe "Module#include?" do + it "returns true if the given module is included by self or one of it's ancestors" do + ModuleSpecs::Super.include?(ModuleSpecs::Basic).should == true + ModuleSpecs::Child.include?(ModuleSpecs::Basic).should == true + ModuleSpecs::Child.include?(ModuleSpecs::Super).should == true + ModuleSpecs::Child.include?(Kernel).should == true + + ModuleSpecs::Parent.include?(ModuleSpecs::Basic).should == false + ModuleSpecs::Basic.include?(ModuleSpecs::Super).should == false + end + + it "returns false if given module is equal to self" do + ModuleSpecs.include?(ModuleSpecs).should == false + end + + it "raises a TypeError when no module was given" do + -> { ModuleSpecs::Child.include?("Test") }.should raise_error(TypeError) + -> { ModuleSpecs::Child.include?(ModuleSpecs::Parent) }.should raise_error(TypeError) + end +end |
