Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/ParameterValueFormMonacoSchemaTests.cs
T

102 lines
4.0 KiB
C#

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;
/// <summary>
/// M9-T31: the raw-JSON escape hatch in <see cref="ParameterValueForm"/> (the
/// fallback for an unresolved <c>$ref</c> / unknown-type node) is a Monaco
/// <c>json</c> editor wired with the form's RESOLVED schema, so Monaco's
/// built-in JSON language gives schema-driven hover + completion. These tests
/// assert at the C#/interop boundary that the resolved schema (with
/// <c>{"$ref":"lib:Name"}</c> inlined) reaches the editor's <c>JsonSchema</c>
/// parameter — the JS hover/completion itself is Monaco-built-in and not
/// unit-testable here.
/// </summary>
public class ParameterValueFormMonacoSchemaTests : BunitContext
{
private readonly ISchemaLibraryQueryService _library = Substitute.For<ISchemaLibraryQueryService>();
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<CancellationToken>())
.Returns(new Dictionary<string, string>());
Services.AddSingleton(_library);
}
[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<ParameterValueForm>(p => p
.Add(x => x.ParameterDefinitions, schema)
.Add(x => x.Values, new Dictionary<string, object?>()));
var editor = cut.FindComponent<MonacoEditor>();
Assert.Equal("json", editor.Instance.Language);
// No plain <textarea> fallback remains for the escape hatch.
Assert.Empty(cut.FindAll("textarea"));
}
[Fact]
public void EscapeHatch_MonacoEditor_ReceivesResolvedSchema_WithRefInlined()
{
// The library entry "Address" resolves to an object with street/city.
const string libSchema = """
{
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
}
}
""";
_library.GetSchemaMapAsync(Arg.Any<CancellationToken>())
.Returns(new Dictionary<string, string> { ["Address"] = libSchema });
// Root has a RESOLVABLE $ref field (shipTo → Address) plus an
// UNRESOLVABLE one (payload) that forces the escape-hatch Monaco editor.
const string schema = """
{
"type": "object",
"properties": {
"shipTo": { "$ref": "lib:Address" },
"payload": { "$ref": "lib:Missing" }
}
}
""";
var cut = Render<ParameterValueForm>(p => p
.Add(x => x.ParameterDefinitions, schema)
.Add(x => x.Values, new Dictionary<string, object?>()));
var editor = cut.FindComponent<MonacoEditor>();
var jsonSchema = editor.Instance.JsonSchema;
Assert.False(string.IsNullOrWhiteSpace(jsonSchema));
// The RESOLVED shape is fed to Monaco: the $ref target's fields are
// inlined (street/city present), and the lib: pointer text is gone.
Assert.Contains("street", jsonSchema!);
Assert.Contains("city", jsonSchema!);
Assert.DoesNotContain("lib:Address", jsonSchema!);
Assert.DoesNotContain("$ref", jsonSchema!);
}
}