opal-rspec-cj 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.gitmodules +15 -0
  4. data/.travis.yml +13 -0
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +25 -0
  7. data/Gemfile +8 -0
  8. data/README.md +147 -0
  9. data/Rakefile +26 -0
  10. data/config.ru +10 -0
  11. data/example/Gemfile +4 -0
  12. data/example/README.md +13 -0
  13. data/example/Rakefile +8 -0
  14. data/example/opal/user.rb +11 -0
  15. data/example/spec/user_spec.rb +15 -0
  16. data/lib/opal-rspec.rb +2 -0
  17. data/lib/opal/rspec.rb +20 -0
  18. data/lib/opal/rspec/rake_task.rb +63 -0
  19. data/lib/opal/rspec/version.rb +5 -0
  20. data/opal-rspec.gemspec +21 -0
  21. data/opal/opal-rspec.rb +1 -0
  22. data/opal/opal/rspec.rb +25 -0
  23. data/opal/opal/rspec/async.rb +289 -0
  24. data/opal/opal/rspec/browser_formatter.rb +188 -0
  25. data/opal/opal/rspec/fixes.rb +116 -0
  26. data/opal/opal/rspec/requires.rb +45 -0
  27. data/opal/opal/rspec/runner.rb +69 -0
  28. data/opal/opal/rspec/sprockets_runner.rb.erb +11 -0
  29. data/opal/opal/rspec/text_formatter.rb +74 -0
  30. data/spec/async_spec.rb +38 -0
  31. data/spec/example_spec.rb +163 -0
  32. data/spec/matchers_spec.rb +201 -0
  33. data/spec/mock_spec.rb +63 -0
  34. data/spec/named_subject_spec.rb +11 -0
  35. data/spec/should_syntax_spec.rb +17 -0
  36. data/vendor/spec_runner.js +50 -0
  37. data/vendor_lib/rspec-expectations.rb +1 -0
  38. data/vendor_lib/rspec.rb +3 -0
  39. data/vendor_lib/rspec/autorun.rb +2 -0
  40. data/vendor_lib/rspec/core.rb +203 -0
  41. data/vendor_lib/rspec/core/backport_random.rb +302 -0
  42. data/vendor_lib/rspec/core/backtrace_formatter.rb +65 -0
  43. data/vendor_lib/rspec/core/command_line.rb +36 -0
  44. data/vendor_lib/rspec/core/configuration.rb +1129 -0
  45. data/vendor_lib/rspec/core/configuration_options.rb +143 -0
  46. data/vendor_lib/rspec/core/drb_command_line.rb +26 -0
  47. data/vendor_lib/rspec/core/drb_options.rb +87 -0
  48. data/vendor_lib/rspec/core/dsl.rb +26 -0
  49. data/vendor_lib/rspec/core/example.rb +312 -0
  50. data/vendor_lib/rspec/core/example_group.rb +540 -0
  51. data/vendor_lib/rspec/core/filter_manager.rb +224 -0
  52. data/vendor_lib/rspec/core/flat_map.rb +17 -0
  53. data/vendor_lib/rspec/core/formatters.rb +54 -0
  54. data/vendor_lib/rspec/core/formatters/base_formatter.rb +291 -0
  55. data/vendor_lib/rspec/core/formatters/base_text_formatter.rb +307 -0
  56. data/vendor_lib/rspec/core/formatters/deprecation_formatter.rb +193 -0
  57. data/vendor_lib/rspec/core/formatters/documentation_formatter.rb +67 -0
  58. data/vendor_lib/rspec/core/formatters/helpers.rb +82 -0
  59. data/vendor_lib/rspec/core/formatters/html_formatter.rb +155 -0
  60. data/vendor_lib/rspec/core/formatters/html_printer.rb +408 -0
  61. data/vendor_lib/rspec/core/formatters/json_formatter.rb +99 -0
  62. data/vendor_lib/rspec/core/formatters/progress_formatter.rb +32 -0
  63. data/vendor_lib/rspec/core/formatters/snippet_extractor.rb +101 -0
  64. data/vendor_lib/rspec/core/hooks.rb +535 -0
  65. data/vendor_lib/rspec/core/memoized_helpers.rb +431 -0
  66. data/vendor_lib/rspec/core/metadata.rb +313 -0
  67. data/vendor_lib/rspec/core/mocking/with_absolutely_nothing.rb +11 -0
  68. data/vendor_lib/rspec/core/mocking/with_flexmock.rb +27 -0
  69. data/vendor_lib/rspec/core/mocking/with_mocha.rb +52 -0
  70. data/vendor_lib/rspec/core/mocking/with_rr.rb +27 -0
  71. data/vendor_lib/rspec/core/mocking/with_rspec.rb +27 -0
  72. data/vendor_lib/rspec/core/option_parser.rb +234 -0
  73. data/vendor_lib/rspec/core/ordering.rb +154 -0
  74. data/vendor_lib/rspec/core/pending.rb +110 -0
  75. data/vendor_lib/rspec/core/project_initializer.rb +88 -0
  76. data/vendor_lib/rspec/core/rake_task.rb +128 -0
  77. data/vendor_lib/rspec/core/reporter.rb +132 -0
  78. data/vendor_lib/rspec/core/ruby_project.rb +44 -0
  79. data/vendor_lib/rspec/core/runner.rb +97 -0
  80. data/vendor_lib/rspec/core/shared_context.rb +53 -0
  81. data/vendor_lib/rspec/core/shared_example_group.rb +146 -0
  82. data/vendor_lib/rspec/core/shared_example_group/collection.rb +27 -0
  83. data/vendor_lib/rspec/core/version.rb +7 -0
  84. data/vendor_lib/rspec/core/warnings.rb +22 -0
  85. data/vendor_lib/rspec/core/world.rb +131 -0
  86. data/vendor_lib/rspec/expectations.rb +75 -0
  87. data/vendor_lib/rspec/expectations/differ.rb +154 -0
  88. data/vendor_lib/rspec/expectations/errors.rb +9 -0
  89. data/vendor_lib/rspec/expectations/expectation_target.rb +87 -0
  90. data/vendor_lib/rspec/expectations/extensions.rb +1 -0
  91. data/vendor_lib/rspec/expectations/extensions/object.rb +29 -0
  92. data/vendor_lib/rspec/expectations/fail_with.rb +79 -0
  93. data/vendor_lib/rspec/expectations/handler.rb +68 -0
  94. data/vendor_lib/rspec/expectations/syntax.rb +182 -0
  95. data/vendor_lib/rspec/expectations/version.rb +8 -0
  96. data/vendor_lib/rspec/matchers.rb +633 -0
  97. data/vendor_lib/rspec/matchers/built_in.rb +39 -0
  98. data/vendor_lib/rspec/matchers/built_in/base_matcher.rb +68 -0
  99. data/vendor_lib/rspec/matchers/built_in/be.rb +213 -0
  100. data/vendor_lib/rspec/matchers/built_in/be_instance_of.rb +15 -0
  101. data/vendor_lib/rspec/matchers/built_in/be_kind_of.rb +11 -0
  102. data/vendor_lib/rspec/matchers/built_in/be_within.rb +55 -0
  103. data/vendor_lib/rspec/matchers/built_in/change.rb +141 -0
  104. data/vendor_lib/rspec/matchers/built_in/cover.rb +21 -0
  105. data/vendor_lib/rspec/matchers/built_in/eq.rb +22 -0
  106. data/vendor_lib/rspec/matchers/built_in/eql.rb +23 -0
  107. data/vendor_lib/rspec/matchers/built_in/equal.rb +48 -0
  108. data/vendor_lib/rspec/matchers/built_in/exist.rb +26 -0
  109. data/vendor_lib/rspec/matchers/built_in/has.rb +48 -0
  110. data/vendor_lib/rspec/matchers/built_in/include.rb +61 -0
  111. data/vendor_lib/rspec/matchers/built_in/match.rb +17 -0
  112. data/vendor_lib/rspec/matchers/built_in/match_array.rb +51 -0
  113. data/vendor_lib/rspec/matchers/built_in/raise_error.rb +154 -0
  114. data/vendor_lib/rspec/matchers/built_in/respond_to.rb +74 -0
  115. data/vendor_lib/rspec/matchers/built_in/satisfy.rb +30 -0
  116. data/vendor_lib/rspec/matchers/built_in/start_and_end_with.rb +48 -0
  117. data/vendor_lib/rspec/matchers/built_in/throw_symbol.rb +94 -0
  118. data/vendor_lib/rspec/matchers/built_in/yield.rb +297 -0
  119. data/vendor_lib/rspec/matchers/compatibility.rb +14 -0
  120. data/vendor_lib/rspec/matchers/configuration.rb +113 -0
  121. data/vendor_lib/rspec/matchers/dsl.rb +23 -0
  122. data/vendor_lib/rspec/matchers/generated_descriptions.rb +35 -0
  123. data/vendor_lib/rspec/matchers/matcher.rb +301 -0
  124. data/vendor_lib/rspec/matchers/method_missing.rb +12 -0
  125. data/vendor_lib/rspec/matchers/operator_matcher.rb +99 -0
  126. data/vendor_lib/rspec/matchers/pretty.rb +70 -0
  127. data/vendor_lib/rspec/matchers/test_unit_integration.rb +11 -0
  128. data/vendor_lib/rspec/mocks.rb +100 -0
  129. data/vendor_lib/rspec/mocks/any_instance/chain.rb +92 -0
  130. data/vendor_lib/rspec/mocks/any_instance/expectation_chain.rb +47 -0
  131. data/vendor_lib/rspec/mocks/any_instance/message_chains.rb +75 -0
  132. data/vendor_lib/rspec/mocks/any_instance/recorder.rb +200 -0
  133. data/vendor_lib/rspec/mocks/any_instance/stub_chain.rb +45 -0
  134. data/vendor_lib/rspec/mocks/any_instance/stub_chain_chain.rb +23 -0
  135. data/vendor_lib/rspec/mocks/argument_list_matcher.rb +104 -0
  136. data/vendor_lib/rspec/mocks/argument_matchers.rb +264 -0
  137. data/vendor_lib/rspec/mocks/arity_calculator.rb +66 -0
  138. data/vendor_lib/rspec/mocks/configuration.rb +111 -0
  139. data/vendor_lib/rspec/mocks/error_generator.rb +203 -0
  140. data/vendor_lib/rspec/mocks/errors.rb +12 -0
  141. data/vendor_lib/rspec/mocks/example_methods.rb +201 -0
  142. data/vendor_lib/rspec/mocks/extensions/marshal.rb +17 -0
  143. data/vendor_lib/rspec/mocks/framework.rb +36 -0
  144. data/vendor_lib/rspec/mocks/instance_method_stasher.rb +112 -0
  145. data/vendor_lib/rspec/mocks/matchers/have_received.rb +99 -0
  146. data/vendor_lib/rspec/mocks/matchers/receive.rb +112 -0
  147. data/vendor_lib/rspec/mocks/matchers/receive_messages.rb +72 -0
  148. data/vendor_lib/rspec/mocks/message_expectation.rb +643 -0
  149. data/vendor_lib/rspec/mocks/method_double.rb +209 -0
  150. data/vendor_lib/rspec/mocks/method_reference.rb +95 -0
  151. data/vendor_lib/rspec/mocks/mock.rb +7 -0
  152. data/vendor_lib/rspec/mocks/mutate_const.rb +406 -0
  153. data/vendor_lib/rspec/mocks/object_reference.rb +90 -0
  154. data/vendor_lib/rspec/mocks/order_group.rb +82 -0
  155. data/vendor_lib/rspec/mocks/proxy.rb +269 -0
  156. data/vendor_lib/rspec/mocks/proxy_for_nil.rb +37 -0
  157. data/vendor_lib/rspec/mocks/space.rb +95 -0
  158. data/vendor_lib/rspec/mocks/standalone.rb +3 -0
  159. data/vendor_lib/rspec/mocks/stub_chain.rb +51 -0
  160. data/vendor_lib/rspec/mocks/syntax.rb +374 -0
  161. data/vendor_lib/rspec/mocks/targets.rb +90 -0
  162. data/vendor_lib/rspec/mocks/test_double.rb +109 -0
  163. data/vendor_lib/rspec/mocks/verifying_double.rb +77 -0
  164. data/vendor_lib/rspec/mocks/verifying_message_expecation.rb +60 -0
  165. data/vendor_lib/rspec/mocks/verifying_proxy.rb +151 -0
  166. data/vendor_lib/rspec/mocks/version.rb +7 -0
  167. data/vendor_lib/rspec/support.rb +6 -0
  168. data/vendor_lib/rspec/support/caller_filter.rb +56 -0
  169. data/vendor_lib/rspec/support/spec.rb +14 -0
  170. data/vendor_lib/rspec/support/spec/deprecation_helpers.rb +29 -0
  171. data/vendor_lib/rspec/support/spec/in_sub_process.rb +40 -0
  172. data/vendor_lib/rspec/support/spec/stderr_splitter.rb +50 -0
  173. data/vendor_lib/rspec/support/version.rb +7 -0
  174. data/vendor_lib/rspec/support/warnings.rb +41 -0
  175. data/vendor_lib/rspec/version.rb +5 -0
  176. metadata +268 -0
