Merge branch 'worktree-agent-a6cee26f4fe0abdd1' into feat/instance-picker
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
@*
|
||||||
|
Instance Picker — a combobox whose dropdown is the site's area tree (design
|
||||||
|
2026-09-08). The field shows the selected instance; the panel hosts the shared
|
||||||
|
TreeView over InstanceTreeBuilder's forest with every area collapsed. Clicking an
|
||||||
|
area row toggles it, clicking an instance row selects it and closes the panel.
|
||||||
|
Bootstrap only, no JavaScript: the transparent fixed backdrop closes the panel on
|
||||||
|
an outside click, the same pattern TreeView uses for its context menu.
|
||||||
|
*@
|
||||||
|
@namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared
|
||||||
|
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances
|
||||||
|
|
||||||
|
<div class="ip-wrap position-relative" data-test="@($"{DataTestPrefix}-picker")">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" class="form-control" role="combobox" autocomplete="off"
|
||||||
|
id="@_inputId" aria-autocomplete="list" aria-haspopup="tree"
|
||||||
|
aria-expanded="@(_open ? "true" : "false")" aria-controls="@_panelId"
|
||||||
|
placeholder="@Placeholder" disabled="@Disabled"
|
||||||
|
data-test="@($"{DataTestPrefix}-input")"
|
||||||
|
value="@_text" @onfocus="Open" />
|
||||||
|
<button type="button" class="btn btn-outline-secondary" tabindex="-1" aria-label="Browse instances"
|
||||||
|
disabled="@Disabled" data-test="@($"{DataTestPrefix}-toggle")" @onclick="Toggle">
|
||||||
|
<i class="bi @(_open ? "bi-chevron-up" : "bi-chevron-down")"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@if (_open)
|
||||||
|
{
|
||||||
|
<div class="ip-backdrop" data-test="@($"{DataTestPrefix}-backdrop")" @onclick="Close"></div>
|
||||||
|
<div class="ip-panel border rounded bg-body shadow-sm" id="@_panelId" data-test="@($"{DataTestPrefix}-panel")">
|
||||||
|
<TreeView @ref="_tree" TItem="InstanceTreeNode" Items="_roots"
|
||||||
|
ChildrenSelector="n => n.Children" HasChildrenSelector="n => n.HasChildren"
|
||||||
|
KeySelector="n => n.Key" IndentPx="16">
|
||||||
|
<NodeContent Context="node">
|
||||||
|
<span class="tv-glyph"><i class="bi @(node.IsInstance ? "bi-box" : "bi-diagram-3")"></i></span>
|
||||||
|
<span class="tv-label @(node.IsInstance ? "ip-instance" : "ip-area")" title="@node.Label"
|
||||||
|
data-test="@($"{DataTestPrefix}-row")" data-instance-id="@(node.Instance?.Id)"
|
||||||
|
@onclick="() => OnRowClickAsync(node)">@RowLabel(node)</span>
|
||||||
|
</NodeContent>
|
||||||
|
<EmptyContent><span class="text-muted fst-italic small px-2">@(_hasFilter ? "No instances match." : "No instances at this site.")</span></EmptyContent>
|
||||||
|
</TreeView>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A combobox that picks one instance out of a site's area tree: a text field over a
|
||||||
|
/// dropdown panel hosting <see cref="TreeView{TItem}"/> across the forest that
|
||||||
|
/// <see cref="InstanceTreeBuilder"/> builds from the site's areas and instances.
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// It replaces the flat instance <c><select></c> on Flow Debugger and Debug View,
|
||||||
|
/// where a site with a few hundred instances made the list unusable. The panel opens
|
||||||
|
/// with every area collapsed so the operator drills down; an outside click closes it
|
||||||
|
/// through a transparent fixed backdrop (no JavaScript, the same pattern
|
||||||
|
/// <see cref="TreeView{TItem}"/> uses for its context menu).
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// A parent that swaps <see cref="Areas"/>/<see cref="Instances"/> (a site change)
|
||||||
|
/// closes the panel and re-derives the field text from the current selection, clearing
|
||||||
|
/// it when the selected instance is not in the new list. A parent that changes
|
||||||
|
/// <see cref="SelectedInstanceId"/> (a deep link or a localStorage restore) updates the
|
||||||
|
/// field text while the panel is closed.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public partial class InstancePicker
|
||||||
|
{
|
||||||
|
/// <summary>The site's areas, flat and linked by <see cref="Area.ParentAreaId"/>; null is treated as empty.</summary>
|
||||||
|
[Parameter]
|
||||||
|
public IReadOnlyList<Area>? Areas { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The site's instances; null is treated as empty.</summary>
|
||||||
|
[Parameter]
|
||||||
|
public IReadOnlyList<Instance>? Instances { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Id of the currently selected instance; <c>0</c> means nothing is selected.</summary>
|
||||||
|
[Parameter]
|
||||||
|
public int SelectedInstanceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Raised with the instance id when the operator picks a row.</summary>
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> SelectedInstanceIdChanged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Disables the field and the chevron; the panel can no longer be opened.</summary>
|
||||||
|
[Parameter]
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps an instance to the text shown on its row and, once picked, in the field.
|
||||||
|
/// Defaults to <see cref="Instance.UniqueName"/>. Note that searching always matches
|
||||||
|
/// on <c>UniqueName</c> (the builder's contract), whatever this renders.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public Func<Instance, string>? ItemLabel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>data-test</c> root for the control: <c>{prefix}-picker</c>, <c>-input</c>,
|
||||||
|
/// <c>-toggle</c>, <c>-panel</c>, <c>-backdrop</c> and <c>-row</c> (instance rows also
|
||||||
|
/// carry <c>data-instance-id</c>).
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public string DataTestPrefix { get; set; } = "instance";
|
||||||
|
|
||||||
|
/// <summary>Placeholder shown in the field while nothing is selected.</summary>
|
||||||
|
[Parameter]
|
||||||
|
public string Placeholder { get; set; } = "Search or browse instances…";
|
||||||
|
|
||||||
|
private readonly string _idSuffix = Guid.NewGuid().ToString("N")[..8];
|
||||||
|
|
||||||
|
private bool _open;
|
||||||
|
private string _text = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name filter applied to the forest. Empty until the search box lands; kept
|
||||||
|
/// separate from <see cref="_text"/> so a selected label never doubles as a filter.
|
||||||
|
/// </summary>
|
||||||
|
private string _filter = string.Empty;
|
||||||
|
|
||||||
|
private bool _hasFilter;
|
||||||
|
private IReadOnlyList<InstanceTreeNode> _roots = [];
|
||||||
|
private TreeView<InstanceTreeNode>? _tree;
|
||||||
|
|
||||||
|
// Last parameter values seen, so OnParametersSet can tell a real change from a
|
||||||
|
// parent that simply re-rendered with the same lists and selection.
|
||||||
|
private IReadOnlyList<Area>? _lastAreas;
|
||||||
|
private IReadOnlyList<Instance>? _lastInstances;
|
||||||
|
private int _lastSelectedId;
|
||||||
|
private bool _parametersSeen;
|
||||||
|
|
||||||
|
private string _inputId => $"ip-{_idSuffix}-input";
|
||||||
|
|
||||||
|
private string _panelId => $"ip-{_idSuffix}-panel";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
var listsChanged = !_parametersSeen
|
||||||
|
|| !ReferenceEquals(_lastAreas, Areas)
|
||||||
|
|| !ReferenceEquals(_lastInstances, Instances);
|
||||||
|
var selectionChanged = !_parametersSeen || _lastSelectedId != SelectedInstanceId;
|
||||||
|
|
||||||
|
_parametersSeen = true;
|
||||||
|
_lastAreas = Areas;
|
||||||
|
_lastInstances = Instances;
|
||||||
|
_lastSelectedId = SelectedInstanceId;
|
||||||
|
|
||||||
|
if (!listsChanged && !selectionChanged) return;
|
||||||
|
|
||||||
|
var selected = CurrentSelection();
|
||||||
|
|
||||||
|
if (listsChanged)
|
||||||
|
{
|
||||||
|
// A site change: drop any filter, rebuild, and close if the selection went away.
|
||||||
|
_filter = string.Empty;
|
||||||
|
Rebuild();
|
||||||
|
if (selected is null) _open = false;
|
||||||
|
_text = selected is null ? string.Empty : LabelOf(selected);
|
||||||
|
}
|
||||||
|
else if (!_open)
|
||||||
|
{
|
||||||
|
_text = selected is null ? string.Empty : LabelOf(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The instance <see cref="SelectedInstanceId"/> points at, or null when unresolved.</summary>
|
||||||
|
private Instance? CurrentSelection() =>
|
||||||
|
SelectedInstanceId == 0 ? null : Instances?.FirstOrDefault(i => i.Id == SelectedInstanceId);
|
||||||
|
|
||||||
|
/// <summary>Rebuilds the forest for the current filter.</summary>
|
||||||
|
private void Rebuild()
|
||||||
|
{
|
||||||
|
_hasFilter = !string.IsNullOrWhiteSpace(_filter);
|
||||||
|
_roots = InstanceTreeBuilder.Build(Areas, Instances, _filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string LabelOf(Instance instance) => ItemLabel?.Invoke(instance) ?? instance.UniqueName;
|
||||||
|
|
||||||
|
private string RowLabel(InstanceTreeNode node) =>
|
||||||
|
node.Instance is { } instance ? LabelOf(instance) : node.Label;
|
||||||
|
|
||||||
|
private void Open()
|
||||||
|
{
|
||||||
|
if (Disabled || _open) return;
|
||||||
|
_open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Toggle()
|
||||||
|
{
|
||||||
|
if (Disabled) return;
|
||||||
|
if (_open) Close(); else _open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
_open = false;
|
||||||
|
if (_hasFilter)
|
||||||
|
{
|
||||||
|
_filter = string.Empty;
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected = CurrentSelection();
|
||||||
|
_text = selected is null ? string.Empty : LabelOf(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task OnRowClickAsync(InstanceTreeNode node)
|
||||||
|
{
|
||||||
|
if (node.Instance is { } instance) return SelectAsync(instance);
|
||||||
|
|
||||||
|
_tree?.ToggleExpanded(node.Key);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectAsync(Instance instance)
|
||||||
|
{
|
||||||
|
_text = LabelOf(instance);
|
||||||
|
_open = false;
|
||||||
|
if (_hasFilter)
|
||||||
|
{
|
||||||
|
_filter = string.Empty;
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
await SelectedInstanceIdChanged.InvokeAsync(instance.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* Instance Picker combobox — the field is a plain input-group; only the dropdown
|
||||||
|
panel and its outside-click backdrop need styling. */
|
||||||
|
|
||||||
|
/* Transparent full-viewport catcher: a click anywhere outside the panel closes it.
|
||||||
|
Sits just under the panel and above page content (Bootstrap's dropdown z-index
|
||||||
|
band), mirroring TreeView's context-menu overlay. */
|
||||||
|
.ip-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1040;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown panel anchored under the field (the wrapper is position-relative).
|
||||||
|
Scrolls once the site's tree outgrows 40% of the viewport. */
|
||||||
|
.ip-panel {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 1041;
|
||||||
|
max-height: 40vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 18rem;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Both row kinds are clickable: an area toggles, an instance selects. */
|
||||||
|
.ip-instance,
|
||||||
|
.ip-area {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -601,6 +601,18 @@ else
|
|||||||
/// <summary>Whether the node with the given key is currently expanded.</summary>
|
/// <summary>Whether the node with the given key is currently expanded.</summary>
|
||||||
public bool IsExpanded(object key) => _expandedKeys.Contains(KeyStr(key));
|
public bool IsExpanded(object key) => _expandedKeys.Contains(KeyStr(key));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expand the node with the given key if it is collapsed, collapse it otherwise —
|
||||||
|
/// the same toggle the row chevron performs, for a consumer whose own
|
||||||
|
/// <c>NodeContent</c> handles the click (a label that toggles its branch).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Key of the node to toggle, as produced by <c>KeySelector</c>.</param>
|
||||||
|
public void ToggleExpanded(object key)
|
||||||
|
{
|
||||||
|
ToggleExpand(key);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Expand every branch node in the tree.</summary>
|
/// <summary>Expand every branch node in the tree.</summary>
|
||||||
public void ExpandAll()
|
public void ExpandAll()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
using Bunit;
|
||||||
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
||||||
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// bUnit tests for the <see cref="InstancePicker"/> combobox: the closed field, the
|
||||||
|
/// panel that opens with every area collapsed, area expansion, instance selection
|
||||||
|
/// (callback + close + field text), the disabled guard, the outside-click backdrop,
|
||||||
|
/// parent-driven selection changes and the <c>ItemLabel</c> hook.
|
||||||
|
/// </summary>
|
||||||
|
public class InstancePickerTests : BunitContext
|
||||||
|
{
|
||||||
|
public InstancePickerTests()
|
||||||
|
{
|
||||||
|
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Area> Areas() =>
|
||||||
|
[
|
||||||
|
new("Line1") { Id = 1, SiteId = 1 },
|
||||||
|
new("Cell A") { Id = 2, SiteId = 1, ParentAreaId = 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
private static List<Instance> Instances() =>
|
||||||
|
[
|
||||||
|
new("P1") { Id = 7, SiteId = 1, AreaId = 2 },
|
||||||
|
new("Zulu") { Id = 8, SiteId = 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
private IRenderedComponent<InstancePicker> RenderPicker(
|
||||||
|
Action<int>? onChanged = null,
|
||||||
|
bool disabled = false,
|
||||||
|
Func<Instance, string>? itemLabel = null,
|
||||||
|
int selectedInstanceId = 0) =>
|
||||||
|
Render<InstancePicker>(ps =>
|
||||||
|
{
|
||||||
|
ps.Add(p => p.Areas, Areas());
|
||||||
|
ps.Add(p => p.Instances, Instances());
|
||||||
|
ps.Add(p => p.DataTestPrefix, "t");
|
||||||
|
ps.Add(p => p.Disabled, disabled);
|
||||||
|
ps.Add(p => p.SelectedInstanceId, selectedInstanceId);
|
||||||
|
if (itemLabel is not null) ps.Add(p => p.ItemLabel, itemLabel);
|
||||||
|
if (onChanged is not null) ps.Add(p => p.SelectedInstanceIdChanged, onChanged);
|
||||||
|
});
|
||||||
|
|
||||||
|
private static void Open(IRenderedComponent<InstancePicker> cut) =>
|
||||||
|
cut.Find("[data-test='t-toggle']").Click();
|
||||||
|
|
||||||
|
private static void ClickRow(IRenderedComponent<InstancePicker> cut, string text) =>
|
||||||
|
cut.FindAll("[data-test='t-row']").First(r => r.TextContent.Trim() == text).Click();
|
||||||
|
|
||||||
|
private static string InputValue(IRenderedComponent<InstancePicker> cut) =>
|
||||||
|
cut.Find("[data-test='t-input']").GetAttribute("value") ?? string.Empty;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Closed_ShowsNothingButTheField()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker();
|
||||||
|
|
||||||
|
Assert.Empty(cut.FindAll("[data-test='t-panel']"));
|
||||||
|
|
||||||
|
var input = cut.Find("[data-test='t-input']");
|
||||||
|
Assert.Equal("combobox", input.GetAttribute("role"));
|
||||||
|
Assert.Equal("false", input.GetAttribute("aria-expanded"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Toggle_OpensPanel_WithAreasCollapsed()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker();
|
||||||
|
|
||||||
|
Open(cut);
|
||||||
|
|
||||||
|
Assert.Single(cut.FindAll("[data-test='t-panel']"));
|
||||||
|
Assert.Equal("true", cut.Find("[data-test='t-input']").GetAttribute("aria-expanded"));
|
||||||
|
|
||||||
|
var rows = cut.FindAll("[data-test='t-row']").Select(r => r.TextContent.Trim()).ToArray();
|
||||||
|
Assert.Equal(new[] { "Line1", "Zulu" }, rows);
|
||||||
|
|
||||||
|
// P1 sits under a collapsed area, so its row is not rendered at all.
|
||||||
|
Assert.Empty(cut.FindAll("[data-instance-id='7']"));
|
||||||
|
|
||||||
|
// Area rows must not carry a data-instance-id at all (Blazor drops a null attribute).
|
||||||
|
var areaRow = cut.FindAll("[data-test='t-row']").First(r => r.TextContent.Trim() == "Line1");
|
||||||
|
Assert.False(areaRow.HasAttribute("data-instance-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClickingAnAreaLabel_ExpandsIt()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker();
|
||||||
|
Open(cut);
|
||||||
|
|
||||||
|
ClickRow(cut, "Line1");
|
||||||
|
Assert.Contains(cut.FindAll("[data-test='t-row']"), r => r.TextContent.Trim() == "Cell A");
|
||||||
|
Assert.Empty(cut.FindAll("[data-instance-id='7']"));
|
||||||
|
|
||||||
|
ClickRow(cut, "Cell A");
|
||||||
|
Assert.Single(cut.FindAll("[data-instance-id='7']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClickingAnInstance_Selects_Closes_AndRaisesTheCallback()
|
||||||
|
{
|
||||||
|
int? picked = null;
|
||||||
|
var cut = RenderPicker(onChanged: id => picked = id);
|
||||||
|
|
||||||
|
Open(cut);
|
||||||
|
ClickRow(cut, "Line1");
|
||||||
|
ClickRow(cut, "Cell A");
|
||||||
|
cut.Find("[data-instance-id='7']").Click();
|
||||||
|
|
||||||
|
Assert.Equal(7, picked);
|
||||||
|
Assert.Empty(cut.FindAll("[data-test='t-panel']"));
|
||||||
|
Assert.Equal("P1", InputValue(cut));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Disabled_NeverOpens()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker(disabled: true);
|
||||||
|
|
||||||
|
Assert.True(cut.Find("[data-test='t-input']").HasAttribute("disabled"));
|
||||||
|
Assert.True(cut.Find("[data-test='t-toggle']").HasAttribute("disabled"));
|
||||||
|
|
||||||
|
Open(cut);
|
||||||
|
|
||||||
|
Assert.Empty(cut.FindAll("[data-test='t-panel']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Backdrop_ClickClosesThePanel_WithoutChangingSelection()
|
||||||
|
{
|
||||||
|
int? picked = null;
|
||||||
|
var cut = RenderPicker(onChanged: id => picked = id);
|
||||||
|
|
||||||
|
Open(cut);
|
||||||
|
cut.Find("[data-test='t-backdrop']").Click();
|
||||||
|
|
||||||
|
Assert.Empty(cut.FindAll("[data-test='t-panel']"));
|
||||||
|
Assert.Null(picked);
|
||||||
|
Assert.Equal(string.Empty, InputValue(cut));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ParentDrivenSelection_UpdatesTheFieldText()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker();
|
||||||
|
|
||||||
|
cut.Render(ps => ps.Add(p => p.SelectedInstanceId, 8));
|
||||||
|
Assert.Equal("Zulu", InputValue(cut));
|
||||||
|
|
||||||
|
cut.Render(ps => ps.Add(p => p.SelectedInstanceId, 0));
|
||||||
|
Assert.Equal(string.Empty, InputValue(cut));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SiteChange_NewInstancesList_ClearsAStaleSelectionText()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker(selectedInstanceId: 8);
|
||||||
|
Assert.Equal("Zulu", InputValue(cut));
|
||||||
|
|
||||||
|
Open(cut);
|
||||||
|
|
||||||
|
// A different site: neither the areas nor the previously selected instance survive.
|
||||||
|
cut.Render(ps =>
|
||||||
|
{
|
||||||
|
ps.Add(p => p.Areas, new List<Area>());
|
||||||
|
ps.Add(p => p.Instances, new List<Instance> { new("Other") { Id = 99, SiteId = 2 } });
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(string.Empty, InputValue(cut));
|
||||||
|
Assert.Empty(cut.FindAll("[data-test='t-panel']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ItemLabel_IsUsedForRowsAndField()
|
||||||
|
{
|
||||||
|
var cut = RenderPicker(itemLabel: i => i.UniqueName + " (x)");
|
||||||
|
|
||||||
|
Open(cut);
|
||||||
|
|
||||||
|
var rows = cut.FindAll("[data-test='t-row']").Select(r => r.TextContent.Trim()).ToArray();
|
||||||
|
Assert.Equal(new[] { "Line1", "Zulu (x)" }, rows);
|
||||||
|
|
||||||
|
ClickRow(cut, "Line1");
|
||||||
|
ClickRow(cut, "Cell A");
|
||||||
|
cut.Find("[data-instance-id='7']").Click();
|
||||||
|
|
||||||
|
Assert.Equal("P1 (x)", InputValue(cut));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user