feat(m9/T28b): trigger analysis-kind selector (UI) + --trigger-kind (CLI)
Surfaces the T28a backend "analysisKind" discriminator in both authoring surfaces: an Advisory|Strict <select> (id="alarm-trigger-kind" / "script-trigger-kind") added to the Expression fragment of AlarmTriggerEditor and ScriptTriggerEditor, and a --trigger-kind option on template alarm/script add+update in the CLI. Key/value contract: "analysisKind":"Strict" when strict; key omitted for Advisory — exactly as ValidationService.IsStrictAnalysis reads it. Selector only shown for Expression triggers; non-Expression triggers do not emit the key even if IsStrictAnalysisKind is set on the model. Both projects build 0 warnings; 101 CentralUI Trigger tests + 33 CLI Template tests pass.
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
using System.CommandLine;
|
||||
using System.Text.Json;
|
||||
using ZB.MOM.WW.ScadaBridge.CLI;
|
||||
using ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// M9-T28b: <c>template alarm add/update</c> and <c>template script add/update</c>
|
||||
/// must expose a <c>--trigger-kind</c> option (advisory|strict) that writes
|
||||
/// <c>"analysisKind":"Strict"</c> into the trigger-config JSON for expression
|
||||
/// triggers, matching the T28a backend contract exactly.
|
||||
/// </summary>
|
||||
public class TemplateTriggerKindTests
|
||||
{
|
||||
private static readonly Option<string> Url = new("--url") { Recursive = true };
|
||||
private static readonly Option<string> Username = new("--username") { Recursive = true };
|
||||
private static readonly Option<string> Password = new("--password") { Recursive = true };
|
||||
private static readonly Option<string> Format = CliOptions.CreateFormatOption();
|
||||
|
||||
private static Command AlarmGroup()
|
||||
=> TemplateCommands.Build(Url, Format, Username, Password)
|
||||
.Subcommands.Single(c => c.Name == "alarm");
|
||||
|
||||
private static Command ScriptGroup()
|
||||
=> TemplateCommands.Build(Url, Format, Username, Password)
|
||||
.Subcommands.Single(c => c.Name == "script");
|
||||
|
||||
// ── Option surface: alarm add + update have --trigger-kind ───────────────
|
||||
|
||||
[Fact]
|
||||
public void AlarmAdd_HasTriggerKindOption()
|
||||
{
|
||||
var add = AlarmGroup().Subcommands.Single(c => c.Name == "add");
|
||||
Assert.Contains("--trigger-kind", add.Options.Select(o => o.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlarmUpdate_HasTriggerKindOption()
|
||||
{
|
||||
var update = AlarmGroup().Subcommands.Single(c => c.Name == "update");
|
||||
Assert.Contains("--trigger-kind", update.Options.Select(o => o.Name));
|
||||
}
|
||||
|
||||
// ── Option surface: script add + update have --trigger-kind ─────────────
|
||||
|
||||
[Fact]
|
||||
public void ScriptAdd_HasTriggerKindOption()
|
||||
{
|
||||
var add = ScriptGroup().Subcommands.Single(c => c.Name == "add");
|
||||
Assert.Contains("--trigger-kind", add.Options.Select(o => o.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptUpdate_HasTriggerKindOption()
|
||||
{
|
||||
var update = ScriptGroup().Subcommands.Single(c => c.Name == "update");
|
||||
Assert.Contains("--trigger-kind", update.Options.Select(o => o.Name));
|
||||
}
|
||||
|
||||
// ── AlarmTriggerConfigJson.Build — analysisKind injection ────────────────
|
||||
|
||||
[Fact]
|
||||
public void AlarmConfigBuild_Expression_StrictKind_InjectsAnalysisKindStrict()
|
||||
{
|
||||
// --trigger-kind strict with --expression set → config carries analysisKind:"Strict"
|
||||
var json = AlarmTriggerConfigJson.Build(
|
||||
triggerType: "Expression",
|
||||
attribute: null,
|
||||
matchValue: null, notEquals: false,
|
||||
min: null, max: null,
|
||||
thresholdPerSecond: null, windowSeconds: null, direction: null,
|
||||
loLo: null, lo: null, hi: null, hiHi: null,
|
||||
expression: "Temp > 80",
|
||||
analysisKind: "strict");
|
||||
|
||||
Assert.NotNull(json);
|
||||
using var doc = JsonDocument.Parse(json!);
|
||||
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlarmConfigBuild_Expression_AdvisoryKind_OmitsAnalysisKindKey()
|
||||
{
|
||||
// --trigger-kind advisory (default) → key omitted
|
||||
var json = AlarmTriggerConfigJson.Build(
|
||||
triggerType: "Expression",
|
||||
attribute: null,
|
||||
matchValue: null, notEquals: false,
|
||||
min: null, max: null,
|
||||
thresholdPerSecond: null, windowSeconds: null, direction: null,
|
||||
loLo: null, lo: null, hi: null, hiHi: null,
|
||||
expression: "Temp > 80",
|
||||
analysisKind: "advisory");
|
||||
|
||||
Assert.NotNull(json);
|
||||
using var doc = JsonDocument.Parse(json!);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlarmConfigBuild_Expression_NullKind_OmitsAnalysisKindKey()
|
||||
{
|
||||
// Omitted --trigger-kind → null → Advisory default → key omitted
|
||||
var json = AlarmTriggerConfigJson.Build(
|
||||
triggerType: "Expression",
|
||||
attribute: null,
|
||||
matchValue: null, notEquals: false,
|
||||
min: null, max: null,
|
||||
thresholdPerSecond: null, windowSeconds: null, direction: null,
|
||||
loLo: null, lo: null, hi: null, hiHi: null,
|
||||
expression: "Temp > 80",
|
||||
analysisKind: null);
|
||||
|
||||
Assert.NotNull(json);
|
||||
using var doc = JsonDocument.Parse(json!);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlarmConfigBuild_NonExpression_StrictKind_DoesNotEmitAnalysisKind()
|
||||
{
|
||||
// analysisKind is meaningless for non-expression triggers — must be suppressed
|
||||
var json = AlarmTriggerConfigJson.Build(
|
||||
triggerType: "RangeViolation",
|
||||
attribute: "Temp",
|
||||
matchValue: null, notEquals: false,
|
||||
min: 0, max: 100,
|
||||
thresholdPerSecond: null, windowSeconds: null, direction: null,
|
||||
loLo: null, lo: null, hi: null, hiHi: null,
|
||||
expression: null,
|
||||
analysisKind: "strict");
|
||||
|
||||
Assert.NotNull(json);
|
||||
using var doc = JsonDocument.Parse(json!);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlarmConfigBuild_Expression_StrictKind_CaseInsensitive()
|
||||
{
|
||||
// "STRICT" (uppercase) must also produce the canonical "Strict" value
|
||||
var json = AlarmTriggerConfigJson.Build(
|
||||
triggerType: "Expression",
|
||||
attribute: null,
|
||||
matchValue: null, notEquals: false,
|
||||
min: null, max: null,
|
||||
thresholdPerSecond: null, windowSeconds: null, direction: null,
|
||||
loLo: null, lo: null, hi: null, hiHi: null,
|
||||
expression: "x > 0",
|
||||
analysisKind: "STRICT");
|
||||
|
||||
Assert.NotNull(json);
|
||||
using var doc = JsonDocument.Parse(json!);
|
||||
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
using System.Text.Json;
|
||||
using Bunit;
|
||||
using Bunit.JSInterop;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// M9-T28b: the <c>AlarmTriggerEditor</c> and <c>ScriptTriggerEditor</c>
|
||||
/// components must surface an Advisory|Strict selector for Expression triggers
|
||||
/// and round-trip the <c>"analysisKind"</c> key into the trigger config JSON
|
||||
/// that the T28a ValidationService backend reads.
|
||||
/// </summary>
|
||||
|
||||
// ── AlarmTriggerConfigCodec — analysisKind parse / serialize ────────────────
|
||||
|
||||
public class AlarmTriggerAnalysisKindCodecTests
|
||||
{
|
||||
// Codec: parse — absent key defaults to Advisory (false)
|
||||
[Fact]
|
||||
public void Parse_Expression_NoAnalysisKind_DefaultsToAdvisory()
|
||||
{
|
||||
var json = @"{""expression"":""Attributes[""Temp""] > 50""}";
|
||||
var model = AlarmTriggerConfigCodec.Parse(json, AlarmTriggerType.Expression);
|
||||
Assert.False(model.IsStrictAnalysisKind);
|
||||
}
|
||||
|
||||
// Codec: parse — "Advisory" explicit → false
|
||||
[Fact]
|
||||
public void Parse_Expression_AdvisoryKey_ReturnsFalse()
|
||||
{
|
||||
var json = @"{""expression"":""x > 0"",""analysisKind"":""Advisory""}";
|
||||
var model = AlarmTriggerConfigCodec.Parse(json, AlarmTriggerType.Expression);
|
||||
Assert.False(model.IsStrictAnalysisKind);
|
||||
}
|
||||
|
||||
// Codec: parse — "Strict" → true
|
||||
[Fact]
|
||||
public void Parse_Expression_StrictKey_ReturnsTrue()
|
||||
{
|
||||
var json = @"{""expression"":""x > 0"",""analysisKind"":""Strict""}";
|
||||
var model = AlarmTriggerConfigCodec.Parse(json, AlarmTriggerType.Expression);
|
||||
Assert.True(model.IsStrictAnalysisKind);
|
||||
}
|
||||
|
||||
// Codec: parse — case-insensitive
|
||||
[Fact]
|
||||
public void Parse_Expression_StrictKeyCaseInsensitive()
|
||||
{
|
||||
var json = @"{""expression"":""x > 0"",""analysisKind"":""strict""}";
|
||||
var model = AlarmTriggerConfigCodec.Parse(json, AlarmTriggerType.Expression);
|
||||
Assert.True(model.IsStrictAnalysisKind);
|
||||
}
|
||||
|
||||
// Codec: serialize — Advisory (false) → "analysisKind" key omitted
|
||||
[Fact]
|
||||
public void Serialize_Expression_Advisory_OmitsAnalysisKindKey()
|
||||
{
|
||||
var model = new AlarmTriggerModel { Expression = "x > 0", IsStrictAnalysisKind = false };
|
||||
var json = AlarmTriggerConfigCodec.Serialize(model, AlarmTriggerType.Expression);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
// Codec: serialize — Strict (true) → "analysisKind":"Strict"
|
||||
[Fact]
|
||||
public void Serialize_Expression_Strict_WritesAnalysisKindStrict()
|
||||
{
|
||||
var model = new AlarmTriggerModel { Expression = "x > 0", IsStrictAnalysisKind = true };
|
||||
var json = AlarmTriggerConfigCodec.Serialize(model, AlarmTriggerType.Expression);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
|
||||
}
|
||||
|
||||
// Codec: serialize — Strict not emitted for non-Expression trigger types
|
||||
[Fact]
|
||||
public void Serialize_NonExpression_DoesNotEmitAnalysisKind()
|
||||
{
|
||||
var model = new AlarmTriggerModel
|
||||
{
|
||||
AttributeName = "Temp",
|
||||
Min = 0,
|
||||
Max = 100,
|
||||
IsStrictAnalysisKind = true // should be ignored for non-Expression
|
||||
};
|
||||
var json = AlarmTriggerConfigCodec.Serialize(model, AlarmTriggerType.RangeViolation);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
// Codec: round-trip Strict
|
||||
[Fact]
|
||||
public void RoundTrip_Expression_Strict_Preserved()
|
||||
{
|
||||
var original = new AlarmTriggerModel { Expression = "Temp > 80", IsStrictAnalysisKind = true };
|
||||
var json = AlarmTriggerConfigCodec.Serialize(original, AlarmTriggerType.Expression);
|
||||
var round = AlarmTriggerConfigCodec.Parse(json, AlarmTriggerType.Expression);
|
||||
|
||||
Assert.True(round.IsStrictAnalysisKind);
|
||||
Assert.Equal("Temp > 80", round.Expression);
|
||||
}
|
||||
|
||||
// Codec: round-trip Advisory (omit + re-parse → still false)
|
||||
[Fact]
|
||||
public void RoundTrip_Expression_Advisory_Preserved()
|
||||
{
|
||||
var original = new AlarmTriggerModel { Expression = "Temp > 80", IsStrictAnalysisKind = false };
|
||||
var json = AlarmTriggerConfigCodec.Serialize(original, AlarmTriggerType.Expression);
|
||||
var round = AlarmTriggerConfigCodec.Parse(json, AlarmTriggerType.Expression);
|
||||
|
||||
Assert.False(round.IsStrictAnalysisKind);
|
||||
}
|
||||
}
|
||||
|
||||
// ── ScriptTriggerConfigCodec — analysisKind parse / serialize ───────────────
|
||||
|
||||
public class ScriptTriggerAnalysisKindCodecTests
|
||||
{
|
||||
// Codec: parse — absent key defaults to Advisory (false)
|
||||
[Fact]
|
||||
public void Parse_Expression_NoAnalysisKind_DefaultsToAdvisory()
|
||||
{
|
||||
var json = @"{""expression"":""x > 0"",""mode"":""OnTrue""}";
|
||||
var model = ScriptTriggerConfigCodec.Parse(json, ScriptTriggerKind.Expression);
|
||||
Assert.False(model.IsStrictAnalysisKind);
|
||||
}
|
||||
|
||||
// Codec: parse — "Strict" → true
|
||||
[Fact]
|
||||
public void Parse_Expression_StrictKey_ReturnsTrue()
|
||||
{
|
||||
var json = @"{""expression"":""x > 0"",""mode"":""OnTrue"",""analysisKind"":""Strict""}";
|
||||
var model = ScriptTriggerConfigCodec.Parse(json, ScriptTriggerKind.Expression);
|
||||
Assert.True(model.IsStrictAnalysisKind);
|
||||
}
|
||||
|
||||
// Codec: serialize — Advisory → key omitted
|
||||
[Fact]
|
||||
public void Serialize_Expression_Advisory_OmitsAnalysisKindKey()
|
||||
{
|
||||
var model = new ScriptTriggerModel { Expression = "x > 0", IsStrictAnalysisKind = false };
|
||||
var json = ScriptTriggerConfigCodec.Serialize(model, ScriptTriggerKind.Expression)!;
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
// Codec: serialize — Strict → "analysisKind":"Strict"
|
||||
[Fact]
|
||||
public void Serialize_Expression_Strict_WritesAnalysisKindStrict()
|
||||
{
|
||||
var model = new ScriptTriggerModel { Expression = "x > 0", IsStrictAnalysisKind = true };
|
||||
var json = ScriptTriggerConfigCodec.Serialize(model, ScriptTriggerKind.Expression)!;
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
|
||||
}
|
||||
|
||||
// Codec: serialize — Strict not emitted for non-Expression triggers
|
||||
[Fact]
|
||||
public void Serialize_NonExpression_DoesNotEmitAnalysisKind()
|
||||
{
|
||||
var model = new ScriptTriggerModel
|
||||
{
|
||||
AttributeName = "Temp",
|
||||
Operator = ">",
|
||||
Threshold = 50,
|
||||
IsStrictAnalysisKind = true // must be ignored for Conditional
|
||||
};
|
||||
var json = ScriptTriggerConfigCodec.Serialize(model, ScriptTriggerKind.Conditional)!;
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
// Codec: round-trip Strict
|
||||
[Fact]
|
||||
public void RoundTrip_Expression_Strict_Preserved()
|
||||
{
|
||||
var original = new ScriptTriggerModel
|
||||
{
|
||||
Expression = "Temp > 80",
|
||||
Mode = ScriptTriggerMode.WhileTrue,
|
||||
IsStrictAnalysisKind = true
|
||||
};
|
||||
var json = ScriptTriggerConfigCodec.Serialize(original, ScriptTriggerKind.Expression)!;
|
||||
var round = ScriptTriggerConfigCodec.Parse(json, ScriptTriggerKind.Expression);
|
||||
|
||||
Assert.True(round.IsStrictAnalysisKind);
|
||||
Assert.Equal(ScriptTriggerMode.WhileTrue, round.Mode);
|
||||
}
|
||||
}
|
||||
|
||||
// ── AlarmTriggerEditor — UI selector (bUnit component tests) ─────────────────
|
||||
|
||||
public class AlarmTriggerEditorAnalysisKindTests : BunitContext
|
||||
{
|
||||
public AlarmTriggerEditorAnalysisKindTests()
|
||||
{
|
||||
// MonacoEditor calls MonacoBlazor.createEditor via JS interop. Use Loose
|
||||
// mode so the editor renders without requiring a full setup for each call.
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
}
|
||||
|
||||
// Selector is visible for Expression trigger type
|
||||
[Fact]
|
||||
public void AlarmTriggerEditor_Expression_ShowsAnalysisKindSelector()
|
||||
{
|
||||
var cut = Render<AlarmTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, AlarmTriggerType.Expression)
|
||||
.Add(p => p.Value, @"{""expression"":""""}"));
|
||||
|
||||
// The selector must exist and have id="alarm-trigger-kind"
|
||||
var select = cut.Find("#alarm-trigger-kind");
|
||||
Assert.NotNull(select);
|
||||
}
|
||||
|
||||
// Selector is NOT visible for non-Expression trigger types
|
||||
[Fact]
|
||||
public void AlarmTriggerEditor_NonExpression_DoesNotShowAnalysisKindSelector()
|
||||
{
|
||||
var cut = Render<AlarmTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, AlarmTriggerType.RangeViolation)
|
||||
.Add(p => p.Value, @"{""attributeName"":""Temp"",""min"":0,""max"":100}"));
|
||||
|
||||
Assert.Throws<Bunit.ElementNotFoundException>(() => cut.Find("#alarm-trigger-kind"));
|
||||
}
|
||||
|
||||
// Selector defaults to Advisory
|
||||
[Fact]
|
||||
public void AlarmTriggerEditor_Expression_NoAnalysisKindInConfig_SelectorDefaultsAdvisory()
|
||||
{
|
||||
var cut = Render<AlarmTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, AlarmTriggerType.Expression)
|
||||
.Add(p => p.Value, @"{""expression"":""x > 0""}"));
|
||||
|
||||
var select = cut.Find("#alarm-trigger-kind");
|
||||
// The selected option must be Advisory (the default)
|
||||
Assert.Contains("Advisory", select.InnerHtml);
|
||||
}
|
||||
|
||||
// Choosing Strict writes analysisKind:"Strict" into emitted config
|
||||
[Fact]
|
||||
public void AlarmTriggerEditor_Expression_ChoosingStrict_EmitsAnalysisKindStrict()
|
||||
{
|
||||
string? emitted = null;
|
||||
var cut = Render<AlarmTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, AlarmTriggerType.Expression)
|
||||
.Add(p => p.Value, @"{""expression"":""x > 0""}")
|
||||
.Add(p => p.ValueChanged,
|
||||
EventCallback.Factory.Create<string?>(this, v => emitted = v)));
|
||||
|
||||
cut.Find("#alarm-trigger-kind").Change("Strict");
|
||||
|
||||
Assert.NotNull(emitted);
|
||||
using var doc = JsonDocument.Parse(emitted!);
|
||||
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
|
||||
}
|
||||
|
||||
// Loaded Strict config reflects in selector, emits Strict on next change
|
||||
[Fact]
|
||||
public void AlarmTriggerEditor_Expression_LoadedStrictConfig_RetainedOnEdit()
|
||||
{
|
||||
string? emitted = null;
|
||||
var cut = Render<AlarmTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, AlarmTriggerType.Expression)
|
||||
.Add(p => p.Value, @"{""expression"":""x > 0"",""analysisKind"":""Strict""}")
|
||||
.Add(p => p.ValueChanged,
|
||||
EventCallback.Factory.Create<string?>(this, v => emitted = v)));
|
||||
|
||||
// Switch to Advisory — config must no longer carry analysisKind
|
||||
cut.Find("#alarm-trigger-kind").Change("Advisory");
|
||||
|
||||
Assert.NotNull(emitted);
|
||||
using var doc = JsonDocument.Parse(emitted!);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
}
|
||||
|
||||
// ── ScriptTriggerEditor — UI selector (bUnit component tests) ────────────────
|
||||
|
||||
public class ScriptTriggerEditorAnalysisKindTests : BunitContext
|
||||
{
|
||||
public ScriptTriggerEditorAnalysisKindTests()
|
||||
{
|
||||
// MonacoEditor calls MonacoBlazor.createEditor via JS interop. Use Loose
|
||||
// mode so the editor renders without requiring a full setup for each call.
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
}
|
||||
|
||||
// Selector is visible for Expression trigger type
|
||||
[Fact]
|
||||
public void ScriptTriggerEditor_Expression_ShowsAnalysisKindSelector()
|
||||
{
|
||||
var cut = Render<ScriptTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, "Expression")
|
||||
.Add(p => p.TriggerConfig, @"{""expression"":"""",""mode"":""OnTrue""}"));
|
||||
|
||||
var select = cut.Find("#script-trigger-kind");
|
||||
Assert.NotNull(select);
|
||||
}
|
||||
|
||||
// Selector is NOT visible for non-Expression trigger types
|
||||
[Fact]
|
||||
public void ScriptTriggerEditor_Conditional_DoesNotShowAnalysisKindSelector()
|
||||
{
|
||||
var cut = Render<ScriptTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, "Conditional")
|
||||
.Add(p => p.TriggerConfig, @"{""attributeName"":""Temp"",""operator"":"">"",""threshold"":50,""mode"":""OnTrue""}"));
|
||||
|
||||
Assert.Throws<Bunit.ElementNotFoundException>(() => cut.Find("#script-trigger-kind"));
|
||||
}
|
||||
|
||||
// Choosing Strict writes analysisKind:"Strict" into emitted config
|
||||
[Fact]
|
||||
public void ScriptTriggerEditor_Expression_ChoosingStrict_EmitsAnalysisKindStrict()
|
||||
{
|
||||
ScriptTriggerValue? captured = null;
|
||||
var cut = Render<ScriptTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, "Expression")
|
||||
.Add(p => p.TriggerConfig, @"{""expression"":""x > 0"",""mode"":""OnTrue""}")
|
||||
.Add(p => p.Changed,
|
||||
EventCallback.Factory.Create<ScriptTriggerValue>(this, v => captured = v)));
|
||||
|
||||
cut.Find("#script-trigger-kind").Change("Strict");
|
||||
|
||||
Assert.NotNull(captured);
|
||||
using var doc = JsonDocument.Parse(captured!.Config!);
|
||||
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
|
||||
}
|
||||
|
||||
// Choosing Advisory emits config without analysisKind key
|
||||
[Fact]
|
||||
public void ScriptTriggerEditor_Expression_ChoosingAdvisory_OmitsAnalysisKindKey()
|
||||
{
|
||||
ScriptTriggerValue? captured = null;
|
||||
var cut = Render<ScriptTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, "Expression")
|
||||
.Add(p => p.TriggerConfig, @"{""expression"":""x > 0"",""mode"":""OnTrue"",""analysisKind"":""Strict""}")
|
||||
.Add(p => p.Changed,
|
||||
EventCallback.Factory.Create<ScriptTriggerValue>(this, v => captured = v)));
|
||||
|
||||
cut.Find("#script-trigger-kind").Change("Advisory");
|
||||
|
||||
Assert.NotNull(captured);
|
||||
using var doc = JsonDocument.Parse(captured!.Config!);
|
||||
Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
|
||||
}
|
||||
|
||||
// Loaded Strict is retained on unrelated edit (fire mode change)
|
||||
[Fact]
|
||||
public void ScriptTriggerEditor_Expression_LoadedStrict_RetainedOnModeChange()
|
||||
{
|
||||
ScriptTriggerValue? captured = null;
|
||||
var cut = Render<ScriptTriggerEditor>(ps => ps
|
||||
.Add(p => p.TriggerType, "Expression")
|
||||
.Add(p => p.TriggerConfig, @"{""expression"":""x > 0"",""mode"":""OnTrue"",""analysisKind"":""Strict""}")
|
||||
.Add(p => p.Changed,
|
||||
EventCallback.Factory.Create<ScriptTriggerValue>(this, v => captured = v)));
|
||||
|
||||
// Change fire mode — Strict kind must survive
|
||||
cut.Find("#script-trigger-mode").Change("WhileTrue");
|
||||
|
||||
Assert.NotNull(captured);
|
||||
Assert.Contains("\"analysisKind\":\"Strict\"", captured!.Config);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user