test(dv-3): add 4-level roll-up + deep-leaf filter tests; return AsReadOnly; add caller-contract remark

Fix 1 (Important): RollUp_FourLevelDeepBadQuality_ReachesRoot — proves bad quality at a
4-segment-deep leaf propagates HasBadQuality up every ancestor to the root.

Fix 2 (Important): Filter_DeepLeafMatch_RetainsAllAncestorBranches — proves filtering on
a terminal segment of a 3-level path retains all ancestor branches.

Fix 3 (Minor): BuildAttributeTree now returns roots.AsReadOnly() so the returned
IReadOnlyList<DebugTreeNode> reference is not a mutable list.

Fix 4 (Minor): Added <remarks> XML doc to BuildAttributeTree noting the caller-contract
that at most one AttributeValueChanged per AttributeName should be passed.

All 18 DebugTreeBuilder tests pass.
This commit is contained in:
Joseph Doherty
2026-06-17 15:09:01 -04:00
parent b5347faf44
commit 69b83379d5
2 changed files with 37 additions and 1 deletions
@@ -21,6 +21,13 @@ public static class DebugTreeBuilder
/// <see cref="AttributeValueChanged.AttributeName"/>). Null/empty/whitespace
/// keeps everything; matching leaves carry along their ancestor branches.
/// </param>
/// <remarks>
/// The caller is expected to pass at most one <see cref="AttributeValueChanged"/>
/// per <see cref="AttributeValueChanged.AttributeName"/>. The DebugView page
/// enforces this by keying a dictionary on the attribute name before calling this
/// method. Passing duplicate names would produce sibling leaves with identical
/// keys under the same parent branch.
/// </remarks>
public static IReadOnlyList<DebugTreeNode> BuildAttributeTree(
IEnumerable<AttributeValueChanged> attributes, string? filter)
{
@@ -71,7 +78,7 @@ public static class DebugTreeBuilder
}
SortAndRollUp(roots);
return roots;
return roots.AsReadOnly();
}
/// <summary>