fix(central-ui): Flow Debugger — remount ParameterValueForm per script so stale form state cannot block Run
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -235,7 +235,12 @@
|
||||
else
|
||||
{
|
||||
<div data-test="flow-script-params">
|
||||
<ParameterValueForm ParameterDefinitions="@script.ParameterDefinitions"
|
||||
@* Keyed on the script so a change of script REMOUNTS the form: its raw
|
||||
text and parse errors are per-instance state that must not survive
|
||||
(and two scripts may even declare byte-identical definitions, which
|
||||
would otherwise skip the reparse and its ValidChanged entirely). *@
|
||||
<ParameterValueForm @key="@script.CanonicalName"
|
||||
ParameterDefinitions="@script.ParameterDefinitions"
|
||||
Values="_scriptParams"
|
||||
ValuesChanged="@(v => _scriptParams = v)"
|
||||
ValidChanged="@(ok => _scriptParamsValid = ok)" />
|
||||
|
||||
@@ -330,6 +330,11 @@ public partial class FlowDebugger : ComponentBase, IDisposable
|
||||
ResetResult();
|
||||
_predicted = null;
|
||||
_state = FlowState.Idle;
|
||||
// Inputs are per-entry-point, as in OnScriptChangedAsync: never carry a script's value or
|
||||
// parameter tree (or its validity) across a switch of entry kind.
|
||||
_scriptParams = new();
|
||||
_scriptParamsValid = false;
|
||||
_valueChangeValue = string.Empty;
|
||||
await PersistAsync(StorageEntry, entry.ToString());
|
||||
if (_entry == EntryKind.Method && _selectedMethodId > 0)
|
||||
await OnMethodChangedAsync();
|
||||
|
||||
@@ -691,4 +691,64 @@ public class FlowDebuggerPageTests : BunitContext
|
||||
cut.WaitForAssertion(() => Assert.Equal("Manual", cut.Find("select[data-test='flow-script-select']").GetAttribute("value")));
|
||||
_ = _flow.Received().PredictScriptAsync(7, "Manual", Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two call-kind scripts declaring BYTE-IDENTICAL parameter definitions — the case that makes
|
||||
/// <c>ParameterValueForm</c> skip its reparse (and its <c>ValidChanged</c>) and keeps the previous
|
||||
/// script's raw text and parse errors. The page must remount the form per script instead.
|
||||
/// </summary>
|
||||
private static readonly FlowScriptOption Manual2 = new("Manual2", "Call", null,
|
||||
"""{"type":"object","properties":{"n":{"type":"integer"}},"required":["n"]}""", 20);
|
||||
|
||||
private void WireInstanceWithTwinScripts()
|
||||
{
|
||||
_templates.GetInstancesBySiteIdAsync(1, Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<Instance>>([new Instance("Sim01") { Id = 7, SiteId = 1, TemplateId = 10 }]));
|
||||
_flow.GetInstanceScriptsAsync(7, Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<FlowScriptOption>>([Manual, Manual2, Tick]));
|
||||
_flow.PredictScriptAsync(7, Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(ThreeNodePrediction());
|
||||
}
|
||||
|
||||
private IRenderedComponent<FlowDebuggerPage> ChooseScriptWithAnInvalidParameter()
|
||||
{
|
||||
var cut = Render<FlowDebuggerPage>();
|
||||
cut.Find("button[data-test='flow-entry-script']").Click();
|
||||
cut.Find("select[data-test='flow-site-select']").Change("1");
|
||||
cut.Find("select[data-test='flow-instance-select']").Change("7");
|
||||
cut.Find("select[data-test='flow-script-select']").Change("Manual");
|
||||
cut.Find("#param-n").Input("abc"); // not an integer: the form parks a parse error
|
||||
Assert.True(cut.Find("button[data-test='flow-run']").HasAttribute("disabled"));
|
||||
return cut;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptEntry_SwitchingBetweenCallKindScripts_ResetsTheFormAndValidity()
|
||||
{
|
||||
RegisterServices();
|
||||
WireInstanceWithTwinScripts();
|
||||
var cut = ChooseScriptWithAnInvalidParameter();
|
||||
|
||||
cut.Find("select[data-test='flow-script-select']").Change("Manual2");
|
||||
|
||||
// A fresh form: no echo of the previous script's text, and no stale parse error keeping
|
||||
// Run disabled invisibly — Run is disabled only because the required field is unset.
|
||||
Assert.Equal(string.Empty, cut.Find("#param-n").GetAttribute("value"));
|
||||
Assert.True(cut.Find("button[data-test='flow-run']").HasAttribute("disabled"));
|
||||
|
||||
cut.Find("#param-n").Input("5");
|
||||
Assert.False(cut.Find("button[data-test='flow-run']").HasAttribute("disabled"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptEntry_SwitchingFromAnInvalidFormToANoParameterScript_EnablesRun()
|
||||
{
|
||||
RegisterServices();
|
||||
WireInstanceWithTwinScripts();
|
||||
var cut = ChooseScriptWithAnInvalidParameter();
|
||||
|
||||
cut.Find("select[data-test='flow-script-select']").Change("Tick");
|
||||
|
||||
Assert.Contains("takes no parameters", cut.Find("[data-test='flow-inputs']").TextContent);
|
||||
Assert.False(cut.Find("button[data-test='flow-run']").HasAttribute("disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user