fix(central-ui): InstancePicker — field above the backdrop; site change always closes; reference contract documented; tests

Three Task 2 review findings:

1. The outside-click backdrop is a fixed full-viewport layer at z-index 1040,
   but the input-group was unpositioned — so once the panel was open a click on
   the field or the chevron hit the backdrop and closed the panel instead of
   letting the operator keep typing. The field now carries .ip-field
   (position:relative, z-index 1042), stacking backdrop < panel < field. Pinned
   in bUnit by the class (the z-index itself is scoped CSS, verified visually).

2. OnParametersSet closes the panel on ANY lists change, not only when the
   selection disappeared: an open panel is showing the previous site's tree,
   which is wrong even when the selected instance happens to exist in both.
   The reference-equality contract on Areas/Instances is now stated on both
   parameters — pass a stable reference, replace it only when the set changed.

3. Tests: the chevron closes an open panel; two pickers mint distinct panel ids
   (and matching aria-controls); an unrelated parameter change with the SAME
   Instances reference leaves a half-typed filter and the open panel alone.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-08 09:39:46 -04:00
parent 621d6bfd94
commit 3c3a7cbbc1
4 changed files with 95 additions and 7 deletions
@@ -355,4 +355,69 @@ public class InstancePickerTests : BunitContext
Assert.Equal(new[] { "Line1", "Zulu" }, Rows(cut));
Assert.Empty(cut.FindAll("[data-instance-id='7']"));
}
[Fact]
public void Toggle_WhileOpen_ClosesThePanel()
{
var cut = RenderPicker();
Open(cut);
Assert.Single(cut.FindAll("[data-test='t-panel']"));
Open(cut);
Assert.Empty(cut.FindAll("[data-test='t-panel']"));
Assert.Equal("false", cut.Find("[data-test='t-input']").GetAttribute("aria-expanded"));
}
[Fact]
public void TheFieldSitsAboveTheBackdrop_SoItStaysClickableWhileOpen()
{
// The backdrop is a fixed full-viewport layer; the stylesheet lifts the field above
// it through this class. Without it a click on the input or the chevron would land on
// the backdrop instead and close the panel. Only the class can be pinned here — the
// z-index itself is scoped CSS, verified visually.
var cut = RenderPicker();
Open(cut);
var field = cut.Find("[data-test='t-picker'] .input-group");
Assert.Contains("ip-field", field.ClassList);
Assert.Single(cut.FindAll("[data-test='t-backdrop']"));
}
[Fact]
public void TwoPickers_GetDistinctPanelIds()
{
// The panel id (and the field's aria-controls pointing at it) is minted per component
// instance, so two pickers on one page cannot collide on a DOM id.
var first = RenderPicker();
var second = RenderPicker();
Open(first);
Open(second);
var firstPanel = first.Find("[data-test='t-panel']").Id;
var secondPanel = second.Find("[data-test='t-panel']").Id;
Assert.False(string.IsNullOrEmpty(firstPanel));
Assert.NotEqual(firstPanel, secondPanel);
Assert.Equal(firstPanel, first.Find("[data-test='t-input']").GetAttribute("aria-controls"));
Assert.Equal(secondPanel, second.Find("[data-test='t-input']").GetAttribute("aria-controls"));
}
[Fact]
public void SameInstancesReference_LeavesTheTypedTextAndOpenPanelAlone()
{
// Complement of SiteChange_NewInstancesList_ClearsAStaleSelectionText: an unrelated
// parameter change is NOT a site change, so a half-typed filter must survive it.
var cut = RenderPicker();
Open(cut);
Type(cut, "zu");
cut.Render(ps => ps.Add(p => p.Placeholder, "Pick an instance"));
Assert.Equal("zu", InputValue(cut));
Assert.Single(cut.FindAll("[data-test='t-panel']"));
Assert.Equal(new[] { "Zulu" }, Rows(cut));
}
}