File: shared_example_group.rb

package info (click to toggle)
ruby-rspec 3.4.0c3e0m1s1-1~bpo8%2B1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-backports
  • size: 6,124 kB
  • sloc: ruby: 59,418; sh: 1,405; makefile: 98
file content (212 lines) | stat: -rw-r--r-- 7,643 bytes parent folder | download
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
module RSpec
  module Core
    # Represents some functionality that is shared with multiple example groups.
    # The functionality is defined by the provided block, which is lazily
    # eval'd when the `SharedExampleGroupModule` instance is included in an example
    # group.
    class SharedExampleGroupModule < Module
      def initialize(description, definition)
        @description = description
        @definition  = definition
      end

      # Provides a human-readable representation of this module.
      def inspect
        "#<#{self.class.name} #{@description.inspect}>"
      end
      alias to_s inspect

      # Ruby callback for when a module is included in another module is class.
      # Our definition evaluates the shared group block in the context of the
      # including example group.
      def included(klass)
        inclusion_line = klass.metadata[:location]
        SharedExampleGroupInclusionStackFrame.with_frame(@description, inclusion_line) do
          klass.class_exec(&@definition)
        end
      end
    end

    # Shared example groups let you define common context and/or common
    # examples that you wish to use in multiple example groups.
    #
    # When defined, the shared group block is stored for later evaluation.
    # It can later be included in an example group either explicitly
    # (using `include_examples`, `include_context` or `it_behaves_like`)
    # or implicitly (via matching metadata).
    #
    # Named shared example groups are scoped based on where they are
    # defined. Shared groups defined in an example group are available
    # for inclusion in that example group or any child example groups,
    # but not in any parent or sibling example groups. Shared example
    # groups defined at the top level can be included from any example group.
    module SharedExampleGroup
      # @overload shared_examples(name, &block)
      #   @param name [String, Symbol, Module] identifer to use when looking up
      #     this shared group
      #   @param block The block to be eval'd
      # @overload shared_examples(name, metadata, &block)
      #   @param name [String, Symbol, Module] identifer to use when looking up
      #     this shared group
      #   @param metadata [Array<Symbol>, Hash] metadata to attach to this
      #     group; any example group or example with matching metadata will
      #     automatically include this shared example group.
      #   @param block The block to be eval'd
      # @overload shared_examples(metadata, &block)
      #   @param metadata [Array<Symbol>, Hash] metadata to attach to this
      #     group; any example group or example with matching metadata will
      #     automatically include this shared example group.
      #   @param block The block to be eval'd
      #
      # Stores the block for later use. The block will be evaluated
      # in the context of an example group via `include_examples`,
      # `include_context`, or `it_behaves_like`.
      #
      # @example
      #   shared_examples "auditable" do
      #     it "stores an audit record on save!" do
      #       expect { auditable.save! }.to change(Audit, :count).by(1)
      #     end
      #   end
      #
      #   describe Account do
      #     it_behaves_like "auditable" do
      #       let(:auditable) { Account.new }
      #     end
      #   end
      #
      # @see ExampleGroup.it_behaves_like
      # @see ExampleGroup.include_examples
      # @see ExampleGroup.include_context
      def shared_examples(name, *args, &block)
        top_level = self == ExampleGroup
        if top_level && RSpec::Support.thread_local_data[:in_example_group]
          raise "Creating isolated shared examples from within a context is " \
                "not allowed. Remove `RSpec.` prefix or move this to a " \
                "top-level scope."
        end

        RSpec.world.shared_example_group_registry.add(self, name, *args, &block)
      end
      alias shared_context      shared_examples
      alias shared_examples_for shared_examples

      # @api private
      #
      # Shared examples top level DSL.
      module TopLevelDSL
        # @private
        # rubocop:disable Lint/NestedMethodDefinition
        def self.definitions
          proc do
            def shared_examples(name, *args, &block)
              RSpec.world.shared_example_group_registry.add(:main, name, *args, &block)
            end
            alias shared_context      shared_examples
            alias shared_examples_for shared_examples
          end
        end
        # rubocop:enable Lint/NestedMethodDefinition

        # @private
        def self.exposed_globally?
          @exposed_globally ||= false
        end

        # @api private
        #
        # Adds the top level DSL methods to Module and the top level binding.
        def self.expose_globally!
          return if exposed_globally?
          Core::DSL.change_global_dsl(&definitions)
          @exposed_globally = true
        end

        # @api private
        #
        # Removes the top level DSL methods to Module and the top level binding.
        def self.remove_globally!
          return unless exposed_globally?

          Core::DSL.change_global_dsl do
            undef shared_examples
            undef shared_context
            undef shared_examples_for
          end

          @exposed_globally = false
        end
      end

      # @private
      class Registry
        def add(context, name, *metadata_args, &block)
          ensure_block_has_source_location(block) { CallerFilter.first_non_rspec_line }

          if valid_name?(name)
            warn_if_key_taken context, name, block
            shared_example_groups[context][name] = block
          else
            metadata_args.unshift name
          end

          return if metadata_args.empty?
          RSpec.configuration.include SharedExampleGroupModule.new(name, block), *metadata_args
        end

        def find(lookup_contexts, name)
          lookup_contexts.each do |context|
            found = shared_example_groups[context][name]
            return found if found
          end

          shared_example_groups[:main][name]
        end

      private

        def shared_example_groups
          @shared_example_groups ||= Hash.new { |hash, context| hash[context] = {} }
        end

        def valid_name?(candidate)
          case candidate
          when String, Symbol, Module then true
          else false
          end
        end

        def warn_if_key_taken(context, key, new_block)
          existing_block = shared_example_groups[context][key]

          return unless existing_block

          RSpec.warn_with <<-WARNING.gsub(/^ +\|/, ''), :call_site => nil
            |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

        if Proc.method_defined?(:source_location)
          def ensure_block_has_source_location(_block); end
        else # for 1.8.7
          # :nocov:
          def ensure_block_has_source_location(block)
            source_location = yield.split(':')
            block.extend Module.new { define_method(:source_location) { source_location } }
          end
          # :nocov:
        end
      end
    end
  end

  instance_exec(&Core::SharedExampleGroup::TopLevelDSL.definitions)
end