From f2b8d3d634f4a089807a88de0f498a705a3ce1ad Mon Sep 17 00:00:00 2001 From: Ngan Pham Date: Sat, 28 Feb 2026 21:48:49 -0800 Subject: [PATCH] Fix fixture declarations not propagating to auto-included shared example groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When shared example groups are auto-included via `config.include_context` with metadata filters, RSpec creates those child groups during configuration — before the spec file's `fixture` call sets the declaration on the parent group's metadata. Since RSpec snapshots parent metadata into child groups at creation time, the child's metadata never includes the fixture declaration, and the `prepend_before` hook never fires for those examples. The fix uses RSpec's own `update_inherited_metadata` to propagate the fixture declaration to any child groups that already exist when `fixture` is called. This is the same mechanism RSpec uses internally to push metadata updates to descendants. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/fixture_kit/rspec.rb | 6 +++- .../integration/fixture_kit_integration.rb | 29 +++++++++++++++++++ spec/integration/dummy_app_spec.rb | 7 +++++ spec/unit/rspec_entrypoint_spec.rb | 4 +++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/fixture_kit/rspec.rb b/lib/fixture_kit/rspec.rb index 00281da..8170cd2 100644 --- a/lib/fixture_kit/rspec.rb +++ b/lib/fixture_kit/rspec.rb @@ -30,7 +30,11 @@ module ClassMethods def fixture(name = nil, extends: nil, &block) definition = Definition.new(extends: extends, &block) if block_given? declaration = ::RSpec.configuration.fixture_kit.register(name || definition, self) - metadata[DECLARATION_METADATA_KEY] = declaration + # Use update_inherited_metadata to set the declaration on this group + # AND propagate to any child groups already created (e.g., shared + # example groups auto-included via `config.include_context` during + # RSpec configuration). + update_inherited_metadata(DECLARATION_METADATA_KEY => declaration) prepend_before(:context) do self.class.metadata[DECLARATION_METADATA_KEY].generate diff --git a/spec/dummy/spec/integration/fixture_kit_integration.rb b/spec/dummy/spec/integration/fixture_kit_integration.rb index 7367fa5..b6b3952 100644 --- a/spec/dummy/spec/integration/fixture_kit_integration.rb +++ b/spec/dummy/spec/integration/fixture_kit_integration.rb @@ -12,6 +12,26 @@ module FixtureKitIntegrationTimeHelpers config.include(FixtureKitIntegrationTimeHelpers) end +# Shared examples auto-included via config.include_context to reproduce the +# ordering issue: RSpec creates the shared group during configuration (before +# the host group's `fixture` call sets metadata), so fixture declarations +# must propagate through runtime metadata inheritance. +RSpec.shared_examples "auto-included shared examples" do + context "inside auto-included shared context" do + let(:shared_user) { User.find_by!(email: "alice@team.test") } + + it "can access fixture data through let blocks defined in the host group" do + expect(fixture.alice.email).to eq("alice@team.test") + expect(shared_user).to eq(fixture.alice) + puts "FKIT_ASSERT:SHARED_EXAMPLE_FIXTURE_ACCESS" + end + end +end + +RSpec.configure do |config| + config.include_context "auto-included shared examples", :include_shared_fixture_test +end + RSpec.describe "FixtureKit integration" do describe "fixture preload timing" do fixture "teams/basic" @@ -220,6 +240,15 @@ module FixtureKitIntegrationTimeHelpers end end + describe "fixture with auto-included shared examples", :include_shared_fixture_test do + fixture "teams/basic" + + it "works in the host group" do + expect(fixture.alice.name).to eq("Alice") + puts "FKIT_ASSERT:SHARED_EXAMPLE_HOST_FIXTURE" + end + end + describe "fixture instance reader without declaration" do it "raises a helpful error message" do expect do diff --git a/spec/integration/dummy_app_spec.rb b/spec/integration/dummy_app_spec.rb index 27685a0..610aa06 100644 --- a/spec/integration/dummy_app_spec.rb +++ b/spec/integration/dummy_app_spec.rb @@ -76,6 +76,13 @@ def run_dummy_tests "FKIT_ASSERT:UNDECLARED_FIXTURE_READER" ] + if INTEGRATION_FRAMEWORK == "rspec" + expected_markers += [ + "FKIT_ASSERT:SHARED_EXAMPLE_FIXTURE_ACCESS", + "FKIT_ASSERT:SHARED_EXAMPLE_HOST_FIXTURE" + ] + end + expected_markers.each do |marker| expect(output).to include(marker), "Expected marker #{marker.inspect} in output.\nOutput:\n#{output}" end diff --git a/spec/unit/rspec_entrypoint_spec.rb b/spec/unit/rspec_entrypoint_spec.rb index a796f53..1e8e657 100644 --- a/spec/unit/rspec_entrypoint_spec.rb +++ b/spec/unit/rspec_entrypoint_spec.rb @@ -109,6 +109,10 @@ def build_group(parent_metadata = {}) @fixture_kit_before_context_hook end + define_singleton_method(:update_inherited_metadata) do |updates| + metadata.update(updates) + end + define_singleton_method(:append_after) do |scope, &block| raise "Unexpected scope: #{scope}" unless scope == :context