@@ -0,0 +1,209 @@
1
+ module RSpec
2
+ module Mocks
3
+ # @private
4
+ class MethodDouble
5
+ # @private
6
+ attr_reader :method_name, :object, :expectations, :stubs
7
+
8
+ # @private
9
+ def initialize(object, method_name, proxy)
10
+ @method_name = method_name
11
+ @object = object
12
+ @proxy = proxy
13
+
14
+ @original_visibility = nil
15
+ @method_stasher = InstanceMethodStasher.new(object, method_name)
16
+ @method_is_proxied = false
17
+ @expectations = []
18
+ @stubs = []
19
+ end
20
+
21
+ def original_method
22
+ # If original method is not present, uses the `method_missing`
23
+ # handler of the object. This accounts for cases where the user has not
24
+ # correctly defined `respond_to?`, and also 1.8 which does not provide
25
+ # method handles for missing methods even if `respond_to?` is correct.
26
+ @original_method ||=
27
+ @method_stasher.original_method ||
28
+ @proxy.method_handle_for(method_name) ||
29
+ Proc.new do |*args, &block|
30
+ @object.__send__(:method_missing, @method_name, *args, &block)
31
+ end
32
+ end
33
+
34
+ alias_method :save_original_method!, :original_method
35
+
36
+ # @private
37
+ def visibility
38
+ if TestDouble === @object
39
+ 'public'
40
+ elsif object_singleton_class.private_method_defined?(@method_name)
41
+ 'private'
42
+ elsif object_singleton_class.protected_method_defined?(@method_name)
43
+ 'protected'
44
+ else
45
+ 'public'
46
+ end
47
+ end
48
+
49
+ # @private
50
+ def object_singleton_class
51
+ class << @object; self; end
52
+ end
53
+
54
+ # @private
55
+ def configure_method
56
+ @original_visibility = [visibility, method_name]
57
+ @method_stasher.stash unless @method_is_proxied
58
+ define_proxy_method
59
+ end
60
+
61
+ # @private
62
+ def define_proxy_method
63
+ return if @method_is_proxied
64
+
65
+ save_original_method!
66
+
67
+ object_singleton_class.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
68
+ define_method(method_name) do |*args, &block|
69
+ method_double.proxy_method_invoked(self, *args, &block)
70
+ end
71
+ self.__send__ visibility, method_name
72
+ end
73
+
74
+ @method_is_proxied = true
75
+ end
76
+
77
+ # The implementation of the proxied method. Subclasses may override this
78
+ # method to perform additional operations.
79
+ #
80
+ # @private
81
+ def proxy_method_invoked(obj, *args, &block)
82
+ @proxy.message_received method_name, *args, &block
83
+ end
84
+
85
+ # @private
86
+ def restore_original_method
87
+ return unless @method_is_proxied
88
+
89
+ object_singleton_class.__send__(:remove_method, @method_name)
90
+ if @method_stasher.method_is_stashed?
91
+ @method_stasher.restore
92
+ end
93
+ restore_original_visibility
94
+
95
+ @method_is_proxied = false
96
+ end
97
+
98
+ # @private
99
+ def restore_original_visibility
100
+ return unless @original_visibility &&
101
+ (object_singleton_class.method_defined?(@method_name) ||
102
+ object_singleton_class.private_method_defined?(@method_name))
103
+
104
+ object_singleton_class.__send__(*@original_visibility)
105
+ end
106
+
107
+ # @private
108
+ def verify
109
+ expectations.each {|e| e.verify_messages_received}
110
+ end
111
+
112
+ # @private
113
+ def reset
114
+ restore_original_method
115
+ clear
116
+ end
117
+
118
+ # @private
119
+ def clear
120
+ expectations.clear
121
+ stubs.clear
122
+ end
123
+
124
+ # The type of message expectation to create has been extracted to its own
125
+ # method so that subclasses can override it.
126
+ #
127
+ # @private
128
+ def message_expectation_class
129
+ MessageExpectation
130
+ end
131
+
132
+ # @private
133
+ def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
134
+ configure_method
135
+ expectation = message_expectation_class.new(error_generator, expectation_ordering,
136
+ expected_from, self, 1, opts, &implementation)
137
+ expectations << expectation
138
+ expectation
139
+ end
140
+
141
+ # @private
142
+ def build_expectation(error_generator, expectation_ordering)
143
+ expected_from = IGNORED_BACKTRACE_LINE
144
+ message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
145
+ end
146
+
147
+ # @private
148
+ def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
149
+ configure_method
150
+ stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
151
+ self, :any, opts, &implementation)
152
+ stubs.unshift stub
153
+ stub
154
+ end
155
+
156
+ # A simple stub can only return a concrete value for a message, and
157
+ # cannot match on arguments. It is used as an optimization over
158
+ # `add_stub` / `add_expectation` where it is known in advance that this
159
+ # is all that will be required of a stub, such as when passing attributes
160
+ # to the `double` example method. They do not stash or restore existing method
161
+ # definitions.
162
+ #
163
+ # @private
164
+ def add_simple_stub(method_name, response)
165
+ setup_simple_method_double method_name, response, stubs
166
+ end
167
+
168
+ # @private
169
+ def add_simple_expectation(method_name, response, error_generator, backtrace_line)
170
+ setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
171
+ end
172
+
173
+ # @private
174
+ def setup_simple_method_double(method_name, response, collection, error_generator = nil, backtrace_line = nil)
175
+ define_proxy_method
176
+
177
+ me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
178
+ collection.unshift me
179
+ me
180
+ end
181
+
182
+ # @private
183
+ def add_default_stub(*args, &implementation)
184
+ return if stubs.any?
185
+ add_stub(*args, &implementation)
186
+ end
187
+
188
+ # @private
189
+ def remove_stub
190
+ raise_method_not_stubbed_error if stubs.empty?
191
+ expectations.empty? ? reset : stubs.clear
192
+ end
193
+
194
+ # @private
195
+ def remove_single_stub(stub)
196
+ stubs.delete(stub)
197
+ restore_original_method if stubs.empty? && expectations.empty?
198
+ end
199
+
200
+ # @private
201
+ def raise_method_not_stubbed_error
202
+ raise MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
203
+ end
204
+
205
+ # @private
206
+ IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored'
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,95 @@
1
+ module RSpec
2
+ module Mocks
3
+ # Represents a method on a module that may or may not be defined.
4
+ #
5
+ # @private
6
+ class MethodReference
7
+ def initialize(module_reference, method_name)
8
+ @module_reference = module_reference
9
+ @method_name = method_name
10
+ end
11
+
12
+ # A method is implemented if sending the message does not result in
13
+ # a `NoMethodError`. It might be dynamically implemented by
14
+ # `method_missing`.
15
+ def implemented?
16
+ @module_reference.when_loaded do |m|
17
+ method_implemented?(m)
18
+ end
19
+ end
20
+
21
+ # A method is defined if we are able to get a `Method` object for it.
22
+ # In that case, we can assert against metadata like the arity.
23
+ def defined?
24
+ @module_reference.when_loaded do |m|
25
+ method_defined?(m)
26
+ end
27
+ end
28
+
29
+ def when_defined
30
+ if original = original_method
31
+ yield original
32
+ end
33
+ end
34
+
35
+ # Yields to the block if the method is not implemented.
36
+ def when_unimplemented
37
+ yield unless implemented?
38
+ end
39
+
40
+ private
41
+ def original_method
42
+ @module_reference.when_loaded do |m|
43
+ self.defined? && find_method(m)
44
+ end
45
+ end
46
+ end
47
+
48
+ # @private
49
+ class InstanceMethodReference < MethodReference
50
+ private
51
+ def method_implemented?(m)
52
+ m.method_defined?(@method_name)
53
+ end
54
+
55
+ # Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
56
+ # reference to an instance to do that and we don't have one. Note that
57
+ # we may get false negatives: if the method is implemented via
58
+ # `method_missing`, we'll return `false` even though it meets our
59
+ # definition of "implemented". However, it's the best we can do.
60
+ alias method_defined? method_implemented?
61
+
62
+ # works around the fact that repeated calls for method parameters will
63
+ # falsely return empty arrays on JRuby in certain circumstances, this
64
+ # is necessary here because we can't dup/clone UnboundMethods.
65
+ #
66
+ # This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
67
+ # https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
68
+ if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
69
+ def find_method(m)
70
+ m.dup.instance_method(@method_name)
71
+ end
72
+ else
73
+ def find_method(m)
74
+ m.instance_method(@method_name)
75
+ end
76
+ end
77
+ end
78
+
79
+ # @private
80
+ class ObjectMethodReference < MethodReference
81
+ private
82
+ def method_implemented?(m)
83
+ m.respond_to?(@method_name)
84
+ end
85
+
86
+ def method_defined?(m)
87
+ (class << m; self; end).method_defined?(@method_name)
88
+ end
89
+
90
+ def find_method(m)
91
+ m.method(@method_name)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,7 @@
1
+ module RSpec
2
+ module Mocks
3
+ class Mock
4
+ include TestDouble
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,406 @@
1
+ module RSpec
2
+ module Mocks
3
+ # Provides recursive constant lookup methods useful for
4
+ # constant stubbing.
5
+ # @api private
6
+ module RecursiveConstMethods
7
+ # We only want to consider constants that are defined directly on a
8
+ # particular module, and not include top-level/inherited constants.
9
+ # Unfortunately, the constant API changed between 1.8 and 1.9, so
10
+ # we need to conditionally define methods to ignore the top-level/inherited
11
+ # constants.
12
+ #
13
+ # Given:
14
+ # class A; B = 1; end
15
+ # class C < A; end
16
+ #
17
+ # On 1.8:
18
+ # - C.const_get("Hash") # => ::Hash
19
+ # - C.const_defined?("Hash") # => false
20
+ # - C.constants # => ["B"]
21
+ # - None of these methods accept the extra `inherit` argument
22
+ # On 1.9:
23
+ # - C.const_get("Hash") # => ::Hash
24
+ # - C.const_defined?("Hash") # => true
25
+ # - C.const_get("Hash", false) # => raises NameError
26
+ # - C.const_defined?("Hash", false) # => false
27
+ # - C.constants # => [:B]
28
+ # - C.constants(false) #=> []
29
+ if Module.method(:const_defined?).arity == 1
30
+ def const_defined_on?(mod, const_name)
31
+ mod.const_defined?(const_name)
32
+ end
33
+
34
+ def get_const_defined_on(mod, const_name)
35
+ if const_defined_on?(mod, const_name)
36
+ return mod.const_get(const_name)
37
+ end
38
+
39
+ raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
40
+ end
41
+
42
+ def constants_defined_on(mod)
43
+ mod.constants.select { |c| const_defined_on?(mod, c) }
44
+ end
45
+ else
46
+ def const_defined_on?(mod, const_name)
47
+ mod.const_defined?(const_name, false)
48
+ end
49
+
50
+ def get_const_defined_on(mod, const_name)
51
+ mod.const_get(const_name, false)
52
+ end
53
+
54
+ def constants_defined_on(mod)
55
+ mod.constants(false)
56
+ end
57
+ end
58
+
59
+ def recursive_const_get(const_name)
60
+ normalize_const_name(const_name).split('::').inject(Object) do |mod, name|
61
+ get_const_defined_on(mod, name)
62
+ end
63
+ end
64
+
65
+ def recursive_const_defined?(const_name)
66
+ normalize_const_name(const_name).split('::').inject([Object, '']) do |(mod, full_name), name|
67
+ yield(full_name, name) if block_given? && !(Module === mod)
68
+ return false unless const_defined_on?(mod, name)
69
+ [get_const_defined_on(mod, name), [mod, name].join('::')]
70
+ end
71
+ end
72
+
73
+ def normalize_const_name(const_name)
74
+ const_name.sub(/\A::/, '')
75
+ end
76
+ end
77
+
78
+ # Provides information about constants that may (or may not)
79
+ # have been mutated by rspec-mocks.
80
+ class Constant
81
+ extend RecursiveConstMethods
82
+
83
+ # @api private
84
+ def initialize(name)
85
+ @name = name
86
+ @previously_defined = false
87
+ @stubbed = false
88
+ @hidden = false
89
+ end
90
+
91
+ # @return [String] The fully qualified name of the constant.
92
+ attr_reader :name
93
+
94
+ # @return [Object, nil] The original value (e.g. before it
95
+ # was mutated by rspec-mocks) of the constant, or
96
+ # nil if the constant was not previously defined.
97
+ attr_accessor :original_value
98
+
99
+ # @api private
100
+ attr_writer :previously_defined, :stubbed, :hidden
101
+
102
+ # @return [Boolean] Whether or not the constant was defined
103
+ # before the current example.
104
+ def previously_defined?
105
+ @previously_defined
106
+ end
107
+
108
+ # @return [Boolean] Whether or not rspec-mocks has mutated
109
+ # (stubbed or hidden) this constant.
110
+ def mutated?
111
+ @stubbed || @hidden
112
+ end
113
+
114
+ # @return [Boolean] Whether or not rspec-mocks has stubbed
115
+ # this constant.
116
+ def stubbed?
117
+ @stubbed
118
+ end
119
+
120
+ # @return [Boolean] Whether or not rspec-mocks has hidden
121
+ # this constant.
122
+ def hidden?
123
+ @hidden
124
+ end
125
+
126
+ def to_s
127
+ "#<#{self.class.name} #{name}>"
128
+ end
129
+ alias inspect to_s
130
+
131
+ # @api private
132
+ def self.unmutated(name)
133
+ const = new(name)
134
+ const.previously_defined = recursive_const_defined?(name)
135
+ const.stubbed = false
136
+ const.hidden = false
137
+ const.original_value = recursive_const_get(name) if const.previously_defined?
138
+
139
+ const
140
+ end
141
+ private_class_method :unmutated
142
+
143
+ # Queries rspec-mocks to find out information about the named constant.
144
+ #
145
+ # @param [String] name the name of the constant
146
+ # @return [Constant] an object contaning information about the named
147
+ # constant.
148
+ def self.original(name)
149
+ mutator = ConstantMutator.find(name)
150
+ mutator ? mutator.to_constant : unmutated(name)
151
+ end
152
+ end
153
+
154
+ # Provides a means to stub constants.
155
+ class ConstantMutator
156
+ extend RecursiveConstMethods
157
+
158
+ # Stubs a constant.
159
+ #
160
+ # @param (see ExampleMethods#stub_const)
161
+ # @option (see ExampleMethods#stub_const)
162
+ # @return (see ExampleMethods#stub_const)
163
+ #
164
+ # @see ExampleMethods#stub_const
165
+ # @note It's recommended that you use `stub_const` in your
166
+ # examples. This is an alternate public API that is provided
167
+ # so you can stub constants in other contexts (e.g. helper
168
+ # classes).
169
+ def self.stub(constant_name, value, options = {})
170
+ mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
171
+ DefinedConstantReplacer
172
+ else
173
+ UndefinedConstantSetter
174
+ end
175
+
176
+ mutate(mutator.new(constant_name, value, options.fetch(
177
+ :transfer_nested_constants,
178
+ RSpec::Mocks.configuration.transfer_nested_constants?
179
+ )))
180
+ value
181
+ end
182
+
183
+ # Hides a constant.
184
+ #
185
+ # @param (see ExampleMethods#hide_const)
186
+ #
187
+ # @see ExampleMethods#hide_const
188
+ # @note It's recommended that you use `hide_const` in your
189
+ # examples. This is an alternate public API that is provided
190
+ # so you can hide constants in other contexts (e.g. helper
191
+ # classes).
192
+ def self.hide(constant_name)
193
+ return unless recursive_const_defined?(constant_name)
194
+
195
+ mutate(ConstantHider.new(constant_name, nil, { }))
196
+ nil
197
+ end
198
+
199
+ # Contains common functionality used by all of the constant mutators.
200
+ #
201
+ # @api private
202
+ class BaseMutator
203
+ include RecursiveConstMethods
204
+
205
+ attr_reader :original_value, :full_constant_name
206
+
207
+ def initialize(full_constant_name, mutated_value, transfer_nested_constants)
208
+ @full_constant_name = normalize_const_name(full_constant_name)
209
+ @mutated_value = mutated_value
210
+ @transfer_nested_constants = transfer_nested_constants
211
+ @context_parts = @full_constant_name.split('::')
212
+ @const_name = @context_parts.pop
213
+ end
214
+
215
+ def to_constant
216
+ const = Constant.new(full_constant_name)
217
+ const.original_value = original_value
218
+
219
+ const
220
+ end
221
+ end
222
+
223
+ # Hides a defined constant for the duration of an example.
224
+ #
225
+ # @api private
226
+ class ConstantHider < BaseMutator
227
+ def mutate
228
+ @context = recursive_const_get(@context_parts.join('::'))
229
+ @original_value = get_const_defined_on(@context, @const_name)
230
+
231
+ @context.__send__(:remove_const, @const_name)
232
+ end
233
+
234
+ def to_constant
235
+ const = super
236
+ const.hidden = true
237
+ const.previously_defined = true
238
+
239
+ const
240
+ end
241
+
242
+ def rspec_reset
243
+ @context.const_set(@const_name, @original_value)
244
+ end
245
+ end
246
+
247
+ # Replaces a defined constant for the duration of an example.
248
+ #
249
+ # @api private
250
+ class DefinedConstantReplacer < BaseMutator
251
+ def mutate
252
+ @context = recursive_const_get(@context_parts.join('::'))
253
+ @original_value = get_const_defined_on(@context, @const_name)
254
+
255
+ constants_to_transfer = verify_constants_to_transfer!
256
+
257
+ @context.__send__(:remove_const, @const_name)
258
+ @context.const_set(@const_name, @mutated_value)
259
+
260
+ transfer_nested_constants(constants_to_transfer)
261
+ end
262
+
263
+ def to_constant
264
+ const = super
265
+ const.stubbed = true
266
+ const.previously_defined = true
267
+
268
+ const
269
+ end
270
+
271
+ def rspec_reset
272
+ @context.__send__(:remove_const, @const_name)
273
+ @context.const_set(@const_name, @original_value)
274
+ end
275
+
276
+ def transfer_nested_constants(constants)
277
+ constants.each do |const|
278
+ @mutated_value.const_set(const, get_const_defined_on(original_value, const))
279
+ end
280
+ end
281
+
282
+ def verify_constants_to_transfer!
283
+ return [] unless @transfer_nested_constants
284
+
285
+ { @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
286
+ unless value.respond_to?(:constants)
287
+ raise ArgumentError,
288
+ "Cannot transfer nested constants for #{@full_constant_name} " +
289
+ "since #{description} is not a class or module and only classes " +
290
+ "and modules support nested constants."
291
+ end
292
+ end
293
+
294
+ if Array === @transfer_nested_constants
295
+ @transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
296
+ undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
297
+
298
+ if undefined_constants.any?
299
+ available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
300
+ raise ArgumentError,
301
+ "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
302
+ "for #{@full_constant_name} since they are not defined. Did you mean " +
303
+ "#{available_constants.join(' or ')}?"
304
+ end
305
+
306
+ @transfer_nested_constants
307
+ else
308
+ constants_defined_on(@original_value)
309
+ end
310
+ end
311
+ end
312
+
313
+ # Sets an undefined constant for the duration of an example.
314
+ #
315
+ # @api private
316
+ class UndefinedConstantSetter < BaseMutator
317
+ def mutate
318
+ remaining_parts = @context_parts.dup
319
+ @deepest_defined_const = @context_parts.inject(Object) do |klass, name|
320
+ break klass unless const_defined_on?(klass, name)
321
+ remaining_parts.shift
322
+ get_const_defined_on(klass, name)
323
+ end
324
+
325
+ context = remaining_parts.inject(@deepest_defined_const) do |klass, name|
326
+ klass.const_set(name, Module.new)
327
+ end
328
+
329
+ @const_to_remove = remaining_parts.first || @const_name
330
+ context.const_set(@const_name, @mutated_value)
331
+ end
332
+
333
+ def to_constant
334
+ const = super
335
+ const.stubbed = true
336
+ const.previously_defined = false
337
+
338
+ const
339
+ end
340
+
341
+ def rspec_reset
342
+ @deepest_defined_const.__send__(:remove_const, @const_to_remove)
343
+ end
344
+ end
345
+
346
+ # Uses the mutator to mutate (stub or hide) a constant. Ensures that
347
+ # the mutator is correctly registered so it can be backed out at the end
348
+ # of the test.
349
+ #
350
+ # @api private
351
+ def self.mutate(mutator)
352
+ register_mutator(mutator)
353
+ mutator.mutate
354
+ end
355
+
356
+ # Resets all stubbed constants. This is called automatically
357
+ # by rspec-mocks when an example finishes.
358
+ #
359
+ # @api private
360
+ def self.reset_all
361
+ # We use reverse order so that if the same constant
362
+ # was stubbed multiple times, the original value gets
363
+ # properly restored.
364
+ mutators.reverse.each { |s| s.rspec_reset }
365
+
366
+ mutators.clear
367
+ end
368
+
369
+ # The list of constant mutators that have been used for
370
+ # the current example.
371
+ #
372
+ # @api private
373
+ def self.mutators
374
+ @mutators ||= []
375
+ end
376
+
377
+ # @api private
378
+ def self.register_mutator(mutator)
379
+ mutators << mutator
380
+ end
381
+
382
+ def self.find(name)
383
+ mutators.find { |s| s.full_constant_name == name }
384
+ end
385
+
386
+ # Used internally by the constant stubbing to raise a helpful
387
+ # error when a constant like "A::B::C" is stubbed and A::B is
388
+ # not a module (and thus, it's impossible to define "A::B::C"
389
+ # since only modules can have nested constants).
390
+ #
391
+ # @api private
392
+ def self.raise_on_invalid_const
393
+ lambda do |const_name, failed_name|
394
+ raise "Cannot stub constant #{failed_name} on #{const_name} " +
395
+ "since #{const_name} is not a module."
396
+ end
397
+ end
398
+ end
399
+
400
+ # Keeps backwards compatibility since we had released an rspec-mocks that
401
+ # only supported stubbing. Later, we released the hide_const feature and
402
+ # decided that the term "mutator" was a better term to wrap up the concept
403
+ # of both stubbing and hiding.
404
+ ConstantStubber = ConstantMutator
405
+ end
406
+ end