fix(raw): a superseded manual-entry open can no longer feed the pickers another device's config
This commit is contained in:
+8
@@ -160,6 +160,9 @@
|
|||||||
private bool _summaryOk;
|
private bool _summaryOk;
|
||||||
private bool _open; // guards re-seeding rows on unrelated re-renders
|
private bool _open; // guards re-seeding rows on unrelated re-renders
|
||||||
|
|
||||||
|
// Identifies WHICH open a post-await write belongs to; _open alone cannot tell two opens apart.
|
||||||
|
private int _openGeneration;
|
||||||
|
|
||||||
// Merged Driver+Device config for each row's address picker — the same blob Test-connect and
|
// Merged Driver+Device config for each row's address picker — the same blob Test-connect and
|
||||||
// RawBrowseModal dial, resolved once per open; null (→ "{}") when the resolve failed: the picker's
|
// RawBrowseModal dial, resolved once per open; null (→ "{}") when the resolve failed: the picker's
|
||||||
// browse degrades to its graceful error chip.
|
// browse degrades to its graceful error chip.
|
||||||
@@ -175,6 +178,7 @@
|
|||||||
}
|
}
|
||||||
if (_open) { return; } // same open, re-render → preserve in-progress rows
|
if (_open) { return; } // same open, re-render → preserve in-progress rows
|
||||||
_open = true;
|
_open = true;
|
||||||
|
var generation = ++_openGeneration;
|
||||||
_rows.Clear();
|
_rows.Clear();
|
||||||
_rows.Add(new RowModel());
|
_rows.Add(new RowModel());
|
||||||
_summary = null;
|
_summary = null;
|
||||||
@@ -186,6 +190,10 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var merged = await Svc.LoadMergedProbeConfigAsync(DeviceId);
|
var merged = await Svc.LoadMergedProbeConfigAsync(DeviceId);
|
||||||
|
// A close-reopen onto another device can complete while this resolve is in flight, so a
|
||||||
|
// superseded open must never install its device's config under the displayed grid.
|
||||||
|
if (generation != _openGeneration || !Visible) { return; }
|
||||||
|
|
||||||
if (merged is { } m) { _mergedConfigJson = m.MergedConfigJson; }
|
if (merged is { } m) { _mergedConfigJson = m.MergedConfigJson; }
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
+36
@@ -94,6 +94,42 @@ public sealed class RawManualTagEntryModalMergedConfigTests
|
|||||||
accessor().ShouldBe(merged);
|
accessor().ShouldBe(merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task A_stale_open_never_feeds_the_picker_another_devices_config()
|
||||||
|
{
|
||||||
|
const string deviceA = """{"endpoint":"10.0.0.1:44818","slot":0}""";
|
||||||
|
const string deviceB = """{"endpoint":"10.0.0.2:44818","slot":1}""";
|
||||||
|
|
||||||
|
var deviceAResolve = new TaskCompletionSource();
|
||||||
|
var svc = new FakeRawTreeService
|
||||||
|
{
|
||||||
|
OnLoadMergedProbeConfigAsync = async deviceId =>
|
||||||
|
{
|
||||||
|
if (deviceId == "dev-A") { await deviceAResolve.Task; return ("AbCip", deviceA); }
|
||||||
|
return ("AbCip", deviceB);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var modal = new RawManualTagEntryModal();
|
||||||
|
InjectService(modal, svc);
|
||||||
|
|
||||||
|
// Quick close-reopen onto a different device while the first resolve is still in flight — the
|
||||||
|
// grid shows dev-B, so dev-A's late continuation must not install its config underneath it.
|
||||||
|
SetParameters(modal, visible: true, deviceId: "dev-A", driverType: "AbCip");
|
||||||
|
var staleOpen = OpenAsync(modal);
|
||||||
|
|
||||||
|
SetParameters(modal, visible: false, deviceId: "dev-A", driverType: "AbCip");
|
||||||
|
await OpenAsync(modal);
|
||||||
|
|
||||||
|
SetParameters(modal, visible: true, deviceId: "dev-B", driverType: "AbCip");
|
||||||
|
await OpenAsync(modal);
|
||||||
|
|
||||||
|
deviceAResolve.SetResult();
|
||||||
|
await staleOpen;
|
||||||
|
|
||||||
|
GetDriverConfigJson(modal).ShouldBe(deviceB);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the modal's <c>[Parameter]</c>s. By reflection because BL0005 forbids assigning component
|
/// Sets the modal's <c>[Parameter]</c>s. By reflection because BL0005 forbids assigning component
|
||||||
/// parameters from outside the component, and this project treats warnings as errors.
|
/// parameters from outside the component, and this project treats warnings as errors.
|
||||||
|
|||||||
Reference in New Issue
Block a user