rspec-sleeping_king_studios 2.7.0 → 2.8.0.rc.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +228 -9
  4. data/config/rubocop-rspec.yml +41 -0
  5. data/lib/rspec/sleeping_king_studios/concerns/example_constants.rb +107 -74
  6. data/lib/rspec/sleeping_king_studios/concerns/memoized_helpers.rb +19 -0
  7. data/lib/rspec/sleeping_king_studios/concerns/shared_example_group.rb +5 -2
  8. data/lib/rspec/sleeping_king_studios/concerns.rb +8 -3
  9. data/lib/rspec/sleeping_king_studios/configuration.rb +45 -37
  10. data/lib/rspec/sleeping_king_studios/deferred/call.rb +74 -0
  11. data/lib/rspec/sleeping_king_studios/deferred/calls/example.rb +42 -0
  12. data/lib/rspec/sleeping_king_studios/deferred/calls/example_group.rb +42 -0
  13. data/lib/rspec/sleeping_king_studios/deferred/calls/hook.rb +64 -0
  14. data/lib/rspec/sleeping_king_studios/deferred/calls/included_examples.rb +34 -0
  15. data/lib/rspec/sleeping_king_studios/deferred/calls/shared_examples.rb +41 -0
  16. data/lib/rspec/sleeping_king_studios/deferred/calls.rb +19 -0
  17. data/lib/rspec/sleeping_king_studios/deferred/consumer.rb +159 -0
  18. data/lib/rspec/sleeping_king_studios/deferred/definitions.rb +42 -0
  19. data/lib/rspec/sleeping_king_studios/deferred/dependencies.rb +138 -0
  20. data/lib/rspec/sleeping_king_studios/deferred/dsl/example_constants.rb +72 -0
  21. data/lib/rspec/sleeping_king_studios/deferred/dsl/example_groups.rb +69 -0
  22. data/lib/rspec/sleeping_king_studios/deferred/dsl/examples.rb +84 -0
  23. data/lib/rspec/sleeping_king_studios/deferred/dsl/hooks.rb +125 -0
  24. data/lib/rspec/sleeping_king_studios/deferred/dsl/memoized_helpers.rb +123 -0
  25. data/lib/rspec/sleeping_king_studios/deferred/dsl/shared_examples.rb +128 -0
  26. data/lib/rspec/sleeping_king_studios/deferred/dsl.rb +26 -0
  27. data/lib/rspec/sleeping_king_studios/deferred/examples.rb +83 -0
  28. data/lib/rspec/sleeping_king_studios/deferred/missing.rb +46 -0
  29. data/lib/rspec/sleeping_king_studios/deferred/provider.rb +164 -0
  30. data/lib/rspec/sleeping_king_studios/deferred.rb +142 -0
  31. data/lib/rspec/sleeping_king_studios/matchers/built_in/include_matcher.rb +85 -70
  32. data/lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb +28 -23
  33. data/lib/rspec/sleeping_king_studios/sandbox.rb +105 -0
  34. data/lib/rspec/sleeping_king_studios/version.rb +4 -3
  35. data/lib/rspec/sleeping_king_studios.rb +10 -4
  36. metadata +36 -141
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred'
4
+
5
+ module RSpec::SleepingKingStudios::Deferred
6
+ # Class methods for defining a registry of deferred calls.
7
+ module Definitions
8
+ # Invokes the deferred examples on the given example group.
9
+ #
10
+ # @param example_group [RSpec::Core::ExampleGroup] the example group.
11
+ #
12
+ # @return [void]
13
+ def call(example_group)
14
+ deferred_calls.each do |deferred|
15
+ deferred.call(example_group)
16
+ end
17
+ end
18
+
19
+ # @private
20
+ def deferred_calls
21
+ @deferred_calls ||= []
22
+ end
23
+
24
+ # Callback invoked when the module is included in another module or class.
25
+ #
26
+ # Calls the deferred calls with the other module as the receiver if the
27
+ # module is an RSpec::Core::ExampleGroup.
28
+ #
29
+ # @param other [Module] the other module or class.
30
+ def included(other)
31
+ super
32
+
33
+ return unless other < RSpec::Core::ExampleGroup
34
+
35
+ ancestors.reverse_each do |ancestor|
36
+ next unless ancestor.singleton_class.method_defined?(:deferred_calls)
37
+
38
+ ancestor.call(other)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred'
4
+
5
+ module RSpec::SleepingKingStudios::Deferred
6
+ # Mixin for declaring dependent methods for deferred example groups.
7
+ #
8
+ # Each dependent method is expected to have a definition, either as a direct
9
+ # method definition (using the `def` keyword or `define_method`), or via a
10
+ # memoized helper (such as `let`).
11
+ #
12
+ # When the deferred examples are included in an example group and that example
13
+ # group is run, a before(:context) hook will check for all of the declared
14
+ # dependencies of that example group. If any of the expected dependencies are
15
+ # not defined, the hook will raise an exception listing the missing methods,
16
+ # the deferred examples that expect that method, and the description provided.
17
+ #
18
+ # @example
19
+ # module RocketExamples
20
+ # include RSpec::SleepingKingStudios::Deferred::Provider
21
+ #
22
+ # deferred_examples 'should launch the rocket' do
23
+ # include RSpec::SleepingKingStudios::Deferred::Dependencies
24
+ #
25
+ # depends_on :rocket,
26
+ # 'an instance of Rocket where #launched? returns false'
27
+ #
28
+ # describe '#launch' do
29
+ # it 'should launch the rocket' do
30
+ # expect { rocket.launch }.to change(rocket, :launched?).to be true
31
+ # end
32
+ # end
33
+ # end
34
+ # end
35
+ module Dependencies
36
+ extend SleepingKingStudios::Tools::Toolbox::Mixin
37
+
38
+ # Exception raised when declared dependencies are not defined.
39
+ class MissingDependenciesError < StandardError; end
40
+
41
+ # Class methods for declaring dependent methods.
42
+ module ClassMethods
43
+ # @private
44
+ def call(example_group)
45
+ super
46
+
47
+ metadata_key = :deferred_dependencies_check_added
48
+
49
+ return if example_group.metadata[metadata_key]
50
+
51
+ example_group.metadata[metadata_key] = true
52
+
53
+ example_group.before(:context) do
54
+ RSpec::SleepingKingStudios::Deferred::Dependencies
55
+ .check_dependencies_for(self)
56
+ end
57
+ end
58
+
59
+ # @private
60
+ def dependent_methods
61
+ @dependent_methods ||= []
62
+ end
63
+
64
+ # Declares an external method dependency.
65
+ #
66
+ # @param method_name [String, Symbol] the name of the expected method.
67
+ # @param description [String] a short description of the method.
68
+ #
69
+ # @return [void]
70
+ def depends_on(method_name, description = nil)
71
+ dependent_methods << {
72
+ deferred_group: self,
73
+ description:,
74
+ method_name: method_name.to_s.sub(/\A#/, '').intern
75
+ }
76
+
77
+ nil
78
+ end
79
+ end
80
+
81
+ class << self
82
+ # Checks for missing dependent methods for the given example.
83
+ #
84
+ # @param example [RSpec::Core::Example] the example to check.
85
+ #
86
+ # @raise [MissingDependenciesError] if there are any dependencies missing
87
+ # for the given example.
88
+ def check_dependencies_for(example)
89
+ missing = missing_dependencies_for(example)
90
+
91
+ return if missing.empty?
92
+
93
+ raise MissingDependenciesError, generate_missing_message(missing)
94
+ end
95
+
96
+ private
97
+
98
+ def all_dependencies_for(example)
99
+ example
100
+ .class
101
+ .ancestors
102
+ .select { |ancestor| ancestor.respond_to?(:dependent_methods) }
103
+ .flat_map(&:dependent_methods)
104
+ end
105
+
106
+ def generate_missing_message(missing) # rubocop:disable Metrics/MethodLength
107
+ [
108
+ 'Unable to run specs with deferred example groups because the ' \
109
+ 'following methods are not defined in the examples:',
110
+ *missing
111
+ .group_by { |item| item[:deferred_group] }
112
+ .map do |deferred_group, dependencies|
113
+ message_for_group(deferred_group, dependencies)
114
+ end,
115
+ 'Please define the missing methods or :let helpers.'
116
+ ]
117
+ .join("\n\n")
118
+ end
119
+
120
+ def message_for_group(deferred_group, dependencies)
121
+ message = "Missing methods for #{deferred_group.description.inspect}:"
122
+
123
+ dependencies.each do |hsh|
124
+ message += "\n ##{hsh[:method_name]}"
125
+ message += ": #{hsh[:description]}" if hsh[:description]
126
+ end
127
+
128
+ message
129
+ end
130
+
131
+ def missing_dependencies_for(example)
132
+ all_dependencies_for(example).reject do |hsh|
133
+ example.respond_to?(hsh[:method_name])
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred/call'
4
+ require 'rspec/sleeping_king_studios/deferred/dsl'
5
+
6
+ module RSpec::SleepingKingStudios::Deferred::Dsl # rubocop:disable Style/Documentation
7
+ # Domain-specific language for defining deferred example constants.
8
+ #
9
+ # @see RSpec::SleepingKingStudios::Concerns::ExampleConstants.
10
+ module ExampleConstants
11
+ # Defines a deferred temporary class scoped to the current example.
12
+ #
13
+ # @param class_name [String] the qualified name of the class.
14
+ # @param base_class [Class, String] the base class or name of the base
15
+ # class. This can be the name of another example class, as long as the
16
+ # base class is defined earlier in the example group or in a parent
17
+ # example group.
18
+ #
19
+ # @yield definitions for the temporary class. This block is evaluated in
20
+ # the context of the example, meaning that methods or memoized helpers
21
+ # can be referenced.
22
+ # @yieldparam klass [Class] the temporary class.
23
+ def example_class(class_name, base_class = nil, &)
24
+ deferred_calls <<
25
+ RSpec::SleepingKingStudios::Deferred::Call.new(
26
+ :example_class,
27
+ class_name,
28
+ base_class,
29
+ &
30
+ )
31
+
32
+ nil
33
+ end
34
+
35
+ # @overload example_constant(constant_name, constant_value)
36
+ # Defines a deferred temporary constant scoped to the current example.
37
+ #
38
+ # @param constant_name [String] the qualified name of the constant.
39
+ # @param constant_value [Object] the value of the constant.
40
+ #
41
+ # @overload example_constant(constant_name, &block)
42
+ # Defines a deferred temporary constant scoped to the current example.
43
+ #
44
+ # @param constant_name [String] the qualified name of the constant.
45
+ #
46
+ # @yield generates the constant value. This block is evaluated in the
47
+ # context of the example, meaning that methods or memoized helpers can
48
+ # be referenced.
49
+ # @yieldreturn the value of the constant.
50
+ #
51
+ # @deprecated 2.8.0 with force: true parameter.
52
+ def example_constant(
53
+ qualified_name,
54
+ constant_value = nil,
55
+ force: false,
56
+ &block
57
+ )
58
+ deferred_calls <<
59
+ RSpec::SleepingKingStudios::Deferred::Call.new(
60
+ :example_constant,
61
+ qualified_name,
62
+ *(constant_value.nil? ? [] : [constant_value]),
63
+ force:,
64
+ &block
65
+ )
66
+
67
+ nil
68
+ end
69
+ end
70
+
71
+ include ExampleConstants
72
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred/calls/example_group'
4
+ require 'rspec/sleeping_king_studios/deferred/dsl'
5
+
6
+ module RSpec::SleepingKingStudios::Deferred::Dsl # rubocop:disable Style/Documentation
7
+ # Methods for defining deferred example groups.
8
+ module ExampleGroups
9
+ # Meta-methods for defining deferred example groups.
10
+ module Macros
11
+ # Registers a method for deferring an example group.
12
+ #
13
+ # @param method_name [String, Symbol] the name of the deferred method.
14
+ #
15
+ # @return [void]
16
+ def define_example_group_method(method_name) # rubocop:disable Metrics/MethodLength
17
+ define_method(method_name) do |*args, **kwargs, &block|
18
+ deferred_calls <<
19
+ RSpec::SleepingKingStudios::Deferred::Calls::ExampleGroup.new(
20
+ method_name,
21
+ *args,
22
+ **kwargs,
23
+ deferred_example_group: self,
24
+ &block
25
+ )
26
+
27
+ nil
28
+ end
29
+ end
30
+ end
31
+
32
+ extend Macros
33
+
34
+ # @!macro [new] define_example_group_method
35
+ # @!method $1(doc_string = nil, *flags, **metadata, &block)
36
+ # Defines a deferred example group using the $1 method.
37
+ #
38
+ # @param doc_string [String] the example group's doc string.
39
+ # @param flags [Array<Symbol>] metadata flags for the example group.
40
+ # Will be transformed into metadata entries with true values.
41
+ # @param metadata [Hash] metadata for the example group.
42
+ # @param block [Proc] the implementation of the example group.
43
+ #
44
+ # @return [void]
45
+
46
+ # @!macro define_example_group_method
47
+ define_example_group_method :context
48
+
49
+ # @!macro define_example_group_method
50
+ define_example_group_method :describe
51
+
52
+ # @!macro define_example_group_method
53
+ define_example_group_method :example_group
54
+
55
+ # @!macro define_example_group_method
56
+ define_example_group_method :fcontext
57
+
58
+ # @!macro define_example_group_method
59
+ define_example_group_method :fdescribe
60
+
61
+ # @!macro define_example_group_method
62
+ define_example_group_method :xcontext
63
+
64
+ # @!macro define_example_group_method
65
+ define_example_group_method :xdescribe
66
+ end
67
+
68
+ include ExampleGroups
69
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred/calls/example'
4
+ require 'rspec/sleeping_king_studios/deferred/dsl'
5
+
6
+ module RSpec::SleepingKingStudios::Deferred::Dsl # rubocop:disable Style/Documentation
7
+ # Methods for defining deferred examples.
8
+ module Examples
9
+ # Meta-methods for defining deferred examples.
10
+ module Macros
11
+ # Registers a method for deferring an example.
12
+ #
13
+ # @param method_name [String, Symbol] the name of the deferred method.
14
+ #
15
+ # @return [void]
16
+ def define_example_method(method_name) # rubocop:disable Metrics/MethodLength
17
+ define_method(method_name) do |*args, **kwargs, &block|
18
+ deferred_calls <<
19
+ RSpec::SleepingKingStudios::Deferred::Calls::Example.new(
20
+ method_name,
21
+ *args,
22
+ **kwargs,
23
+ deferred_example_group: self,
24
+ &block
25
+ )
26
+
27
+ nil
28
+ end
29
+ end
30
+ end
31
+
32
+ extend Macros
33
+
34
+ # @!macro [new] define_example_method
35
+ # @!method $1(doc_string = nil, *flags, **metadata, &block)
36
+ # Defines a deferred example using the $1 method.
37
+ #
38
+ # @param doc_string [String] the example's doc string.
39
+ # @param flags [Array<Symbol>] metadata flags for the example. Will be
40
+ # transformed into metadata entries with true values.
41
+ # @param metadata [Hash] metadata for the example.
42
+ # @param block [Proc] the implementation of the example.
43
+ #
44
+ # @return [void]
45
+
46
+ # @!macro define_example_method
47
+ define_example_method :example
48
+
49
+ # @!macro define_example_method
50
+ define_example_method :fexample
51
+
52
+ # @!macro define_example_method
53
+ define_example_method :fit
54
+
55
+ # @!macro define_example_method
56
+ define_example_method :focus
57
+
58
+ # @!macro define_example_method
59
+ define_example_method :fspecify
60
+
61
+ # @!macro define_example_method
62
+ define_example_method :it
63
+
64
+ # @!macro define_example_method
65
+ define_example_method :pending
66
+
67
+ # @!macro define_example_method
68
+ define_example_method :skip
69
+
70
+ # @!macro define_example_method
71
+ define_example_method :specify
72
+
73
+ # @!macro define_example_method
74
+ define_example_method :xexample
75
+
76
+ # @!macro define_example_method
77
+ define_example_method :xit
78
+
79
+ # @!macro define_example_method
80
+ define_example_method :xspecify
81
+ end
82
+
83
+ include Examples
84
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred/calls/hook'
4
+ require 'rspec/sleeping_king_studios/deferred/dsl'
5
+
6
+ module RSpec::SleepingKingStudios::Deferred::Dsl # rubocop:disable Style/Documentation
7
+ # Domain-specific language for defining deferred hooks.
8
+ module Hooks
9
+ # @overload after(scope, *flags, **conditions, &block)
10
+ # Defines a deferred hook using the #after method.
11
+ #
12
+ # @param scope [Symbol] the scope for the hook. Must be one of :context,
13
+ # :each, or :example.
14
+ # @param flags [Array<Symbol>] condition flags for the hook. Will be
15
+ # transformed into conditions entries with true values.
16
+ # @param block [Proc] the implementation of the hook.
17
+ #
18
+ # @return [void]
19
+ def after(scope, *flags, **conditions, &)
20
+ deferred_hooks << RSpec::SleepingKingStudios::Deferred::Calls::Hook.new(
21
+ :after,
22
+ scope,
23
+ *flags,
24
+ **conditions,
25
+ &
26
+ )
27
+ end
28
+
29
+ # @overload append_after(scope, *flags, **conditions, &block)
30
+ # Defines a deferred hook using the #append_after method.
31
+ #
32
+ # @param scope [Symbol] the scope for the hook. Must be one of :context,
33
+ # :each, or :example.
34
+ # @param flags [Array<Symbol>] condition flags for the hook. Will be
35
+ # transformed into conditions entries with true values.
36
+ # @param block [Proc] the implementation of the hook.
37
+ #
38
+ # @return [void]
39
+ def append_after(scope, *flags, **conditions, &)
40
+ deferred_hooks << RSpec::SleepingKingStudios::Deferred::Calls::Hook.new(
41
+ :append_after,
42
+ scope,
43
+ *flags,
44
+ **conditions,
45
+ &
46
+ )
47
+ end
48
+
49
+ # @overload around(scope, *flags, **conditions, &block)
50
+ # Defines a deferred hook using the #around method.
51
+ #
52
+ # @param scope [Symbol] the scope for the hook. Must be one of :context,
53
+ # :each, or :example.
54
+ # @param flags [Array<Symbol>] condition flags for the hook. Will be
55
+ # transformed into conditions entries with true values.
56
+ # @param block [Proc] the implementation of the hook.
57
+ #
58
+ # @return [void]
59
+ def around(scope, *flags, **conditions, &)
60
+ deferred_hooks << RSpec::SleepingKingStudios::Deferred::Calls::Hook.new(
61
+ :around,
62
+ scope,
63
+ *flags,
64
+ **conditions,
65
+ &
66
+ )
67
+ end
68
+
69
+ # @overload before(scope, *flags, **conditions, &block)
70
+ # Defines a deferred hook using the #before method.
71
+ #
72
+ # @param scope [Symbol] the scope for the hook. Must be one of :context,
73
+ # :each, or :example.
74
+ # @param flags [Array<Symbol>] condition flags for the hook. Will be
75
+ # transformed into conditions entries with true values.
76
+ # @param block [Proc] the implementation of the hook.
77
+ #
78
+ # @return [void]
79
+ def before(scope, *flags, **conditions, &)
80
+ deferred_hooks << RSpec::SleepingKingStudios::Deferred::Calls::Hook.new(
81
+ :before,
82
+ scope,
83
+ *flags,
84
+ **conditions,
85
+ &
86
+ )
87
+ end
88
+
89
+ # (see RSpec::SleepingKingStudios::Deferred::Definitions#call)
90
+ def call(example_group)
91
+ super
92
+
93
+ deferred_hooks.reverse_each do |deferred_hook|
94
+ deferred_hook.call(example_group)
95
+ end
96
+ end
97
+
98
+ # @private
99
+ def deferred_hooks
100
+ @deferred_hooks ||= []
101
+ end
102
+
103
+ # @overload prepend_before(scope, *flags, **conditions, &block)
104
+ # Defines a deferred hook using the #prepend_before method.
105
+ #
106
+ # @param scope [Symbol] the scope for the hook. Must be one of :context,
107
+ # :each, or :example.
108
+ # @param flags [Array<Symbol>] condition flags for the hook. Will be
109
+ # transformed into conditions entries with true values.
110
+ # @param block [Proc] the implementation of the hook.
111
+ #
112
+ # @return [void]
113
+ def prepend_before(scope, *flags, **conditions, &)
114
+ deferred_hooks << RSpec::SleepingKingStudios::Deferred::Calls::Hook.new(
115
+ :prepend_before,
116
+ scope,
117
+ *flags,
118
+ **conditions,
119
+ &
120
+ )
121
+ end
122
+ end
123
+
124
+ include Hooks
125
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred/dsl'
4
+
5
+ module RSpec::SleepingKingStudios::Deferred::Dsl # rubocop:disable Style/Documentation
6
+ # DSL for defining memoized helpers for deferred examples.
7
+ module MemoizedHelpers
8
+ # Callback invoked when the module is extended into another module or class.
9
+ #
10
+ # Defines a HelperImplementations module on the module and includes it in
11
+ # the module.
12
+ #
13
+ # @param other [Module] the other module or class.
14
+ def self.extended(other)
15
+ super
16
+
17
+ return if other.const_defined?(:HelperImplementations, true)
18
+
19
+ other.const_set(
20
+ :HelperImplementations,
21
+ Module.new do
22
+ named = Module.new
23
+
24
+ const_set(:NamedSuper, named)
25
+
26
+ include named
27
+ end
28
+ )
29
+ end
30
+
31
+ # (see RSpec::SleepingKingStudios::Deferred::Definitions#call)
32
+ def call(example_group)
33
+ super
34
+
35
+ include self::HelperImplementations
36
+ end
37
+
38
+ # @overload let(helper_name = nil, &block)
39
+ # Defines a memoized helper.
40
+ #
41
+ # @param helper_name [String, Symbol] the name of the helper method.
42
+ # @param block [Block] the implementation of the helper method.
43
+ #
44
+ # @return [void]
45
+ def let(helper_name, &)
46
+ helper_name = helper_name.to_sym
47
+
48
+ self::HelperImplementations.define_method(helper_name, &)
49
+
50
+ define_method(helper_name) do
51
+ helper_values = @memoized_helper_values ||= {}
52
+
53
+ helper_values.fetch(helper_name) do
54
+ helper_values[helper_name] = super()
55
+ end
56
+ end
57
+ end
58
+
59
+ # @overload let!(helper_name = nil, &block)
60
+ # Defines a memoized helper and adds a hook to evaluate it before examples.
61
+ #
62
+ # @param helper_name [String, Symbol] the name of the helper method.
63
+ # @param block [Block] the implementation of the helper method.
64
+ #
65
+ # @return [void]
66
+ def let!(helper_name, &)
67
+ let(helper_name, &)
68
+
69
+ before(:example) { send(helper_name) }
70
+ end
71
+
72
+ # Defines an optional memoized helper.
73
+ #
74
+ # The helper will use the parent value if defined; otherwise, will use the
75
+ # given value.
76
+ #
77
+ # @param helper_name [String, Symbol] the name of the helper method.
78
+ # @param block [Block] the implementation of the helper method.
79
+ #
80
+ # @return [void]
81
+ def let?(helper_name, &block)
82
+ wrapped = lambda do
83
+ next super() if defined?(super())
84
+
85
+ instance_exec(&block)
86
+ end
87
+
88
+ let(helper_name, &wrapped)
89
+ end
90
+
91
+ # @overload subject(helper_name = nil, &block)
92
+ # Defines a memoized subject helper.
93
+ #
94
+ # @param helper_name [String, Symbol] the name of the helper method.
95
+ # @param block [Block] the implementation of the helper method.
96
+ #
97
+ # @return [void]
98
+ def subject(helper_name = nil, &)
99
+ let(:subject, &)
100
+
101
+ define_method(helper_name) { subject } if helper_name
102
+
103
+ self::HelperImplementations::NamedSuper.define_method(:subject) do
104
+ raise NotImplementedError, '`super` in named subjects is not supported'
105
+ end
106
+ end
107
+
108
+ # @overload subject!(helper_name = nil, &block)
109
+ # Defines a memoized subject helper and adds a hook to evaluate it.
110
+ #
111
+ # @param helper_name [String, Symbol] the name of the helper method.
112
+ # @param block [Block] the implementation of the helper method.
113
+ #
114
+ # @return [void]
115
+ def subject!(helper_name = nil, &)
116
+ subject(helper_name, &)
117
+
118
+ before(:example) { send(helper_name) }
119
+ end
120
+ end
121
+
122
+ include MemoizedHelpers
123
+ end