-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bd397ed
commit 4420f2a
Showing
6 changed files
with
332 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 107 additions & 74 deletions
181
lib/rspec/sleeping_king_studios/concerns/example_constants.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.