1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
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)
SharedExampleGroup.registry.add_group(self, *args, &block)
end
alias_method :shared_context, :shared_examples
alias_method :share_examples_for, :shared_examples
alias_method :shared_examples_for, :shared_examples
# @deprecated
def share_as(name, &block)
RSpec.deprecate("Rspec::Core::SharedExampleGroup#share_as",
:replacement => "RSpec::SharedContext or shared_examples")
SharedExampleGroup.registry.add_const(self, name, &block)
end
def shared_example_groups
SharedExampleGroup.registry.shared_example_groups_for('main', *ancestors[0..-1])
end
module TopLevelDSL
def shared_examples(*args, &block)
SharedExampleGroup.registry.add_group('main', *args, &block)
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)
RSpec.deprecate("Rspec::Core::SharedExampleGroup#share_as",
:replacement => "RSpec::SharedContext or shared_examples")
SharedExampleGroup.registry.add_const('main', name, &block)
end
def shared_example_groups
SharedExampleGroup.registry.shared_example_groups_for('main')
end
end
def self.registry
@registry ||= Registry.new
end
# @private
#
# Used internally to manage the shared example groups and
# constants. We want to limit the number of methods we add
# to objects we don't own (main and Module) so this allows
# us to have helper methods that don't get added to those
# objects.
class Registry
def add_group(source, *args, &block)
ensure_block_has_source_location(block, caller[1])
if key? args.first
key = args.shift
warn_if_key_taken source, key, block
add_shared_example_group source, 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
def add_const(source, 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)
add_shared_example_group source, shared_const, block
end
def shared_example_groups_for(*sources)
Collection.new(sources, shared_example_groups)
end
def shared_example_groups
@shared_example_groups ||= Hash.new { |hash,key| hash[key] = Hash.new }
end
def clear
shared_example_groups.clear
end
private
def add_shared_example_group(source, key, block)
shared_example_groups[source][key] = block
end
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 warn_if_key_taken(source, key, new_block)
return unless existing_block = example_block_for(source, key)
Kernel.warn <<-WARNING.gsub(/^ +\|/, '')
|WARNING: Shared example group '#{key}' has been previously defined at:
| #{formatted_location existing_block}
|...and you are now defining it at:
| #{formatted_location new_block}
|The new definition will overwrite the original one.
WARNING
end
def formatted_location(block)
block.source_location.join ":"
end
def example_block_for(source, key)
shared_example_groups[source][key]
end
def ensure_block_has_source_location(block, caller_line)
return if block.respond_to?(:source_location)
block.extend Module.new {
define_method :source_location do
caller_line.split(':')
end
}
end
end
end
end
end
extend RSpec::Core::SharedExampleGroup::TopLevelDSL
Module.send(:include, RSpec::Core::SharedExampleGroup::TopLevelDSL)
|