File: expectation_target.rb

package info (click to toggle)
ruby-rspec 3.13.0c0e0m0s1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,856 kB
  • sloc: ruby: 70,868; sh: 1,423; makefile: 99
file content (163 lines) | stat: -rw-r--r-- 6,022 bytes parent folder | download | duplicates (2)
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
module RSpec
  module Expectations
    # Wraps the target of an expectation.
    #
    # @example
    #   expect(something)       # => ExpectationTarget wrapping something
    #   expect { do_something } # => ExpectationTarget wrapping the block
    #
    #   # used with `to`
    #   expect(actual).to eq(3)
    #
    #   # with `not_to`
    #   expect(actual).not_to eq(3)
    #
    # @note `ExpectationTarget` is not intended to be instantiated
    #   directly by users. Use `expect` instead.
    class ExpectationTarget
      # @private
      # Used as a sentinel value to be able to tell when the user
      # did not pass an argument. We can't use `nil` for that because
      # `nil` is a valid value to pass.
      UndefinedValue = Module.new

      # @note this name aligns with `Minitest::Expectation` so that our
      #   {InstanceMethods} module can be included in that class when
      #   used in a Minitest context.
      # @return [Object] the target of the expectation
      attr_reader :target

      # @api private
      def initialize(value)
        @target = value
      end

      # @private
      def self.for(value, block)
        if UndefinedValue.equal?(value)
          unless block
            raise ArgumentError, "You must pass either an argument or a block to `expect`."
          end
          BlockExpectationTarget.new(block)
        elsif block
          raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
        else
          ValueExpectationTarget.new(value)
        end
      end

      # Defines instance {ExpectationTarget} instance methods. These are defined
      # in a module so we can include it in `Minitest::Expectation` when
      # `rspec/expectations/minitest_integration` is loaded in order to
      # support usage with Minitest.
      module InstanceMethods
        # Runs the given expectation, passing if `matcher` returns true.
        # @example
        #   expect(value).to eq(5)
        #   expect { perform }.to raise_error
        # @param [Matcher]
        #   matcher
        # @param [String, Proc] message optional message to display when the expectation fails
        # @return [Boolean] true if the expectation succeeds (else raises)
        # @see RSpec::Matchers
        def to(matcher=nil, message=nil, &block)
          prevent_operator_matchers(:to) unless matcher
          RSpec::Expectations::PositiveExpectationHandler.handle_matcher(target, matcher, message, &block)
        end

        # Runs the given expectation, passing if `matcher` returns false.
        # @example
        #   expect(value).not_to eq(5)
        # @param [Matcher]
        #   matcher
        # @param [String, Proc] message optional message to display when the expectation fails
        # @return [Boolean] false if the negative expectation succeeds (else raises)
        # @see RSpec::Matchers
        def not_to(matcher=nil, message=nil, &block)
          prevent_operator_matchers(:not_to) unless matcher
          RSpec::Expectations::NegativeExpectationHandler.handle_matcher(target, matcher, message, &block)
        end
        alias to_not not_to

      private

        def prevent_operator_matchers(verb)
          raise ArgumentError, "The expect syntax does not support operator matchers, " \
                               "so you must pass a matcher to `##{verb}`."
        end
      end

      include InstanceMethods
    end

    # @private
    # Validates the provided matcher to ensure it supports block
    # expectations, in order to avoid user confusion when they
    # use a block thinking the expectation will be on the return
    # value of the block rather than the block itself.
    class ValueExpectationTarget < ExpectationTarget
      def to(matcher=nil, message=nil, &block)
        enforce_value_expectation(matcher)
        super
      end

      def not_to(matcher=nil, message=nil, &block)
        enforce_value_expectation(matcher)
        super
      end

    private

      def enforce_value_expectation(matcher)
        return if supports_value_expectations?(matcher)

        RSpec.deprecate(
          "expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}",
          :message =>
            "The implicit block expectation syntax is deprecated, you should pass " \
            "a block rather than an argument to `expect` to use the provided " \
            "block expectation matcher or the matcher must implement " \
            "`supports_value_expectations?`. e.g  `expect { value }.to " \
            "#{RSpec::Support::ObjectFormatter.format(matcher)}` not " \
            "`expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}`"
        )
      end

      def supports_value_expectations?(matcher)
        !matcher.respond_to?(:supports_value_expectations?) || matcher.supports_value_expectations?
      end
    end

    # @private
    # Validates the provided matcher to ensure it supports block
    # expectations, in order to avoid user confusion when they
    # use a block thinking the expectation will be on the return
    # value of the block rather than the block itself.
    class BlockExpectationTarget < ExpectationTarget
      def to(matcher, message=nil, &block)
        enforce_block_expectation(matcher)
        super
      end

      def not_to(matcher, message=nil, &block)
        enforce_block_expectation(matcher)
        super
      end
      alias to_not not_to

    private

      def enforce_block_expectation(matcher)
        return if supports_block_expectations?(matcher)

        raise ExpectationNotMetError, "You must pass an argument rather than a block to `expect` to use the provided " \
          "matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \
          "`supports_block_expectations?`."
      end

      def supports_block_expectations?(matcher)
        matcher.respond_to?(:supports_block_expectations?) && matcher.supports_block_expectations?
      end
    end
  end
end