diff --git a/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/LoginCard.razor b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/LoginCard.razor new file mode 100644 index 0000000..4cfc8fd --- /dev/null +++ b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/LoginCard.razor @@ -0,0 +1,39 @@ +@namespace ZB.MOM.WW.Theme +@* Components/LoginCard.razor — static form-POST sign-in card. *@ +
+
+ +
+
+ +@code { + [Parameter, EditorRequired] public string Product { get; set; } = string.Empty; + [Parameter] public string Action { get; set; } = "/auth/login"; + [Parameter] public string? ReturnUrl { get; set; } + [Parameter] public string? Error { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } +} diff --git a/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/LoginCardTests.cs b/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/LoginCardTests.cs new file mode 100644 index 0000000..a98a74c --- /dev/null +++ b/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/LoginCardTests.cs @@ -0,0 +1,44 @@ +namespace ZB.MOM.WW.Theme.Tests; + +public class LoginCardTests : TestContext +{ + [Fact] + public void Posts_to_action_with_username_password_fields() + { + var cut = RenderComponent(p => p + .Add(x => x.Product, "OtOpcUa") + .Add(x => x.Action, "/auth/login")); + var form = cut.Find("form"); + Assert.Equal("post", form.GetAttribute("method")); + Assert.Equal("/auth/login", form.GetAttribute("action")); + Assert.NotNull(cut.Find("input#username")); + Assert.NotNull(cut.Find("input#password")); + Assert.Contains("OtOpcUa", cut.Find(".login-title").TextContent); + } + + [Fact] + public void ReturnUrl_renders_hidden_input() + { + var cut = RenderComponent(p => p + .Add(x => x.Product, "OtOpcUa") + .Add(x => x.ReturnUrl, "/clusters")); + var hidden = cut.Find("input[name=returnUrl]"); + Assert.Equal("/clusters", hidden.GetAttribute("value")); + } + + [Fact] + public void Error_renders_notice() + { + var cut = RenderComponent(p => p + .Add(x => x.Product, "OtOpcUa") + .Add(x => x.Error, "Bad credentials")); + Assert.Contains("Bad credentials", cut.Find(".notice").TextContent); + } + + [Fact] + public void No_returnUrl_no_hidden_input() + { + var cut = RenderComponent(p => p.Add(x => x.Product, "OtOpcUa")); + Assert.Empty(cut.FindAll("input[name=returnUrl]")); + } +}