Skip to content

Commit e672713

Browse files
test(coverage): PropertyGrid EditChain + statics — 80.70% → 80.79%
45 new tests targeting the pure-C# core of PropertyGridComponent.cs: PropertyGridDefaultsTests.cs (14, picked up from prior session): - PropertyLabelTemplate: DisplayName/Name fallback, AutomationName prefix, indent×4 margin, tooltip propagation - PropertyRowTemplate: FlexRow shape, Editor: prefix, indent×16 padding - ArrayItemTemplate: expand glyph ▶/▼, toggle inversion, [N] bracket format, 3/4/6-child action branches, ✕ callback wiring EditChainTests.cs (31, new): - BuildPath: empty-chain + multi-level dot-joining (pin: regression to "/" breaks every saved expand-state key) - CannotPropagate (6 branches): direct SetValue short-circuit; Compose-less + no-callback → true; OnRootChanged-only → false; root Compose-only → false; chain-entry SetValue makes leaf editable; mid-chain SetValue=null+Compose=null terminates true - PropagateNewOwner (4): empty path → OnRootChanged; mutable ancestor SetValue absorbs and stops (pin: no redundant root reassignment); multi-level immutable Compose chain to root; dead-end entry silently drops - PropagateImmutableEdit (2): mutable-ancestor SetValue absorbs Compose'd child (lines 379-384); no-chain + no-root-Compose silently drops without NRE - RenderReadOnlyValue via reflection (6): bool→ToggleSwitch disabled; bool null→IsOn=false; string→TextField disabled; string null→""; other→TextBlock.ToString(); other null→"(null)" - IsPrimitiveOrEnum via reflection (Theory×11): primitives, string, decimal, enum = true; class/record = false Merged: 80.70% → 80.79% line (+0.09), 68.83% → 69.00% branch (+0.17). Branch swing 1.9× line — branch-shaped-target heuristic holds again. 8,053/8,099 unit suite pass (one transient WindowPersistedScopeIsolation flake on first run; passes on retry, unrelated to this iteration). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 82a2f8f commit e672713

3 files changed

Lines changed: 867 additions & 0 deletions

File tree

