mocktail 1.2.3 → 2.0.0

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +6 -5
  3. data/.gitignore +3 -0
  4. data/.standard.yml +8 -0
  5. data/CHANGELOG.md +14 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +98 -25
  8. data/README.md +18 -922
  9. data/Rakefile +0 -1
  10. data/bin/console +1 -2
  11. data/bin/tapioca +29 -0
  12. data/lib/mocktail/collects_calls.rb +2 -0
  13. data/lib/mocktail/debug.rb +13 -10
  14. data/lib/mocktail/dsl.rb +2 -0
  15. data/lib/mocktail/errors.rb +2 -0
  16. data/lib/mocktail/explains_nils.rb +2 -0
  17. data/lib/mocktail/explains_thing.rb +7 -4
  18. data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
  19. data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
  20. data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
  21. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
  22. data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
  23. data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
  24. data/lib/mocktail/handles_dry_call.rb +2 -0
  25. data/lib/mocktail/handles_dry_new_call.rb +2 -0
  26. data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
  27. data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
  28. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
  29. data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
  30. data/lib/mocktail/imitates_type/makes_double.rb +3 -0
  31. data/lib/mocktail/imitates_type.rb +3 -1
  32. data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
  33. data/lib/mocktail/initializes_mocktail.rb +5 -0
  34. data/lib/mocktail/matcher_presentation.rb +4 -2
  35. data/lib/mocktail/matchers/any.rb +4 -3
  36. data/lib/mocktail/matchers/base.rb +10 -2
  37. data/lib/mocktail/matchers/captor.rb +9 -0
  38. data/lib/mocktail/matchers/includes.rb +2 -0
  39. data/lib/mocktail/matchers/includes_hash.rb +9 -0
  40. data/lib/mocktail/matchers/includes_key.rb +9 -0
  41. data/lib/mocktail/matchers/includes_string.rb +9 -0
  42. data/lib/mocktail/matchers/is_a.rb +2 -0
  43. data/lib/mocktail/matchers/matches.rb +2 -0
  44. data/lib/mocktail/matchers/not.rb +2 -0
  45. data/lib/mocktail/matchers/numeric.rb +5 -4
  46. data/lib/mocktail/matchers/that.rb +2 -0
  47. data/lib/mocktail/matchers.rb +3 -0
  48. data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
  49. data/lib/mocktail/records_demonstration.rb +2 -0
  50. data/lib/mocktail/registers_matcher.rb +8 -3
  51. data/lib/mocktail/registers_stubbing.rb +2 -0
  52. data/lib/mocktail/replaces_next.rb +7 -1
  53. data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
  54. data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
  55. data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
  56. data/lib/mocktail/replaces_type.rb +6 -0
  57. data/lib/mocktail/resets_state.rb +2 -0
  58. data/lib/mocktail/share/bind.rb +7 -5
  59. data/lib/mocktail/share/cleans_backtrace.rb +3 -5
  60. data/lib/mocktail/share/creates_identifier.rb +16 -9
  61. data/lib/mocktail/share/determines_matching_calls.rb +4 -2
  62. data/lib/mocktail/share/stringifies_call.rb +6 -2
  63. data/lib/mocktail/share/stringifies_method_name.rb +3 -1
  64. data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
  65. data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
  66. data/lib/mocktail/simulates_argument_error/transforms_params.rb +15 -8
  67. data/lib/mocktail/simulates_argument_error.rb +2 -0
  68. data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
  69. data/lib/mocktail/sorbet/mocktail/debug.rb +54 -0
  70. data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
  71. data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
  72. data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
  73. data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
  74. data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
  75. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  76. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  77. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  78. data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
  79. data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  80. data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
  81. data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
  82. data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  83. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  84. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  85. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  86. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
  87. data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
  88. data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  89. data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
  90. data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
  91. data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
  92. data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
  93. data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
  94. data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
  95. data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
  96. data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
  97. data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
  98. data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
  99. data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
  100. data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
  101. data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
  102. data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
  103. data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
  104. data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
  105. data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
  106. data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
  107. data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
  108. data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
  109. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
  110. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  111. data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  112. data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
  113. data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
  114. data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
  115. data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
  116. data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
  117. data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
  118. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +85 -0
  119. data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
  120. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  121. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  122. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  123. data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
  124. data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
  125. data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
  126. data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
  127. data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
  128. data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
  129. data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
  130. data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
  131. data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
  132. data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
  133. data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
  134. data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
  135. data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
  136. data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
  137. data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
  138. data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
  139. data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
  140. data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
  141. data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
  142. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
  143. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  144. data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
  145. data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  146. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  147. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
  148. data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
  149. data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
  150. data/lib/mocktail/sorbet/mocktail.rb +154 -0
  151. data/lib/mocktail/sorbet.rb +1 -0
  152. data/lib/mocktail/stringifies_method_signature.rb +2 -0
  153. data/lib/mocktail/typed.rb +3 -0
  154. data/lib/mocktail/value/cabinet.rb +8 -1
  155. data/lib/mocktail/value/call.rb +44 -12
  156. data/lib/mocktail/value/demo_config.rb +6 -7
  157. data/lib/mocktail/value/double.rb +6 -7
  158. data/lib/mocktail/value/double_data.rb +11 -7
  159. data/lib/mocktail/value/explanation.rb +28 -3
  160. data/lib/mocktail/value/explanation_data.rb +14 -0
  161. data/lib/mocktail/value/fake_method_data.rb +7 -6
  162. data/lib/mocktail/value/matcher_registry.rb +2 -0
  163. data/lib/mocktail/value/no_explanation_data.rb +16 -0
  164. data/lib/mocktail/value/signature.rb +19 -27
  165. data/lib/mocktail/value/stubbing.rb +11 -12
  166. data/lib/mocktail/value/top_shelf.rb +5 -0
  167. data/lib/mocktail/value/type_replacement.rb +7 -8
  168. data/lib/mocktail/value/type_replacement_data.rb +10 -7
  169. data/lib/mocktail/value/unsatisfying_call.rb +5 -6
  170. data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
  171. data/lib/mocktail/value.rb +5 -2
  172. data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
  173. data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
  174. data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
  175. data/lib/mocktail/verifies_call.rb +3 -0
  176. data/lib/mocktail/version.rb +8 -1
  177. data/lib/mocktail.rb +46 -5
  178. data/mocktail.gemspec +8 -4
  179. data/rbi/mocktail-pregenerated.rbi +1865 -0
  180. data/rbi/mocktail.rbi +77 -0
  181. data/rbi/sorbet-runtime.rbi +29 -0
  182. data/spoom_report.html +1248 -0
  183. metadata +130 -3
