You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#297 (PR #306) made "no test suite re-declares a collection-item shape the fixture provides" a build
failure. The guard works — and it scans exactly one assembly, FormCraft.ForMudBlazor.UnitTests.
FormCraft.ForFluentUI.UnitTests gained a full collection-field suite in #278 (merged as #291), and it
has already re-declared the fixture's models. Measured on dev:
OrderItem { string ProductName } is CollectionItemFixture.OrderItem, under the same name, in a
second assembly. Two more collection roots are alongside it — CollectionFieldTests.cs:207 (List<OrderLine>) and RenderPipelineParityTests.cs:175
(List<ParityLine>) — which is the #205 story restarting from zero in the newer adapter: the same
shapes, under drifting vocabularies, with nothing to notice.
This is not a hypothetical tail. It is the exact condition #205 was filed for, reproduced in a suite
the guard cannot see.
Proposed solution
Two things, in order — and the first is the reason this is not a one-line change:
The fixture is not reachable from the Fluent UI suite.CollectionItemFixture lives inside FormCraft.ForMudBlazor.UnitTests, and test projects do not reference one another. Sharing it means
extracting the framework-agnostic half — the models and the factories, which name no MudBlazor type
— into a small shared test-support project both suites reference. The builders return IFormConfiguration<T> and are equally framework-agnostic; only the RenderItemForm extension is
bUnit/MudBlazor-flavoured and stays behind.
Then make the guard scan both.TestAssemblyTypes() already exists; it needs to take an
assembly rather than hard-coding its own, and each suite runs the guard over its own.
Note the ownership rule needs a second look for the cross-assembly case: it currently reads
"namespace-scope = shared, nested = copy", and the Fluent UI copies are public and namespace-scope,
so they would be classified as shared rather than flagged. Ownership has to become "declared in the
shared fixture assembly", which is what step 1 makes expressible.
Leave it; the two suites are independent. Rejected on evidence — the duplication already exists,
under the same type names, and the adapter parity work (Bring FormCraft.ForFluentUI to parity with the MudBlazor adapter #278) guarantees more of it as Fluent UI
catches up with MudBlazor feature by feature.
Generalise the guard first, extract the fixture later. Rejected as the wrong order: a guard that
flags the Fluent UI models before there is any shared fixture for them to adopt reports a problem
with no legal fix, so the only available response is to allowlist all of them — which trains everyone
to reach for the allowlist.
Area
FormCraft.ForFluentUI.UnitTests / FormCraft.ForMudBlazor.UnitTests — test infrastructure
Four issues have now worked the same seam. #205 extracted CollectionItemFixture after eleven suites
each carried their own copies; #258 migrated eleven more and closed with a name-based grep; #282 found
the five that grep could not see; #297 made the rule mechanical so a fifth human sweep is not needed.
The guard #297 shipped is genuinely good at its job within FormCraft.ForMudBlazor.UnitTests. Its
blind spot is structural rather than logical: Assembly.GetTypes() on its own assembly. The Fluent UI
adapter's test suite is a second assembly, it did not exist in a meaningful form when the fixture was
extracted, and it now models collections of its own.
The awkward part is that the obvious fix — "point the guard at both assemblies" — cannot come first.
The Fluent UI suite has no access to CollectionItemFixture, so flagging its models would report a
defect with no available remedy.
Approaches
A. Extract the framework-agnostic half of the fixture into a shared test-support project, then scan both. Pros: removes the duplication rather than merely reporting it; the guard becomes cross-assembly for
free; the Fluent UI suite gets the same five-field-type coverage MudBlazor's suites get by default. Cons: a new project in the solution; the fixture's ownership rule has to move from
"namespace-scope vs nested" to "declared in the shared assembly".
B. Scan both assemblies now, allowlist the Fluent UI models. Pros: one-line-ish. Cons: the allowlist becomes where problems go to be silenced rather than where
exceptions are justified — the opposite of what #297 designed it for.
C. Leave the Fluent UI suite unguarded and document it. Pros: zero work. Cons: the duplication is already three models deep and grows with every parity
issue; documenting a known gap is how #258's check came to be trusted for four years' worth of drift.
Recommendation
A, in the stated order. It is the only option where the guard's report has a legal fix at the moment
it fires. The extraction is also smaller than it sounds: the models are dumb POCOs and the builders
return IFormConfiguration<T>, so only the bUnit render extension is adapter-specific.
📋 Spec
Goal
Both adapter test suites draw their collection-item models from one shared fixture, and the shape guard
runs over both assemblies.
Scope
A shared test-support project holding the fixture's models, factories and item-form builders.
Both test projects reference it; the Fluent UI collection suites migrate onto it.
The guard takes an assembly, and runs from both suites.
Ownership becomes "declared in the shared fixture assembly", replacing namespace-scope vs nested.
Non-goals
Any production-code change.
Moving CollectionItemFixtureRenderExtensions.RenderItemForm — it is bUnit + FormCraftComponent
flavoured, and each suite keeps its own render helper.
Unifying the two suites' tests. They test different adapters and must stay separate.
flowchart TD
S["FormCraft.TestSupport (new)<br/>models · factories · item-form builders<br/>+ CollectionItemShapeGuard"]
S --> M["FormCraft.ForMudBlazor.UnitTests<br/>RenderItemForm (bUnit/Mud)<br/>guard over its own assembly"]
S --> F["FormCraft.ForFluentUI.UnitTests<br/>its own render helper<br/>guard over its own assembly"]
Loading
Validation rules
Per-suite test counts unchanged for every migrated Fluent UI suite; project totals never fall.
The guard, run from the Fluent UI suite, reports zero offenders after migration and a non-zero
count before it (capture the before-list — it is the evidence the migration was needed).
No production file in git diff --name-only dev...HEAD.
Edge cases
The Fluent UI copies are public and namespace-scope, so today's ownership rule would call them
shared. The rule must key on the declaring assembly before the guard is pointed at them, or it
reports nothing and looks like it passed.
OrderModel exists in both assemblies under the same name. After extraction one must win;
the Fluent UI one also carries a root Reference field, which is the NamedOrderModel shape rather
than OrderModel's — check which fixture model each Fluent UI suite actually needs instead of
assuming the name maps.
FormCraft.UnitTests is a third assembly. It has no collection-item models today; decide whether
it references the shared project now or is left out until it needs to.
Assumptions
Base branch is dev. Test-only, so no version impact.
A shared test-support project is acceptable in the solution; if not, the fixture could instead be
compiled into both suites via a linked-file glob, which is uglier but avoids a project.
🛠️ Implementation plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: one shared collection-item fixture, consumed by both adapter test suites, with the shape
guard running over both assemblies.
Architecture: test infrastructure only. No production file may be modified.
Base branch dev; commit as Philippe Matray <phmatray@gmail.com>; conventional commits; PR title
ends (#<issue>) and is linted by pr-title-lint.yml.
TreatWarningsAsErrors=true — any warning fails the build. That is the format/lint gate.
dotnet test --filter is inert (MTP0001). Filter via the test host directly, after a build: FormCraft.ForFluentUI.UnitTests/bin/Release/net10.0/FormCraft.ForFluentUI.UnitTests --filter-class <FQN>.
Treat Zero tests ran (exit 8) as a hard stop.
Record per-suite test counts before starting and assert each is unchanged after.
CI gate is ./build.sh Test.
Task 1: Extract the framework-agnostic fixture into a shared test-support project
Files: create FormCraft.TestSupport/FormCraft.TestSupport.csproj and CollectionItemFixture.cs; modify FormCraft.sln, FormCraft.ForMudBlazor.UnitTests.csproj, FormCraft.ForMudBlazor.UnitTests/Fields/CollectionItemFixture.cs.
Interfaces: the existing models, factories and *ItemForm builders, moved verbatim; RenderItemForm stays in the MudBlazor suite.
Step 1: Record every suite's test count and the three project totals.
Step 2: Create the project (no bUnit/MudBlazor reference — if it needs one, the wrong half is moving) and add it to the solution.
Step 3: Move the models, factories and item-form builders across, leaving CollectionItemFixtureRenderExtensions in the MudBlazor suite; reference the new project from FormCraft.ForMudBlazor.UnitTests.
Step 4: Run dotnet build -c Release --no-incremental → 0 warnings, then dotnet test -c Release → PASS with every count unchanged.
Step 5: Commit: test: extract the collection-item fixture into a shared test-support project.
Task 2: Re-key the guard's ownership rule to the fixture assembly
Files: modify FormCraft.ForMudBlazor.UnitTests/Fields/CollectionItemShapeGuard.cs + its tests (move both to the shared project if the guard is to run from both suites).
Interfaces:TestAssemblyTypes(Assembly assembly); IsSharedShape keyed on the declaring assembly rather than on nesting.
Step 1: Write the failing test: a namespace-scope, public copy in a non-fixture assembly must be reported as an offender (today's nesting rule calls it shared).
Step 2: Run the guard suite → FAIL.
Step 3: Re-key IsSharedShape to "declared in the fixture's assembly" and thread an Assembly parameter through TestAssemblyTypes.
Step 4: Run dotnet test -c Release → PASS, MudBlazor's own guard still reporting zero offenders.
Step 5: Commit: test: key collection-item ownership on the fixture assembly.
Task 3: Point the guard at the Fluent UI suite and migrate what it flags
Files: create the guard entry point in FormCraft.ForFluentUI.UnitTests; modify Components/CollectionFieldSubmitTests.cs, Fields/CollectionFieldTests.cs, Components/RenderPipelineParityTests.cs.
Interfaces: none new — Tasks 1-2 provide them.
Step 1: Add the guard test to the Fluent UI suite and run it; record the offender list — it is the evidence this issue is about, and the before/after pair is what proves the migration landed.
Step 2: Record each flagged suite's test count.
Step 3: Migrate each flagged model onto the shared fixture, checking which fixture shape each suite actually needs (OrderModel there carries a root Reference, which is NamedOrderModel's shape, not OrderModel's).
Step 4: Allowlist, with a written reason, any model that is genuinely bespoke to the Fluent UI suite rather than migrating it.
Step 5: Run dotnet test -c Release → PASS, every recorded count unchanged, and the Fluent UI guard now reporting zero offenders.
Step 6: Run dotnet build -c Release --no-incremental → 0 warnings, then ./build.sh Test → green.
Step 7: Confirm git diff --name-only dev...HEAD lists no production file.
Step 8: Commit: test(fluentui): adopt the shared collection-item fixture.
Problem / motivation
#297 (PR #306) made "no test suite re-declares a collection-item shape the fixture provides" a build
failure. The guard works — and it scans exactly one assembly,
FormCraft.ForMudBlazor.UnitTests.FormCraft.ForFluentUI.UnitTestsgained a full collection-field suite in #278 (merged as #291), and ithas already re-declared the fixture's models. Measured on
dev:OrderItem { string ProductName }isCollectionItemFixture.OrderItem, under the same name, in asecond assembly. Two more collection roots are alongside it —
CollectionFieldTests.cs:207(List<OrderLine>) andRenderPipelineParityTests.cs:175(
List<ParityLine>) — which is the #205 story restarting from zero in the newer adapter: the sameshapes, under drifting vocabularies, with nothing to notice.
This is not a hypothetical tail. It is the exact condition #205 was filed for, reproduced in a suite
the guard cannot see.
Proposed solution
Two things, in order — and the first is the reason this is not a one-line change:
CollectionItemFixturelives insideFormCraft.ForMudBlazor.UnitTests, and test projects do not reference one another. Sharing it meansextracting the framework-agnostic half — the models and the factories, which name no MudBlazor type
— into a small shared test-support project both suites reference. The builders return
IFormConfiguration<T>and are equally framework-agnostic; only theRenderItemFormextension isbUnit/MudBlazor-flavoured and stays behind.
TestAssemblyTypes()already exists; it needs to take anassembly rather than hard-coding its own, and each suite runs the guard over its own.
Note the ownership rule needs a second look for the cross-assembly case: it currently reads
"namespace-scope = shared, nested = copy", and the Fluent UI copies are
publicand namespace-scope,so they would be classified as shared rather than flagged. Ownership has to become "declared in the
shared fixture assembly", which is what step 1 makes expressible.
Alternatives considered
fixtures drift exactly the way the eleven per-suite copies did before Extract a shared collection-item test fixture instead of copying models per suite #205.
under the same type names, and the adapter parity work (Bring FormCraft.ForFluentUI to parity with the MudBlazor adapter #278) guarantees more of it as Fluent UI
catches up with MudBlazor feature by feature.
flags the Fluent UI models before there is any shared fixture for them to adopt reports a problem
with no legal fix, so the only available response is to allowlist all of them — which trains everyone
to reach for the allowlist.
Area
FormCraft.ForFluentUI.UnitTests / FormCraft.ForMudBlazor.UnitTests — test infrastructure
Follow-up from #306. Related: #205, #258, #282, #297, #278
🧠 Brainstorm
Problem / context
Four issues have now worked the same seam. #205 extracted
CollectionItemFixtureafter eleven suiteseach carried their own copies; #258 migrated eleven more and closed with a name-based grep; #282 found
the five that grep could not see; #297 made the rule mechanical so a fifth human sweep is not needed.
The guard #297 shipped is genuinely good at its job within
FormCraft.ForMudBlazor.UnitTests. Itsblind spot is structural rather than logical:
Assembly.GetTypes()on its own assembly. The Fluent UIadapter's test suite is a second assembly, it did not exist in a meaningful form when the fixture was
extracted, and it now models collections of its own.
The awkward part is that the obvious fix — "point the guard at both assemblies" — cannot come first.
The Fluent UI suite has no access to
CollectionItemFixture, so flagging its models would report adefect with no available remedy.
Approaches
A. Extract the framework-agnostic half of the fixture into a shared test-support project, then scan both.
Pros: removes the duplication rather than merely reporting it; the guard becomes cross-assembly for
free; the Fluent UI suite gets the same five-field-type coverage MudBlazor's suites get by default.
Cons: a new project in the solution; the fixture's ownership rule has to move from
"namespace-scope vs nested" to "declared in the shared assembly".
B. Scan both assemblies now, allowlist the Fluent UI models.
Pros: one-line-ish. Cons: the allowlist becomes where problems go to be silenced rather than where
exceptions are justified — the opposite of what #297 designed it for.
C. Leave the Fluent UI suite unguarded and document it.
Pros: zero work. Cons: the duplication is already three models deep and grows with every parity
issue; documenting a known gap is how #258's check came to be trusted for four years' worth of drift.
Recommendation
A, in the stated order. It is the only option where the guard's report has a legal fix at the moment
it fires. The extraction is also smaller than it sounds: the models are dumb POCOs and the builders
return
IFormConfiguration<T>, so only the bUnit render extension is adapter-specific.📋 Spec
Goal
Both adapter test suites draw their collection-item models from one shared fixture, and the shape guard
runs over both assemblies.
Scope
Non-goals
CollectionItemFixtureRenderExtensions.RenderItemForm— it is bUnit +FormCraftComponentflavoured, and each suite keeps its own render helper.
allowlisted where they match.
Shape
flowchart TD S["FormCraft.TestSupport (new)<br/>models · factories · item-form builders<br/>+ CollectionItemShapeGuard"] S --> M["FormCraft.ForMudBlazor.UnitTests<br/>RenderItemForm (bUnit/Mud)<br/>guard over its own assembly"] S --> F["FormCraft.ForFluentUI.UnitTests<br/>its own render helper<br/>guard over its own assembly"]Validation rules
count before it (capture the before-list — it is the evidence the migration was needed).
dotnet build -c Release --no-incremental→ 0 warnings;./build.sh Test→ green.git diff --name-only dev...HEAD.Edge cases
publicand namespace-scope, so today's ownership rule would call themshared. The rule must key on the declaring assembly before the guard is pointed at them, or it
reports nothing and looks like it passed.
OrderModelexists in both assemblies under the same name. After extraction one must win;the Fluent UI one also carries a root
Referencefield, which is theNamedOrderModelshape ratherthan
OrderModel's — check which fixture model each Fluent UI suite actually needs instead ofassuming the name maps.
FormCraft.UnitTestsis a third assembly. It has no collection-item models today; decide whetherit references the shared project now or is left out until it needs to.
Assumptions
dev. Test-only, so no version impact.compiled into both suites via a linked-file glob, which is uglier but avoids a project.
🛠️ Implementation plan
Goal: one shared collection-item fixture, consumed by both adapter test suites, with the shape
guard running over both assemblies.
Architecture: test infrastructure only. No production file may be modified.
Tech stack: .NET 10 test projects (libraries multi-target
net8.0;net10.0), xUnit v3 +Microsoft.Testing.Platform, bUnit, Shouldly.
Global constraints:
dev; commit asPhilippe Matray <phmatray@gmail.com>; conventional commits; PR titleends
(#<issue>)and is linted bypr-title-lint.yml.TreatWarningsAsErrors=true— any warning fails the build. That is the format/lint gate.dotnet test --filteris inert (MTP0001). Filter via the test host directly, after a build:FormCraft.ForFluentUI.UnitTests/bin/Release/net10.0/FormCraft.ForFluentUI.UnitTests --filter-class <FQN>.Treat
Zero tests ran(exit 8) as a hard stop../build.sh Test.Task 1: Extract the framework-agnostic fixture into a shared test-support project
Files: create
FormCraft.TestSupport/FormCraft.TestSupport.csprojandCollectionItemFixture.cs; modifyFormCraft.sln,FormCraft.ForMudBlazor.UnitTests.csproj,FormCraft.ForMudBlazor.UnitTests/Fields/CollectionItemFixture.cs.Interfaces: the existing models, factories and
*ItemFormbuilders, moved verbatim;RenderItemFormstays in the MudBlazor suite.CollectionItemFixtureRenderExtensionsin the MudBlazor suite; reference the new project fromFormCraft.ForMudBlazor.UnitTests.dotnet build -c Release --no-incremental→ 0 warnings, thendotnet test -c Release→ PASS with every count unchanged.test: extract the collection-item fixture into a shared test-support project.Task 2: Re-key the guard's ownership rule to the fixture assembly
Files: modify
FormCraft.ForMudBlazor.UnitTests/Fields/CollectionItemShapeGuard.cs+ its tests (move both to the shared project if the guard is to run from both suites).Interfaces:
TestAssemblyTypes(Assembly assembly);IsSharedShapekeyed on the declaring assembly rather than on nesting.publiccopy in a non-fixture assembly must be reported as an offender (today's nesting rule calls it shared).IsSharedShapeto "declared in the fixture's assembly" and thread anAssemblyparameter throughTestAssemblyTypes.dotnet test -c Release→ PASS, MudBlazor's own guard still reporting zero offenders.test: key collection-item ownership on the fixture assembly.Task 3: Point the guard at the Fluent UI suite and migrate what it flags
Files: create the guard entry point in
FormCraft.ForFluentUI.UnitTests; modifyComponents/CollectionFieldSubmitTests.cs,Fields/CollectionFieldTests.cs,Components/RenderPipelineParityTests.cs.Interfaces: none new — Tasks 1-2 provide them.
OrderModelthere carries a rootReference, which isNamedOrderModel's shape, notOrderModel's).dotnet test -c Release→ PASS, every recorded count unchanged, and the Fluent UI guard now reporting zero offenders.dotnet build -c Release --no-incremental→ 0 warnings, then./build.sh Test→ green.git diff --name-only dev...HEADlists no production file.test(fluentui): adopt the shared collection-item fixture.