diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 05e5d09..670628b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -124,6 +124,17 @@ jobs:
grep -q '"/counter"' Components/UI/AppSidebar.razor || (echo "AppSidebar links were not built from the pages"; exit 1)
dotnet build -c Debug
+ cd "$TMPDIR/app"
+ dotnet new blazor -o IdentityApp --auth Individual --no-restore
+ cd IdentityApp
+ shellui init --tailwind standalone --yes --dashboard 02
+ grep -q '
mine
", rootNamespace)); + } + + [Fact] + public void Restyle_KeepsRazorExpressionsInsideTags() + { + var restyled = AccountPages.Restyle(Fixture("net10", "Pages/Login.razor")); + + Assert.Contains(" { [\"ReturnUrl\"] = ReturnUrl }))\">", restyled); + Assert.Contains("@bind-Value=\"Input.Email\"", restyled); + Assert.Contains("OnSubmit=\"LoginUser\"", restyled); + } + + [Fact] + public void Restyle_StatusMessageUsesThemeColors() + { + var restyled = AccountPages.Restyle(Fixture("net10", "Shared/StatusMessage.razor")); + + Assert.DoesNotContain("alert-@", restyled); + Assert.Contains("\"text-destructive\"", restyled); + Assert.Contains("@statusMessageClass\"", restyled); + } + + [Theory] + [InlineData("btn btn-primary", "bg-primary", "h-10 px-4 py-2")] + [InlineData("w-100 btn btn-lg btn-primary", "w-full", "h-11 px-8")] + [InlineData("btn btn-danger", "bg-destructive", "h-10 px-4 py-2")] + public void MapClasses_BuildsShellUIButtons(string bootstrap, string variant, string size) + { + var mapped = AccountPages.MapClasses(bootstrap); + + Assert.Contains(variant, mapped); + Assert.Contains(size, mapped); + Assert.DoesNotContain("btn", mapped.Split(' ')); + Assert.DoesNotContain("w-100", mapped.Split(' ')); + } + + [Fact] + public void Apply_RestylesStockAccountPagesAndReportsEditedOnes() + { + var root = Path.Combine(Path.GetTempPath(), "shellui-account-" + Guid.NewGuid().ToString("N")); + try + { + var login = Fixture("net10", "Pages/Login.razor").Replace(DashboardSetup.StockProjectName, "MyApp"); + Write(root, "Components/Account/Pages/Login.razor", login); + Write(root, "Components/Account/Shared/ManageLayout.razor", Fixture("net10", "Shared/ManageLayout.razor").Replace(DashboardSetup.StockProjectName, "MyApp") + "\nmine
"); + Write(root, "Components/Pages/Counter.razor", ""); + + var result = AccountPages.Apply(root, "MyApp"); + + Assert.Equal(new[] { "Components/Account/Pages/Login.razor" }, result.Restyled); + Assert.False(AccountPages.UsesBootstrap(File.ReadAllText(Path.Combine(root, "Components/Account/Pages/Login.razor")))); + Assert.Contains(result.Notes, n => n.Contains("Components/Account/Shared/ManageLayout.razor") && n.Contains("modified")); + Assert.Single(result.Notes); + Assert.Contains("btn btn-primary", File.ReadAllText(Path.Combine(root, "Components/Pages/Counter.razor"))); + } + finally + { + Directory.Delete(root, true); + } + } + + private static void Write(string root, string relative, string content) + { + var path = Path.Combine(root, relative); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, content); + } + + private static string Fixture(string framework, string file, [CallerFilePath] string thisFile = "") => + File.ReadAllText(Path.Combine(Path.GetDirectoryName(thisFile)!, "Fixtures", "StockBlazor", framework, "Account", file + ".txt")); +} diff --git a/ShellUI.Tests/AuthSetupTests.cs b/ShellUI.Tests/AuthSetupTests.cs new file mode 100644 index 0000000..fc2cc4d --- /dev/null +++ b/ShellUI.Tests/AuthSetupTests.cs @@ -0,0 +1,61 @@ +using ShellUI.CLI.Services; +using ShellUI.Templates; +using Xunit; + +namespace ShellUI.Tests; + +public class AuthSetupTests +{ + private const string Layout = "MyApp.Components.Layout.AuthLayout01"; + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + public void SetLayout_AddsTheLayoutWhereNoneIsSet(string newline) + { + var imports = $"@using MyApp.Components.Account.Shared{newline}@attribute [ExcludeFromInteractiveRouting]{newline}"; + + var updated = AuthSetup.SetLayout(imports, Layout); + + Assert.Equal($"@using MyApp.Components.Account.Shared{newline}@attribute [ExcludeFromInteractiveRouting]{newline}@layout {Layout}{newline}", updated); + Assert.Equal(updated, AuthSetup.SetLayout(updated!, Layout)); + } + + [Theory] + [InlineData("@layout AccountLayout")] + [InlineData("@layout MyApp.Components.Layout.AuthLayout02")] + public void SetLayout_ReplacesTheNet8AccountLayoutOrAnotherAuthBlock(string directive) + { + var updated = AuthSetup.SetLayout($"@using MyApp.Components.Account.Shared\r\n{directive}\r\n", Layout); + + Assert.Equal($"@using MyApp.Components.Account.Shared\r\n@layout {Layout}\r\n", updated); + } + + [Fact] + public void SetLayout_LeavesACustomLayoutAlone() + { + Assert.Null(AuthSetup.SetLayout("@layout MyApp.Components.Layout.SignInLayout\n", Layout)); + } + + [Theory] + [InlineData("Components/Layout", "MyApp.Components.Layout.AuthLayout01")] + [InlineData("Layouts", "MyApp.Layouts.AuthLayout01")] + public void LayoutTypeName_FollowsTheLayoutPath(string layoutPath, string expected) + { + Assert.Equal(expected, AuthSetup.LayoutTypeName("MyApp", layoutPath, "AuthLayout01")); + } + + [Fact] + public void EveryAuthBlock_IsAnInstallableLayout() + { + foreach (var (name, layout) in AuthSetup.Layouts) + { + var metadata = ComponentRegistry.GetMetadata(name); + Assert.NotNull(metadata); + Assert.True(metadata!.IsAvailable); + Assert.True(metadata.IsLayoutBlock); + Assert.Equal($"{layout}.razor", metadata.FilePath); + Assert.Contains("@Body", ComponentRegistry.GetComponentContent(name)); + } + } +} diff --git a/ShellUI.Tests/DashboardSetupTests.cs b/ShellUI.Tests/DashboardSetupTests.cs index 0b6f486..93d5d99 100644 --- a/ShellUI.Tests/DashboardSetupTests.cs +++ b/ShellUI.Tests/DashboardSetupTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using ShellUI.CLI.Services; using ShellUI.Core.Models; using ShellUI.Templates; @@ -196,6 +197,39 @@ public void AppSidebarTemplate_LinksAreRewrittenFromPages() Assert.Null(DashboardSetup.RewriteSidebarLinks(rewritten!, links)); } + [Fact] + public void AppSidebarTemplate_FooterBecomesTheAccountMenu() + { + var sidebar = ComponentRegistry.GetComponentContent("app-sidebar")!; + var templateFooter = Regex.Match(sidebar, "mine
", File.ReadAllText(Path.Combine(root, "Components/Pages/Weather.razor"))); } finally diff --git a/ShellUI.Tests/TemplateCompileTests.cs b/ShellUI.Tests/TemplateCompileTests.cs index a6c0459..0c09a16 100644 --- a/ShellUI.Tests/TemplateCompileTests.cs +++ b/ShellUI.Tests/TemplateCompileTests.cs @@ -127,13 +127,13 @@ public void EveryHiddenEntry_IsReachableFromAnInstallableTarget() foreach (var dep in ComponentRegistry.Components[name].Dependencies ?? new ListMy App
+Sign in or create an account to continue.
+