lib/rspec/core/shared_example_group.rb



module RSpec
  module Core
    module SharedExampleGroup
      # @overload shared_examples(name, &block)
      # @overload shared_examples(name, tags, &block)
      #
      # Wraps the `block` in a module which can then be included in example
      # groups using `include_examples`, `include_context`, or
      # `it_behaves_like`.
      #
      # @param [String] name to match when looking up this shared group
      # @param block to be eval'd in a nested example group generated by `it_behaves_like`
      #
      # @example
      #
      #   shared_examples "auditable" do
      #     it "stores an audit record on save!" do
      #       lambda { auditable.save! }.should change(Audit, :count).by(1)
      #     end
      #   end
      #
      #   class Account do
      #     it_behaves_like "auditable" do
      #       def auditable; Account.new; end
      #     end
      #   end
      #
      # @see ExampleGroup.it_behaves_like
      # @see ExampleGroup.include_examples
      # @see ExampleGroup.include_context
      def shared_examples *args, &block
        if key? args.first
          key = args.shift
          raise_key_taken key if key_taken? key
          RSpec.world.shared_example_groups[key] = block
        end

        unless args.empty?
          mod = Module.new
          (class << mod; self; end).send :define_method, :extended  do |host|
            host.class_eval &block
          end
          RSpec.configuration.extend mod, *args
        end
      end

      alias_method :shared_context,      :shared_examples
      alias_method :share_examples_for,  :shared_examples
      alias_method :shared_examples_for, :shared_examples

      def share_as(name, &block)
        if Object.const_defined?(name)
          mod = Object.const_get(name)
          raise_name_error unless mod.created_from_caller(caller)
        end

        mod = Module.new do
          @shared_block = block
          @caller_line = caller.last

          def self.created_from_caller(other_caller)
            @caller_line == other_caller.last
          end

          def self.included(kls)
            kls.describe(&@shared_block)
            kls.children.first.metadata[:shared_group_name] = name
          end
        end

        shared_const = Object.const_set(name, mod)
        RSpec.world.shared_example_groups[shared_const] = block
      end

    private

      def key? candidate
        [String, Symbol, Module].any? { |cls| cls === candidate }
      end

      def raise_name_error
        raise NameError, "The first argument (#{name}) to share_as must be a legal name for a constant not already in use."
      end

      def raise_key_taken key
        raise ArgumentError, "Shared example group '#{key}' already exists"
      end

      def key_taken? key
        RSpec.world.shared_example_groups.has_key?(key)
      end
    end
  end
end

include RSpec::Core::SharedExampleGroup