a9e8f5abfe
I-1 - a non-C# paste produced thousands of Roslyn markers. The Problems-list report sent them all as one JS->.NET message, which could exceed the 1 MB hub limit and kill the circuit. The JS now sends at most MaxReportedMarkers (200) markers, errors first, each message cut to 300 chars, plus the total. ScriptProblemReport.FromEditor clamps again on arrival, and the Problems list shows "showing N of Total" / "+N more". The diagnostics endpoint also bounds its response: Distinct, errors first, MaxDiagnosticMarkers (1000), messages cut to 1000 chars. An error is always kept, so editor-accepts == publish-accepts. M-1 - drop the per-instance compilation lineage; analyses derive from BaseCompilation. M-2 - the VirtualTag modal clears its problems (and editor ref) on close and on a script reload. M-5 - an over-limit text is announced at blur (OnSyncRefused), not only at Save. Refs #745 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
105 lines
4.6 KiB
C#
105 lines
4.6 KiB
C#
using System.Text.RegularExpressions;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis;
|
|
|
|
/// <summary>
|
|
/// Source pins for the Monaco interop (AdminUI has no bUnit and no JS test runner). These do not prove the
|
|
/// browser behaviour — the docker-dev live check does — they stop the specific regressions R04:P1/P9/S2/S3 fixed
|
|
/// from quietly coming back: per-keystroke text sync, unsequenced diagnostics, the inlay-hints POST, and an
|
|
/// unconsumed marker round-trip.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class MonacoInteropSourceTests
|
|
{
|
|
private static string Read(params string[] relative)
|
|
{
|
|
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
|
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx")))
|
|
dir = dir.Parent;
|
|
dir.ShouldNotBeNull("could not locate the repository root");
|
|
return File.ReadAllText(Path.Combine(new[] { dir!.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.AdminUI" }.Concat(relative).ToArray()));
|
|
}
|
|
|
|
private static string Js() => Read("wwwroot", "js", "monaco-init.js");
|
|
|
|
[Fact]
|
|
public void The_content_change_handler_does_not_ship_the_text_to_dotnet()
|
|
{
|
|
var js = Js();
|
|
var handler = Regex.Match(js, @"onDidChangeModelContent\(function \(\) \{(?<body>.*?)\n \}\);", RegexOptions.Singleline);
|
|
handler.Success.ShouldBeTrue("the content-change handler moved; update this pin");
|
|
handler.Groups["body"].Value.ShouldNotContain("OnValueChanged");
|
|
handler.Groups["body"].Value.ShouldNotContain("getValue");
|
|
// Positive control: the text IS still reported — at blur.
|
|
js.ShouldContain("onDidBlurEditorText");
|
|
js.ShouldContain("\"OnValueChanged\"");
|
|
}
|
|
|
|
[Fact]
|
|
public void Diagnostics_are_generation_guarded_and_abortable()
|
|
{
|
|
var js = Js();
|
|
js.ShouldContain("diagGeneration");
|
|
js.ShouldContain("generation !== e.diagGeneration");
|
|
js.ShouldContain("new AbortController()");
|
|
js.ShouldContain("signal: signal");
|
|
}
|
|
|
|
[Fact]
|
|
public void Providers_honour_the_monaco_cancellation_token()
|
|
{
|
|
var js = Js();
|
|
js.ShouldContain("token.onCancellationRequested");
|
|
foreach (var path in new[] { "\"completions\"", "\"hover\"", "\"format\"", "\"signature-help\"" })
|
|
js.ShouldContain("postAnalysis(" + path);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_inlay_hints_provider_is_not_registered() => Js().ShouldNotContain("registerInlayHintsProvider");
|
|
|
|
[Fact]
|
|
public void Markers_cross_to_dotnet_only_for_a_subscribed_problems_list()
|
|
{
|
|
Js().ShouldContain("if (e.reportMarkers) {");
|
|
Read("Components", "Shared", "MonacoEditor.razor").ShouldContain("reportMarkers = MarkersChanged.HasDelegate");
|
|
}
|
|
|
|
[Fact]
|
|
public void The_markers_report_is_capped_before_it_crosses_the_circuit()
|
|
{
|
|
// Review I-1: the only OnMarkersChanged invoke sends the capped report, never the raw marker array.
|
|
var js = Js();
|
|
Regex.Matches(js, "\"OnMarkersChanged\"").Count.ShouldBe(1);
|
|
js.ShouldContain("dotNetRef.invokeMethodAsync(\"OnMarkersChanged\", report.markers, report.total)");
|
|
js.ShouldContain("const report = markerReport(e, markers);");
|
|
js.ShouldContain(".slice(0, max)");
|
|
js.ShouldContain("m.message.substring(0, maxChars - 1)");
|
|
// The caps come from the C# constants, so the JS and ScriptProblemReport.FromEditor agree.
|
|
var razor = Read("Components", "Shared", "MonacoEditor.razor");
|
|
razor.ShouldContain("maxReportedMarkers = ScriptEditorLimits.MaxReportedMarkers");
|
|
razor.ShouldContain("maxReportedMessageChars = ScriptEditorLimits.MaxReportedMessageChars");
|
|
razor.ShouldContain("ScriptProblemReport.FromEditor(markers, total)");
|
|
}
|
|
|
|
[Fact]
|
|
public void An_oversize_text_is_announced_at_blur_not_only_at_save()
|
|
{
|
|
Js().ShouldContain("invokeMethodAsync(\"OnSyncRefused\", serializedBytes(v))");
|
|
Read("Components", "Shared", "MonacoEditor.razor").ShouldContain("public void OnSyncRefused(int bytes)");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Components", "Pages", "ScriptEdit.razor")]
|
|
[InlineData("Components", "Shared", "Uns", "VirtualTagModal.razor")]
|
|
public void Script_hosts_read_the_editor_on_save_and_render_the_problems_list(params string[] path)
|
|
{
|
|
var src = Read(path);
|
|
src.ShouldContain("GetValueAsync()");
|
|
src.ShouldContain("<ScriptProblemsList");
|
|
src.ShouldContain("RevealLineAsync(");
|
|
src.ShouldContain("MarkersChanged=");
|
|
}
|
|
}
|