using Bunit; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; using ZB.MOM.WW.ScadaBridge.CentralUI.Services; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Design; /// /// The raw-JSON escape hatch in /// (the fallback for an unresolved $ref / /// unknown-type node) is a Monaco json editor wired with THAT FIELD's own /// sub-schema — not the whole root object schema — so each field's editor /// validates/completes against just its own portion of the type. These tests /// assert at the C#/interop boundary that the correct per-field fragment reaches /// each editor's JsonSchema parameter (and that a sibling field's resolved /// shape never leaks in), using FindComponents when 2+ escape-hatch fields /// are present. The JS hover/completion itself is Monaco-built-in and not /// unit-testable here. /// public class ParameterValueFormMonacoSchemaTests : BunitContext { private readonly ISchemaLibraryQueryService _library = Substitute.For(); /// Initializes a new instance of the class. public ParameterValueFormMonacoSchemaTests() { // The escape-hatch surface renders a MonacoEditor whose JS interop is not // exercised here — Loose mode lets the unconfigured createEditor call no-op // so the render completes and we can assert on the editor's parameters. JSInterop.Mode = JSRuntimeMode.Loose; _library.GetSchemaMapAsync(Arg.Any()) .Returns(new Dictionary()); Services.AddSingleton(_library); } /// Raw JSON errors describe the named composite and disappear after correction. [Fact] public async Task EscapeHatch_ParseErrorIsLinkedToNamedGroupUntilCorrection() { const string schema = """{"type":"object","properties":{"payload":{"$ref":"lib:Missing"}}}"""; var cut = Render(p => p.Add(c => c.ParameterDefinitions, schema)); var editor = cut.FindComponent(); Assert.Equal("Parameter payload JSON", editor.Instance.AriaLabel); await cut.InvokeAsync(() => editor.Instance.ValueChanged.InvokeAsync("{bad")); var group = cut.Find(".param-json-editor"); Assert.Equal("group", group.GetAttribute("role")); Assert.Equal("Parameter payload JSON", group.GetAttribute("aria-label")); Assert.Equal("true", group.GetAttribute("aria-invalid")); Assert.Contains("JSON parse error", cut.Find("#" + group.GetAttribute("aria-describedby")).TextContent); await cut.InvokeAsync(() => editor.Instance.ValueChanged.InvokeAsync("{}")); group = cut.Find(".param-json-editor"); // Valid syntax does not erase an unresolved schema's existing validation error. Assert.Equal("true", group.GetAttribute("aria-invalid")); Assert.Contains("unknown declared type", cut.Find("#" + group.GetAttribute("aria-describedby")).TextContent); // Existing schema contract permits a present null for any declared type. await cut.InvokeAsync(() => editor.Instance.ValueChanged.InvokeAsync("null")); group = cut.Find(".param-json-editor"); Assert.Equal("false", group.GetAttribute("aria-invalid")); Assert.Null(group.GetAttribute("aria-describedby")); Assert.Equal("Parameter payload JSON", cut.FindComponent().Instance.AriaLabel); } /// Verifies the case: escape Hatch, renders Monaco Json Editor, not Plain Textarea. [Fact] public void EscapeHatch_RendersMonacoJsonEditor_NotPlainTextarea() { // A nested field whose $ref cannot be resolved drops to the raw-JSON // escape hatch. That surface must now be a Monaco json editor. const string schema = """ { "type": "object", "properties": { "payload": { "$ref": "lib:Missing" } } } """; var cut = Render(p => p .Add(x => x.ParameterDefinitions, schema) .Add(x => x.Values, new Dictionary())); var editor = cut.FindComponent(); Assert.Equal("json", editor.Instance.Language); // No plain