Skip to content

Commit

Permalink
Clean up ExampleConstants concern.
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepingkingstudios committed Sep 19, 2024
1 parent bd397ed commit 4420f2a
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 243 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ AllCops:
- rspec-sleeping_king_studios.gemspec
- lib/rspec/sleeping_king_studios.rb
- lib/rspec/sleeping_king_studios/concerns.rb
- lib/rspec/sleeping_king_studios/concerns/example_constants.rb
- lib/rspec/sleeping_king_studios/concerns/memoized_helpers.rb
- lib/rspec/sleeping_king_studios/configuration.rb
- lib/rspec/sleeping_king_studios/deferred.rb
Expand All @@ -22,6 +23,7 @@ AllCops:
- spec/integration/concerns/**/*
- spec/integration/deferred/**/*
- spec/rspec/sleeping_king_studios_spec.rb
- spec/rspec/sleeping_king_studios/concerns/example_constants_spec.rb
- spec/rspec/sleeping_king_studios/concerns/memoized_helpers_spec.rb
- spec/rspec/sleeping_king_studios/configuration_spec.rb
- spec/rspec/sleeping_king_studios/deferred/**/*
Expand All @@ -30,6 +32,7 @@ AllCops:
- spec/rspec/sleeping_king_studios/support/shared_examples/deferred_call_examples.rb
- spec/spec_helper.rb
- spec/support/integration/**/*.rb
- spec/support/mock_example_group.rb
- '*.thor'
Exclude:
- 'tmp/**/*'
Expand Down
181 changes: 107 additions & 74 deletions lib/rspec/sleeping_king_studios/concerns/example_constants.rb
Original file line number Diff line number Diff line change
@@ -1,107 +1,140 @@
# lib/rspec/sleeping_king_studios/concerns/example_constants.rb
# frozen_string_literal: true

require 'rspec/sleeping_king_studios/concerns'

module RSpec::SleepingKingStudios::Concerns
module ExampleConstants
# Methods for defining example-scoped classes and constants.
module ExampleConstants # rubocop:disable Metrics/ModuleLength
DEFAULT_VALUE = Object.new.freeze
private_constant :DEFAULT_VALUE

def self.assign_constant namespace, constant_name, constant_value
prior_value = DEFAULT_VALUE
class << self
# @api private
def define_class(class_name:, example:, base_class: nil, &block)
klass = Class.new(resolve_base_class(base_class))

if namespace.const_defined?(constant_name)
prior_value = namespace.const_get(constant_name)
end # if
klass.define_singleton_method(:name) { class_name }
klass.singleton_class.send(:alias_method, :inspect, :name)
klass.singleton_class.send(:alias_method, :to_s, :name)

namespace.const_set(constant_name, constant_value)
example.instance_exec(klass, &block) if block_given?

yield
ensure
if prior_value == DEFAULT_VALUE
namespace.send :remove_const, constant_name
else
namespace.const_set(constant_name, prior_value)
end # if-else
end # class method assign_constant
klass
end

def self.guard_existing_constant! namespace, constant_name
return unless namespace.const_defined?(constant_name)
# @api private
def with_constant( # rubocop:disable Metrics/MethodLength
constant_name:,
constant_value:,
namespace:,
force: false
)
guard_existing_constant!(namespace, constant_name) unless force

message =
"constant #{constant_name} is already defined with value "\
"#{namespace.const_get(constant_name).inspect}"
prior_value = DEFAULT_VALUE

raise NameError, message
end # class method guard_existing_constant!
if namespace.const_defined?(constant_name)
prior_value = namespace.const_get(constant_name)
end

def self.resolve_base_class value
value = value.fetch(:base_class, nil) if value.is_a?(Hash)
namespace.const_set(constant_name, constant_value)

return Object if value.nil?
yield
ensure
if prior_value == DEFAULT_VALUE
namespace.send :remove_const, constant_name
else
namespace.const_set(constant_name, prior_value)
end
end

return Object.const_get(value) if value.is_a?(String)
# @api private
def with_namespace(module_names) # rubocop:disable Metrics/MethodLength
last_defined = nil

value
end
resolved =
module_names.reduce(Object) do |namespace, module_name|
if namespace.const_defined?(module_name)
next namespace.const_get(module_name)
end

def self.resolve_namespace module_names
last_defined = nil
last_defined ||= { namespace:, module_name: }

resolved =
module_names.reduce(Object) do |ns, module_name|
next ns.const_get(module_name) if ns.const_defined?(module_name)
namespace.const_set(module_name, Module.new)
end

last_defined ||= { :namespace => ns, :module_name => module_name }
yield resolved
ensure
if last_defined
last_defined[:namespace]
.send(:remove_const, last_defined[:module_name])
end
end

ns.const_set(module_name, Module.new)
end # reduce
private

yield resolved
ensure
if last_defined
last_defined[:namespace].send(:remove_const, last_defined[:module_name])
end # if
end # class method resolve_namespace
def guard_existing_constant!(namespace, constant_name)
return unless namespace.const_defined?(constant_name)

def example_class class_name, base_class = nil, &block
class_name = class_name.to_s if class_name.is_a?(Symbol)
message =
"constant #{constant_name} is already defined with value " \
"#{namespace.const_get(constant_name).inspect}"

example_constant(class_name) do
klass = Class.new(ExampleConstants.resolve_base_class(base_class))
raise NameError, message
end

klass.define_singleton_method(:name) { class_name }
klass.singleton_class.send(:alias_method, :inspect, :name)
klass.singleton_class.send(:alias_method, :to_s, :name)
def resolve_base_class(value)
value = value.fetch(:base_class, nil) if value.is_a?(Hash)

instance_exec(klass, &block) if block_given?
return Object if value.nil?

klass
end # example_constant
end # method example_class
return Object.const_get(value) if value.is_a?(String)

def example_constant qualified_name, constant_value = DEFAULT_VALUE, force: false, &block
around(:example) do |wrapped_example|
example = wrapped_example.example
value
end
end

def example_class(class_name, base_class = nil, &block)
class_name = class_name.to_s if class_name.is_a?(Symbol)

example_constant(class_name) do
ExampleConstants.define_class(
base_class:,
class_name:,
example: self,
&block
)
end
end

def example_constant( # rubocop:disable Metrics/MethodLength
qualified_name,
constant_value = DEFAULT_VALUE,
force: false,
&block
)
around(:example) do |wrapped_example|
resolved_value =
if constant_value == DEFAULT_VALUE
block ? example.instance_exec(&block) : nil
if constant_value == DEFAULT_VALUE && block_given?
wrapped_example.example.instance_exec(&block)
else
constant_value
end # if

module_names = qualified_name.to_s.split('::')
constant_name = module_names.pop

ExampleConstants.resolve_namespace(module_names) do |namespace|
ExampleConstants.guard_existing_constant!(namespace, constant_name) unless force

ExampleConstants.assign_constant(namespace, constant_name, resolved_value) do
end

*module_names, constant_name = qualified_name.to_s.split('::')

ExampleConstants.with_namespace(module_names) do |namespace|
ExampleConstants.with_constant(
constant_name:,
constant_value: resolved_value,
namespace:,
force:
) \
do
wrapped_example.call
end # assign_constant
end # resolve_namespace
end # before example
end # method example_constant
end # module
end # module
end
end
end
end
end
end
Loading

0 comments on commit 4420f2a

Please sign in to comment.