@@ -0,0 +1,58 @@
1
+ # typed: strict
2
+
3
+ require_relative "../share/bind"
4
+
5
+ module Mocktail
6
+ class TransformsParams
7
+ extend T::Sig
8
+
9
+ sig { void }
10
+ def initialize
11
+ @grabs_original_method_parameters = T.let(GrabsOriginalMethodParameters.new, GrabsOriginalMethodParameters)
12
+ end
13
+
14
+ sig { params(dry_call: Call, params: T.nilable(T::Array[T::Array[Symbol]])).returns(Signature) }
15
+ def transform(dry_call, params: nil)
16
+ params ||= @grabs_original_method_parameters.grab(dry_call.original_method)
17
+ params = name_unnamed_params(params)
18
+
19
+ Signature.new(
20
+ positional_params: Params.new(
21
+ all: params.select { |t, _|
22
+ [:req, :opt, :rest].any? { |param_type| Bind.call(t, :==, param_type) }
23
+ }.map { |pair| pair.fetch(1) },
24
+ required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |pair| pair.fetch(1) },
25
+ optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |pair| pair.fetch(1) },
26
+ rest: params.find { |t, _| Bind.call(t, :==, :rest) }&.last
27
+ ),
28
+ positional_args: dry_call.args,
29
+
30
+ keyword_params: Params.new(
31
+ all: params.select { |type, _|
32
+ [:keyreq, :key, :keyrest].include?(type)
33
+ }.map { |pair| pair.fetch(1) },
34
+ required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |pair| pair.fetch(1) },
35
+ optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |pair| pair.fetch(1) },
36
+ rest: params.find { |t, _| Bind.call(t, :==, :keyrest) }&.last
37
+ ),
38
+ keyword_args: dry_call.kwargs,
39
+
40
+ block_param: params.find { |t, _| Bind.call(t, :==, :block) }&.last,
41
+ block_arg: dry_call.block
42
+ )
43
+ end
44
+
45
+ private
46
+
47
+ sig { params(params: T::Array[T::Array[Symbol]]).returns(T::Array[T::Array[Symbol]]) }
48
+ def name_unnamed_params(params)
49
+ params.map.with_index { |param, i|
50
+ if param.size == 1
51
+ param + ["unnamed_arg_#{i + 1}".to_sym]
52
+ else
53
+ param
54
+ end
55
+ }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+
3
+ require_relative "simulates_argument_error/transforms_params"
4
+ require_relative "simulates_argument_error/reconciles_args_with_params"
5
+ require_relative "simulates_argument_error/recreates_message"
6
+ require_relative "share/cleans_backtrace"
7
+ require_relative "share/stringifies_call"
8
+
9
+ module Mocktail
10
+ class SimulatesArgumentError
11
+ extend T::Sig
12
+
13
+ sig { void }
14
+ def initialize
15
+ @transforms_params = T.let(TransformsParams.new, TransformsParams)
16
+ @reconciles_args_with_params = T.let(ReconcilesArgsWithParams.new, ReconcilesArgsWithParams)
17
+ @recreates_message = T.let(RecreatesMessage.new, RecreatesMessage)
18
+ @cleans_backtrace = T.let(CleansBacktrace.new, CleansBacktrace)
19
+ @stringifies_call = T.let(StringifiesCall.new, StringifiesCall)
20
+ end
21
+
22
+ sig { params(dry_call: Call).returns(T.nilable(ArgumentError)) }
23
+ def simulate(dry_call)
24
+ signature = @transforms_params.transform(dry_call)
25
+
26
+ unless @reconciles_args_with_params.reconcile(signature)
27
+ @cleans_backtrace.clean(
28
+ ArgumentError.new([
29
+ @recreates_message.recreate(signature),
30
+ "[Mocktail call: `#{@stringifies_call.stringify(dry_call)}']"
31
+ ].join(" "))
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ # typed: strict
2
+
3
+ require_relative "sorbet/mocktail"
@@ -0,0 +1,53 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class StringifiesMethodSignature
5
+ extend T::Sig
6
+
7
+ sig { params(signature: Signature).returns(String) }
8
+ def stringify(signature)
9
+ positional_params = positional(signature)
10
+ keyword_params = keyword(signature)
11
+ block_param = block(signature)
12
+
13
+ "(#{[positional_params, keyword_params, block_param].compact.join(", ")})"
14
+ end
15
+
16
+ private
17
+
18
+ sig { params(signature: Signature).returns(T.nilable(String)) }
19
+ def positional(signature)
20
+ params = signature.positional_params.all.map do |name|
21
+ if signature.positional_params.allowed.include?(name)
22
+ "#{name} = ((__mocktail_default_args ||= {})[:#{name}] = nil)"
23
+ elsif signature.positional_params.rest == name
24
+ "*#{(name == :*) ? Signature::DEFAULT_REST_ARGS : name}"
25
+ end
26
+ end.compact
27
+
28
+ params.join(", ") if params.any?
29
+ end
30
+
31
+ sig { params(signature: Signature).returns(T.nilable(String)) }
32
+ def keyword(signature)
33
+ params = signature.keyword_params.all.map do |name|
34
+ if signature.keyword_params.allowed.include?(name)
35
+ "#{name}: ((__mocktail_default_args ||= {})[:#{name}] = nil)"
36
+ elsif signature.keyword_params.rest == name
37
+ "**#{(name == :**) ? Signature::DEFAULT_REST_KWARGS : name}"
38
+ end
39
+ end.compact
40
+
41
+ params.join(", ") if params.any?
42
+ end
43
+
44
+ sig { params(signature: Signature).returns(String) }
45
+ def block(signature)
46
+ if signature.block_param && signature.block_param != :&
47
+ "&#{signature.block_param}"
48
+ else
49
+ "&#{Signature::DEFAULT_BLOCK_PARAM}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ TYPED = true
5
+ end
@@ -0,0 +1,91 @@
1
+ # typed: strict
2
+
3
+ require_relative "../share/bind"
4
+
5
+ # The Cabinet stores all thread-local state, so anything that goes here
6
+ # is guaranteed by Mocktail to be local to the currently-running thread
7
+ module Mocktail
8
+ class Cabinet
9
+ extend T::Sig
10
+
11
+ sig { params(demonstration_in_progress: T::Boolean).void }
12
+ attr_writer :demonstration_in_progress
13
+
14
+ sig { returns(T::Array[Call]) }
15
+ attr_reader :calls
16
+
17
+ sig { returns(T::Array[Stubbing[T.anything]]) }
18
+ attr_reader :stubbings
19
+
20
+ sig { returns(T::Array[UnsatisfyingCall]) }
21
+ attr_reader :unsatisfying_calls
22
+
23
+ sig { void }
24
+ def initialize
25
+ @doubles = T.let([], T::Array[Double])
26
+ @calls = T.let([], T::Array[Call])
27
+ @stubbings = T.let([], T::Array[Stubbing[T.anything]])
28
+ @unsatisfying_calls = T.let([], T::Array[UnsatisfyingCall])
29
+ @demonstration_in_progress = T.let(false, T::Boolean)
30
+ end
31
+
32
+ sig { void }
33
+ def reset!
34
+ @calls = []
35
+ @stubbings = []
36
+ @unsatisfying_calls = []
37
+ # Could cause an exception or prevent pollution—you decide!
38
+ @demonstration_in_progress = false
39
+ # note we don't reset doubles as they don't carry any
40
+ # user-meaningful state on them, and clearing them on reset could result
41
+ # in valid mocks being broken and stop working
42
+ end
43
+
44
+ sig { params(double: Double).void }
45
+ def store_double(double)
46
+ @doubles << double
47
+ end
48
+
49
+ sig { params(call: Call).void }
50
+ def store_call(call)
51
+ @calls << call
52
+ end
53
+
54
+ sig { params(stubbing: Stubbing[T.anything]).void }
55
+ def store_stubbing(stubbing)
56
+ @stubbings << stubbing
57
+ end
58
+
59
+ sig { params(unsatisfying_call: UnsatisfyingCall).void }
60
+ def store_unsatisfying_call(unsatisfying_call)
61
+ @unsatisfying_calls << unsatisfying_call
62
+ end
63
+
64
+ sig { returns(T::Boolean) }
65
+ def demonstration_in_progress?
66
+ @demonstration_in_progress
67
+ end
68
+
69
+ sig { params(thing: T.anything).returns(T.nilable(Double)) }
70
+ def double_for_instance(thing)
71
+ @doubles.find { |double|
72
+ # Intentionally calling directly to avoid an infinite recursion in Bind.call
73
+ Object.instance_method(:==).bind_call(double.dry_instance, thing)
74
+ }
75
+ end
76
+
77
+ sig { params(double: Double).returns(T::Array[Stubbing[T.anything]]) }
78
+ def stubbings_for_double(double)
79
+ @stubbings.select { |stubbing|
80
+ Bind.call(stubbing.recording.double, :==, double.dry_instance)
81
+ }
82
+ end
83
+
84
+ sig { params(double: Double).returns(T::Array[Call]) }
85
+ def calls_for_double(double)
86
+ @calls.select { |call|
87
+ Bind.call(call.double, :==, double.dry_instance)
88
+ }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class Call < T::Struct
5
+ extend T::Sig
6
+
7
+ const :singleton, T.nilable(T::Boolean)
8
+ const :double, Object, default: nil
9
+ const :original_type, T.nilable(T.any(T::Class[T.anything], Module))
10
+ const :dry_type, T.nilable(T.any(T::Class[T.anything], Module))
11
+ const :method, T.nilable(Symbol), without_accessors: true
12
+ const :original_method, T.nilable(T.any(UnboundMethod, Method))
13
+ const :args, T::Array[T.untyped], default: []
14
+ const :kwargs, T::Hash[Symbol, T.untyped], default: {}
15
+ # At present, there's no way to type optional/variadic params in blocks
16
+ # (i.e. `T.proc.params(*T.untyped).returns(T.untyped)` doesn't work)
17
+ #
18
+ # See: https://github.com/sorbet/sorbet/issues/1142#issuecomment-1586195730
19
+ const :block, T.nilable(Proc)
20
+
21
+ sig { returns(T.nilable(Symbol)) }
22
+ attr_reader :method
23
+
24
+ # Because T::Struct compares with referential equality, we need
25
+ # to redefine the equality methods to compare the values of the attributes.
26
+ sig { params(other: T.nilable(T.anything)).returns(T::Boolean) }
27
+ def ==(other)
28
+ eql?(other)
29
+ end
30
+
31
+ sig { params(other: T.anything).returns(T::Boolean) }
32
+ def eql?(other)
33
+ case other
34
+ when Call
35
+ [
36
+ :singleton, :double, :original_type, :dry_type,
37
+ :method, :original_method, :args, :kwargs, :block
38
+ ].all? { |attr|
39
+ instance_variable_get("@#{attr}") == other.send(attr)
40
+ }
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ sig { returns(Integer) }
47
+ def hash
48
+ [@singleton, @double, @original_type, @dry_type, @method, @original_method, @args, @kwargs, @block].hash
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class DemoConfig < T::Struct
5
+ const :ignore_block, T::Boolean, default: false
6
+ const :ignore_extra_args, T::Boolean, default: false
7
+ const :ignore_arity, T::Boolean, default: false
8
+ const :times, T.nilable(Integer), default: nil
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class Double < T::Struct
5
+ const :original_type, T.any(T::Class[T.anything], Module)
6
+ const :dry_type, T::Class[T.anything]
7
+ const :dry_instance, Object
8
+ const :dry_methods, T::Array[Symbol]
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+
3
+ require_relative "call"
4
+ require_relative "stubbing"
5
+
6
+ module Mocktail
7
+ class DoubleData < T::Struct
8
+ include ExplanationData
9
+
10
+ const :type, T.any(T::Class[T.anything], Module)
11
+ const :double, Object
12
+ const :calls, T::Array[Call]
13
+ const :stubbings, T::Array[Stubbing[T.anything]]
14
+ end
15
+ end
@@ -0,0 +1,68 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class Explanation
5
+ extend T::Sig
6
+
7
+ sig { returns(Mocktail::ExplanationData) }
8
+ attr_reader :reference
9
+
10
+ sig { returns(String) }
11
+ attr_reader :message
12
+
13
+ sig { params(reference: Mocktail::ExplanationData, message: String).void }
14
+ def initialize(reference, message)
15
+ @reference = reference
16
+ @message = message
17
+ end
18
+
19
+ sig { returns(T.class_of(Explanation)) }
20
+ def type
21
+ self.class
22
+ end
23
+ end
24
+
25
+ class NoExplanation < Explanation
26
+ sig { override.returns(NoExplanationData) }
27
+ attr_reader :reference
28
+
29
+ sig { params(reference: NoExplanationData, message: String).void }
30
+ def initialize(reference, message)
31
+ @reference = reference
32
+ @message = message
33
+ end
34
+ end
35
+
36
+ class DoubleExplanation < Explanation
37
+ sig { override.returns(DoubleData) }
38
+ attr_reader :reference
39
+
40
+ sig { params(reference: DoubleData, message: String).void }
41
+ def initialize(reference, message)
42
+ @reference = reference
43
+ @message = message
44
+ end
45
+ end
46
+
47
+ class ReplacedTypeExplanation < Explanation
48
+ sig { override.returns(TypeReplacementData) }
49
+ attr_reader :reference
50
+
51
+ sig { params(reference: TypeReplacementData, message: String).void }
52
+ def initialize(reference, message)
53
+ @reference = reference
54
+ @message = message
55
+ end
56
+ end
57
+
58
+ class FakeMethodExplanation < Explanation
59
+ sig { override.returns(FakeMethodData) }
60
+ attr_reader :reference
61
+
62
+ sig { params(reference: FakeMethodData, message: String).void }
63
+ def initialize(reference, message)
64
+ @reference = reference
65
+ @message = message
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ module ExplanationData
5
+ extend T::Helpers
6
+ extend T::Sig
7
+
8
+ interface!
9
+ include Kernel
10
+
11
+ sig { abstract.returns T::Array[Mocktail::Call] }
12
+ def calls
13
+ end
14
+
15
+ sig { abstract.returns T::Array[Mocktail::Stubbing[T.anything]] }
16
+ def stubbings
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class FakeMethodData < T::Struct
5
+ include ExplanationData
6
+
7
+ const :receiver, T.anything
8
+ const :calls, T::Array[Call]
9
+ const :stubbings, T::Array[Stubbing[T.anything]]
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class MatcherRegistry
5
+ extend T::Sig
6
+
7
+ sig { returns(MatcherRegistry) }
8
+ def self.instance
9
+ @matcher_registry ||= T.let(new, T.nilable(T.attached_class))
10
+ end
11
+
12
+ sig { void }
13
+ def initialize
14
+ @matchers = T.let({}, T::Hash[Symbol, T.class_of(Matchers::Base)])
15
+ end
16
+
17
+ sig { params(matcher_type: T.class_of(Matchers::Base)).void }
18
+ def add(matcher_type)
19
+ @matchers[matcher_type.matcher_name] = matcher_type
20
+ end
21
+
22
+ sig { params(name: Symbol).returns(T.nilable(T.class_of(Matchers::Base))) }
23
+ def get(name)
24
+ @matchers[name]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class NoExplanationData < T::Struct
5
+ extend T::Sig
6
+ include ExplanationData
7
+
8
+ const :thing, Object
9
+
10
+ sig { override.returns(T::Array[Mocktail::Call]) }
11
+ def calls
12
+ raise Error.new("No calls have been recorded for #{thing.inspect}, because Mocktail doesn't know what it is.")
13
+ end
14
+
15
+ sig { override.returns T::Array[Mocktail::Stubbing[T.anything]] }
16
+ def stubbings
17
+ raise Error.new("No stubbings exist on #{thing.inspect}, because Mocktail doesn't know what it is.")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class Params < T::Struct
5
+ extend T::Sig
6
+
7
+ prop :all, T::Array[Symbol], default: []
8
+ prop :required, T::Array[Symbol], default: []
9
+ prop :optional, T::Array[Symbol], default: []
10
+ prop :rest, T.nilable(Symbol)
11
+
12
+ sig { returns(T::Array[Symbol]) }
13
+ def allowed
14
+ all.select { |name| required.include?(name) || optional.include?(name) }
15
+ end
16
+
17
+ sig { returns(T::Boolean) }
18
+ def rest?
19
+ !!rest
20
+ end
21
+ end
22
+
23
+ class Signature < T::Struct
24
+ const :positional_params, Params
25
+ const :positional_args, T::Array[T.anything]
26
+ const :keyword_params, Params
27
+ const :keyword_args, T::Hash[Symbol, T.anything]
28
+ const :block_param, T.nilable(Symbol)
29
+ const :block_arg, T.nilable(Proc), default: nil
30
+
31
+ DEFAULT_REST_ARGS = "args"
32
+ DEFAULT_REST_KWARGS = "kwargs"
33
+ DEFAULT_BLOCK_PARAM = "blk"
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class Stubbing < T::Struct
5
+ extend T::Sig
6
+ extend T::Generic
7
+ MethodReturnType = type_member
8
+
9
+ const :demonstration, T.proc.params(matchers: Mocktail::MatcherPresentation).returns(MethodReturnType)
10
+ const :demo_config, DemoConfig
11
+ prop :satisfaction_count, Integer, default: 0
12
+ const :recording, Call
13
+ prop :effect, T.nilable(T.proc.params(call: Mocktail::Call).returns(MethodReturnType))
14
+
15
+ sig { void }
16
+ def satisfied!
17
+ self.satisfaction_count += 1
18
+ end
19
+
20
+ sig { params(block: T.proc.params(call: Mocktail::Call).returns(MethodReturnType)).void }
21
+ def with(&block)
22
+ self.effect = block
23
+ nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ # The TopShelf is where we keep all the more global, dangerous state.
5
+ # In particular, this is where Mocktail manages state related to singleton
6
+ # method replacements carried out with Mocktail.replace(ClassOrModule)
7
+ class TopShelf
8
+ extend T::Sig
9
+
10
+ sig { returns(TopShelf) }
11
+ def self.instance
12
+ Thread.current[:mocktail_top_shelf] ||= new
13
+ end
14
+
15
+ @@type_replacements = T.let({}, T::Hash[T.any(Module, T::Class[T.anything]), TypeReplacement])
16
+ @@type_replacements_mutex = T.let(Mutex.new, Mutex)
17
+
18
+ sig { void }
19
+ def initialize
20
+ @new_registrations = T.let([], T::Array[T.any(Module, T::Class[T.anything])])
21
+ @of_next_registrations = T.let([], T::Array[T::Class[T.anything]])
22
+ @singleton_method_registrations = T.let([], T::Array[T.any(Module, T::Class[T.anything])])
23
+ end
24
+
25
+ sig { params(type: T.any(Module, T::Class[T.anything])).returns(TypeReplacement) }
26
+ def type_replacement_for(type)
27
+ @@type_replacements_mutex.synchronize {
28
+ @@type_replacements[type] ||= TypeReplacement.new(type: type)
29
+ }
30
+ end
31
+
32
+ sig { params(type: T.any(Module, T::Class[T.anything])).returns(T.nilable(TypeReplacement)) }
33
+ def type_replacement_if_exists_for(type)
34
+ @@type_replacements_mutex.synchronize {
35
+ @@type_replacements[type]
36
+ }
37
+ end
38
+
39
+ sig { void }
40
+ def reset_current_thread!
41
+ Thread.current[:mocktail_top_shelf] = self.class.new
42
+ end
43
+
44
+ sig { params(type: T.any(Module, T::Class[T.anything])).void }
45
+ def register_new_replacement!(type)
46
+ @new_registrations |= [type]
47
+ end
48
+
49
+ sig { params(type: T.any(Module, T::Class[T.anything])).returns(T::Boolean) }
50
+ def new_replaced?(type)
51
+ @new_registrations.include?(type)
52
+ end
53
+
54
+ sig { params(type: T::Class[T.anything]).void }
55
+ def register_of_next_replacement!(type)
56
+ @of_next_registrations |= [type]
57
+ end
58
+
59
+ sig { params(type: T::Class[T.anything]).returns(T::Boolean) }
60
+ def of_next_registered?(type)
61
+ @of_next_registrations.include?(type)
62
+ end
63
+
64
+ sig { params(type: T::Class[T.anything]).void }
65
+ def unregister_of_next_replacement!(type)
66
+ @of_next_registrations -= [type]
67
+ end
68
+
69
+ sig { params(type: T.any(Module, T::Class[T.anything])).void }
70
+ def register_singleton_method_replacement!(type)
71
+ @singleton_method_registrations |= [type]
72
+ end
73
+
74
+ sig { params(type: T.any(Module, T::Class[T.anything])).returns(T::Boolean) }
75
+ def singleton_methods_replaced?(type)
76
+ @singleton_method_registrations.include?(type)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class TypeReplacement < T::Struct
5
+ const :type, T.any(T::Class[T.anything], Module)
6
+ prop :original_methods, T.nilable(T::Array[Method])
7
+ prop :replacement_methods, T.nilable(T::Array[Method])
8
+ prop :original_new, T.nilable(Method)
9
+ prop :replacement_new, T.nilable(Method)
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class TypeReplacementData < T::Struct
5
+ extend T::Sig
6
+
7
+ const :type, T.any(T::Class[T.anything], Module)
8
+ const :replaced_method_names, T::Array[Symbol]
9
+ const :calls, T::Array[Call]
10
+ const :stubbings, T::Array[Stubbing[T.anything]]
11
+
12
+ include ExplanationData
13
+
14
+ sig { returns(T.any(T::Class[T.anything], Module)) }
15
+ def double
16
+ type
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class UnsatisfyingCall < T::Struct
5
+ const :call, Call
6
+ const :other_stubbings, T::Array[Stubbing[T.anything]]
7
+ const :backtrace, T::Array[String]
8
+ end
9
+ end