fix(adminui): stale-open guards, validation summaries, key-delete confirm, error boundary (#604)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw;
|
||||
|
||||
/// <summary>
|
||||
/// Issue #604 source-scan guards (Razor has no bUnit): an <c>EditForm</c> with a validator but nothing
|
||||
/// to show its messages is a silent no-op Save, the layout must keep its error boundary, and the
|
||||
/// API-key Delete must not fire straight from the row button.
|
||||
/// </summary>
|
||||
public sealed class AdminUiFormSweepGuardTests
|
||||
{
|
||||
/// <summary>EditForm pages whose validation messages are rendered by a child/section component.</summary>
|
||||
private static readonly HashSet<string> AllowList = new(StringComparer.Ordinal);
|
||||
|
||||
[Fact]
|
||||
public void Every_razor_with_an_EditForm_and_a_DataAnnotationsValidator_renders_validation_messages()
|
||||
{
|
||||
var offenders = new List<string>();
|
||||
foreach (var file in Directory.EnumerateFiles(ComponentsDir(), "*.razor", SearchOption.AllDirectories))
|
||||
{
|
||||
var text = File.ReadAllText(file);
|
||||
if (!text.Contains("<EditForm") || !text.Contains("<DataAnnotationsValidator")) { continue; }
|
||||
if (text.Contains("<ValidationSummary") || text.Contains("<ValidationMessage")) { continue; }
|
||||
var name = Path.GetRelativePath(ComponentsDir(), file).Replace('\\', '/');
|
||||
if (!AllowList.Contains(name)) { offenders.Add(name); }
|
||||
}
|
||||
|
||||
offenders.ShouldBeEmpty("these forms validate but never show a message: " + string.Join(", ", offenders));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MainLayout_wraps_the_body_in_an_ErrorBoundary()
|
||||
{
|
||||
var text = File.ReadAllText(Path.Combine(ComponentsDir(), "Layout", "MainLayout.razor"));
|
||||
var boundary = text.IndexOf("<ErrorBoundary", StringComparison.Ordinal);
|
||||
boundary.ShouldBeGreaterThan(-1);
|
||||
text.IndexOf("@Body", StringComparison.Ordinal).ShouldBeGreaterThan(boundary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApiKeys_delete_button_goes_through_the_confirm_dialog()
|
||||
{
|
||||
var text = File.ReadAllText(Path.Combine(ComponentsDir(), "Pages", "ApiKeys.razor"));
|
||||
text.ShouldContain("<RawConfirmDialog");
|
||||
text.ShouldNotContain("DeleteAsync(k.ApiKeyId)");
|
||||
}
|
||||
|
||||
private static string ComponentsDir()
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir is not null)
|
||||
{
|
||||
var candidate = Path.Combine(dir.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.AdminUI", "Components");
|
||||
if (Directory.Exists(candidate)) { return candidate; }
|
||||
dir = dir.Parent;
|
||||
}
|
||||
throw new DirectoryNotFoundException("AdminUI Components directory not found above the test binaries.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user