docs/reports/coverage-uplift-85.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,3 +1343,116 @@ prior iteration), with `-Top` and `-MinMissed` knobs. Use it after
13431343
`[ExcludeFromCodeCoverage]` on the genuinely-host-bound
13441344
methods within those files (per-method exclusions, not
13451345
per-file).
1346+
1347+
### 2026-05-18 — PropertyGrid EditChain + statics (machine A, twelfth pass)
1348+
1349+
- Baseline at start: **80.70% line / 68.83% branch**.
1350+
- Area picked: **`Controls/PropertyGrid/PropertyGridComponent.cs`**
1351+
(68.4% line / 57.6% branch / 178 missed) — the `EditChain` internal
1352+
class and the file's static helpers (`RenderReadOnlyValue`,
1353+
`IsPrimitiveOrEnum`). Picked because:
1354+
- The file's `Render()` and `BlankButton` are host-bound
1355+
(`SolidColorBrush`, `FlexColumn` mount-time, `WithFlyout`) — but
1356+
the bottom half of the file (≈110 lines of `EditChain` + 3
1357+
static helpers) is pure C# with substantial branch logic.
1358+
- `EditChain.CannotPropagate` has 5 distinct decision points
1359+
gating the read-only-fallback in `RenderEditor` — each one
1360+
represents a real product-bug shape (e.g. "mutable ancestor
1361+
in chain makes leaf editable even without leaf SetValue").
1362+
- **Auditing existing tests** (`PropertyGridDecompositionTests.cs`):
1363+
`EditChain` was already partly exercised by
1364+
`Fully_Immutable_Root_Fires_OnRootChanged` and
1365+
`EditChain_Propagates_Through_Multiple_Immutable_Levels`, but
1366+
`CannotPropagate`, `PropagateNewOwner`, `BuildPath`, and the
1367+
"mutable ancestor absorbs composed child" branch of
1368+
`PropagateImmutableEdit` (lines 379-384) had zero coverage.
1369+
- **Picked up the untracked `PropertyGridDefaultsTests.cs` from a
1370+
prior session's worktree** (14 tests, all passing). Targets
1371+
`PropertyGridDefaults` templates: `PropertyLabelTemplate` (label
1372+
fallback, AutomationName prefix, indent×4 margin, tooltip),
1373+
`PropertyRowTemplate` (FlexRow shape, editor AutomationName,
1374+
indent×16 padding), `ArrayItemTemplate` (expand glyph
1375+
▶/▼, toggle inversion, [N] bracket, 3/4/6-child branches,
1376+
remove ✕ callback). `ArrayToolbarTemplate` skipped — calls
1377+
`.SemiBold()` which dereferences `FontWeights.SemiBold`, a WinRT
1378+
activation factory that throws COMException without packaged WinUI
1379+
(same trap class as iteration 4's ColorCompact and iteration 7's
1380+
brush-shaped CellRenderers).
1381+
- Added **31 new tests** in
1382+
`tests/Reactor.Tests/Controls/EditChainTests.cs`:
1383+
- **BuildPath (2)** — empty-chain returns just the property name;
1384+
multi-level joins with `.` (pin: a regression to `/` would
1385+
break every saved expand-state key).
1386+
- **CannotPropagate (6)** — direct SetValue short-circuits false;
1387+
empty-path + Compose-less root + null callback → true (the
1388+
only "read-only" terminal); empty-path + OnRootChanged → false;
1389+
empty-path + Compose → false; path-entry SetValue → false
1390+
(mutable ancestor unfreezes a leaf); path-entry SetValue=null
1391+
AND Compose=null → true (terminal mid-chain).
1392+
- **PropagateNewOwner (4)** — empty-path hits OnRootChanged with
1393+
the new owner; mutable ancestor `SetValue` returning Same(parent)
1394+
stops propagation (pin: a regression that always invoked
1395+
OnRootChanged would do a redundant root reassignment on every
1396+
leaf edit); multi-level Compose chain reaches root with
1397+
fully-immutable hierarchy; chain entry with neither SetValue
1398+
nor Compose silently drops.
1399+
- **PropagateImmutableEdit (2)** — mutable-ancestor branch
1400+
(lines 379-384): Compose builds new immutable child, then
1401+
SetValue on the mutable holder absorbs it. Pin: dropping the
1402+
SetValue call after Compose would lose the edit silently.
1403+
No-Compose-chain + no-Compose-root + callback → silent drop
1404+
without NRE.
1405+
- **RenderReadOnlyValue via reflection (6)** — bool true →
1406+
`ToggleSwitchElement.IsOn=true` with `Modifiers.IsEnabled=false`;
1407+
bool null → IsOn=false (the `?? false` coercion);
1408+
string → `TextFieldElement` with `Modifiers.IsEnabled=false`;
1409+
string null → Value=""; other-type → `TextBlockElement` with
1410+
`ToString()` content; other-type null → `(null)` sentinel
1411+
(pin: a regression to empty would visually hide null values).
1412+
- **IsPrimitiveOrEnum via reflection (Theory × 11)** — int/long/
1413+
double/bool/byte = primitive; string + decimal = special-cased
1414+
true; enum = true; object/class/record = false. Pin: the
1415+
`IsPrimitive || IsEnum || string || decimal` predicate exactly,
1416+
so that decimals don't get auto-decomposed in the grid.
1417+
- **Test results:** 45/45 new (14 PropertyGridDefaults + 31
1418+
EditChain) pass; **8,053 / 8,099 unit suite pass** (was 7,996 — clean
1419+
+57; one transient WindowPersistedScopeIsolation flake on first run
1420+
passed cleanly on second).
1421+
- **Coverage delta** (merged):
1422+
**80.70% → 80.79% line (+0.09)**, **68.83% → 69.00% branch (+0.17)**.
1423+
Branch swing 1.9× line — again validating the branch-shaped-target
1424+
heuristic. The 45 new tests hit ~70 net-new lines on a 101k
1425+
denominator (≈0.07% by arithmetic; the 0.02% surplus came from
1426+
incidental Element-record / FieldDescriptor constructor coverage).
1427+
- **Surprises / non-obvious findings:**
1428+
- **`TypeRegistry.Register` is generic-only**
1429+
(`Register<T>(TypeMetadata)`). My first draft used
1430+
`Register(typeof(X), ...)` and didn't compile. The next
1431+
agent should remember: to inject a Compose-less metadata
1432+
for an existing record-shaped type, call the generic
1433+
overload — there's no `Register(Type, ...)` non-generic.
1434+
- **`Decompose` on a Compose-less re-registration**: when you
1435+
`Register<T>(new TypeMetadata { Decompose = oldMeta.Decompose })`
1436+
you can keep field decomposition while explicitly removing
1437+
Compose, which is exactly the setup needed to test the
1438+
"terminal mid-chain Compose=null" branch.
1439+
- **The `WindowPersistedScopeIsolation` test fails intermittently
1440+
in parallel runs** — there's a `tools/flake-loop.ps1` and
1441+
`.flake-runs/` from prior work tracking it. It's pre-existing,
1442+
not caused by this iteration. The coverage script should retry
1443+
on transient failures; consider adding `-MaxRetry 1` to
1444+
`run-coverage.ps1` for the unit leg.
1445+
- **Hand-off:** This iteration validates that the PropertyGrid
1446+
family still has unit-testable surface despite the WinUI-bound
1447+
`Render()` method — the pure-C# helpers and the EditChain logic
1448+
are the testable core. Same shape as the iteration-8 lesson:
1449+
"host-bound files often have a pure-C# core that's underrated."
1450+
The remaining mid-tier unit-testable targets (per gap-report):
1451+
- `DevtoolsPropertyTools.cs` (714 missed, U) — reflection over
1452+
records, the biggest remaining unit-only hot spot. The most
1453+
valuable next pick if focusing on raw line gain.
1454+
- `ReflectionTypeMetadataProvider.cs` (sibling of TypeRegistry,
1455+
PropertyGrid family) — likely ≤30 lines uncovered after the
1456+
existing TypedColumnsBehaviorTests, but worth a 10-min audit.
1457+
- The deferral approval discussion (still pending) — would
1458+
jump the metric ~1.2 points without writing a single test.

0 commit comments

Comments
 (0)