From 4905c554088b0491e23ff252dcbb15bac9bae939 Mon Sep 17 00:00:00 2001 From: Rob Smith Date: Sat, 8 Jun 2024 23:26:59 -0400 Subject: [PATCH] Implement MemoizedHelpers#let? --- .../deferred/dsl/memoized_helpers.rb | 20 ++++++++ .../deferred/optional_helpers_spec.fixture.rb | 9 ++++ .../deferred/optional_helpers_spec.rb | 27 +++++++++++ .../integration/deferred/orbit_examples.rb | 47 +++++++++++++++++++ spec/support/models/rocket.rb | 5 +- .../shared_examples/deferred_examples.rb | 38 +++++++++++---- 6 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 spec/integration/deferred/optional_helpers_spec.fixture.rb create mode 100644 spec/integration/deferred/optional_helpers_spec.rb create mode 100644 spec/support/integration/deferred/orbit_examples.rb diff --git a/lib/rspec/sleeping_king_studios/deferred/dsl/memoized_helpers.rb b/lib/rspec/sleeping_king_studios/deferred/dsl/memoized_helpers.rb index cca6bf2..7b1c78f 100644 --- a/lib/rspec/sleeping_king_studios/deferred/dsl/memoized_helpers.rb +++ b/lib/rspec/sleeping_king_studios/deferred/dsl/memoized_helpers.rb @@ -28,6 +28,7 @@ def self.extended(other) ) end + # @api private def call(example_group) super @@ -66,6 +67,25 @@ def let!(helper_name, &) before(:example) { send(helper_name) } end + # Defines an optional memoized helper. + # + # The helper will use the parent value if defined; otherwise, will use the + # given value. + # + # @param helper_name [String, Symbol] the name of the helper method. + # @param block [Block] the implementation of the helper method. + # + # @return [void] + def let?(helper_name, &block) + wrapped = lambda do + next super() if defined?(super()) + + instance_exec(&block) + end + + let(helper_name, &wrapped) + end + # Defines a memoized subject helper. # # @param helper_name [String, Symbol] the name of the helper method. diff --git a/spec/integration/deferred/optional_helpers_spec.fixture.rb b/spec/integration/deferred/optional_helpers_spec.fixture.rb new file mode 100644 index 0000000..4984c24 --- /dev/null +++ b/spec/integration/deferred/optional_helpers_spec.fixture.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'support/integration/deferred/orbit_examples' + +RSpec.describe Spec::Models::Rocket do + subject(:rocket) { described_class.new('Imp IV') } + + include Spec::Integration::Deferred::OrbitExamples +end diff --git a/spec/integration/deferred/optional_helpers_spec.rb b/spec/integration/deferred/optional_helpers_spec.rb new file mode 100644 index 0000000..1d11e3b --- /dev/null +++ b/spec/integration/deferred/optional_helpers_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rspec/sleeping_king_studios/deferred/examples' + +require 'support/sandbox' + +RSpec.describe RSpec::SleepingKingStudios::Deferred::Examples do + let(:fixture_file) do + %w[spec/integration/deferred/optional_helpers_spec.fixture.rb] + end + let(:result) do + Spec::Support::Sandbox.run(fixture_file) + end + let(:expected_examples) do + <<~EXAMPLES.lines.map(&:strip) + Spec::Models::Rocket#launch when the rocket is launched should set the orbit + Spec::Models::Rocket#launch with orbit: value when the rocket is launched should set the orbit + Spec::Models::Rocket#orbit is expected to equal nil + EXAMPLES + end + + it 'should apply the deferred examples', :aggregate_failures do + expect(result.summary).to be == '3 examples, 0 failures' + + expect(result.example_descriptions).to be == expected_examples + end +end diff --git a/spec/support/integration/deferred/orbit_examples.rb b/spec/support/integration/deferred/orbit_examples.rb new file mode 100644 index 0000000..3617fa9 --- /dev/null +++ b/spec/support/integration/deferred/orbit_examples.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'support/integration/deferred' + +module Spec::Integration::Deferred + module ShouldSetTheOrbit + include RSpec::SleepingKingStudios::Deferred::Examples + + let?(:expected_orbit) { nil } + + it 'should set the orbit' do + launch_rocket + + expect(subject.orbit).to eq expected_orbit + end + end + + module OrbitExamples + include RSpec::SleepingKingStudios::Deferred::Examples + + describe '#launch' do + let(:launch_site) { 'KSC' } + let(:options) { {} } + + def launch_rocket + subject.launch(launch_site:, **options) + end + + context 'when the rocket is launched' do # rubocop:disable RSpec/EmptyExampleGroup + include Spec::Integration::Deferred::ShouldSetTheOrbit + end + + describe 'with orbit: value' do + let(:expected_orbit) { options[:orbit] } + let(:options) { { orbit: { periapsis: '100 km', apoapsis: '300 km' } } } + + context 'when the rocket is launched' do # rubocop:disable RSpec/EmptyExampleGroup + include Spec::Integration::Deferred::ShouldSetTheOrbit + end + end + end + + describe '#orbit' do + it { expect(subject.orbit).to be nil } + end + end +end diff --git a/spec/support/models/rocket.rb b/spec/support/models/rocket.rb index 7c5fe2b..56d0d03 100644 --- a/spec/support/models/rocket.rb +++ b/spec/support/models/rocket.rb @@ -19,16 +19,19 @@ def initialize(name) @launched = false @launch_site = nil + @orbit = nil @payload = {} end attr_reader \ :launch_site, + :orbit, :payload - def launch(launch_site:, payload: {}) + def launch(launch_site:, orbit: nil, payload: {}) @launched = true @launch_site = launch_site + @orbit = orbit @payload = payload end diff --git a/spec/support/shared_examples/deferred_examples.rb b/spec/support/shared_examples/deferred_examples.rb index 6dd7f34..9acaa0f 100644 --- a/spec/support/shared_examples/deferred_examples.rb +++ b/spec/support/shared_examples/deferred_examples.rb @@ -445,7 +445,7 @@ module DeferredExamples shared_examples 'should define memoized helpers' do shared_examples 'should define a memoized helper macro' \ - do |method_name, before: false, subject: false| + do |method_name, before: false, optional: false, subject: false| shared_context 'when the helper is defined' do before(:example) { define_helper } end @@ -545,9 +545,13 @@ def call_helper parent_group.let(helper_name, &existing_block) end - include_examples 'should call and memoize the block value' + if optional + include_examples 'should call the existing implementation' + else + include_examples 'should call and memoize the block value' - include_examples 'should wrap the existing implementation' + include_examples 'should wrap the existing implementation' + end end context 'when a method is declared in a parent example group' do @@ -555,9 +559,13 @@ def call_helper parent_group.define_method(helper_name, &existing_block) end - include_examples 'should call and memoize the block value' + if optional + include_examples 'should call the existing implementation' + else + include_examples 'should call and memoize the block value' - include_examples 'should wrap the existing implementation' + include_examples 'should wrap the existing implementation' + end end context 'when a helper is defined in the example scope' do @@ -597,7 +605,11 @@ def call_helper ancestor_class.let(helper_name) { -1 } end - include_examples 'should call and memoize the block value' + if optional + include_examples 'should call the existing implementation' + else + include_examples 'should call and memoize the block value' + end end context 'when a method is defined in inherited examples' do @@ -605,7 +617,11 @@ def call_helper ancestor_class.define_method(helper_name) { -1 } end - include_examples 'should call and memoize the block value' + if optional + include_examples 'should call the existing implementation' + else + include_examples 'should call and memoize the block value' + end end if before @@ -717,9 +733,11 @@ def call_helper before: true end - # describe '.let?' do - # pending - # end + describe '.let?' do + include_examples 'should define a memoized helper macro', + :let?, + optional: true + end describe '.subject' do include_examples 'should define a memoized helper macro',