namespace GmRelay.Bot.Tests.Web; public sealed class CampaignTemplatesNavigationTests { [Fact] public async Task NavMenu_ShouldExposeTemplatesTab() { var navMenu = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Layout/NavMenu.razor")); Assert.Contains("href=\"templates\"", navMenu, StringComparison.Ordinal); Assert.Contains("Шаблоны", navMenu, StringComparison.Ordinal); } [Fact] public async Task NavMenu_ShouldExposeCurrentProjectVersion() { // Read the version from Directory.Build.props (the canonical source of // truth) so the test doesn't need to be hand-edited on every version // bump. Asserting the rendered NavMenu matches the canonical version // catches real bugs (e.g. someone bumps Directory.Build.props but // forgets to update NavMenu.razor) without false alarms from a stale // hard-coded literal. var propsPath = FindRepositoryFile("Directory.Build.props"); var version = ReadVersionFromProps(propsPath); var navMenu = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Layout/NavMenu.razor")); Assert.Contains($"v{version}", navMenu, StringComparison.Ordinal); } [Fact] public async Task NavMenuStyles_ShouldStyleNavLinkAnchorsAsStackedRows() { var navCss = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Layout/NavMenu.razor.css")); Assert.Contains("::deep .nav-item", navCss, StringComparison.Ordinal); Assert.Matches( @"\.nav-section\s*\{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;[^}]*gap:\s*0\.25rem;", navCss); Assert.Matches( @"::deep\s+\.nav-item\s*\{[^}]*display:\s*flex;[^}]*width:\s*100%;", navCss); } [Fact] public async Task GroupDetails_ShouldApplyTemplatesWithoutManagingThem() { var groupDetails = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Pages/GroupDetails.razor")); Assert.Contains("CreateBatchFromTemplate", groupDetails, StringComparison.Ordinal); Assert.DoesNotContain("OnValidSubmit=\"CreateCampaignTemplate\"", groupDetails, StringComparison.Ordinal); Assert.DoesNotContain("DeleteCampaignTemplate", groupDetails, StringComparison.Ordinal); } [Fact] public async Task CampaignTemplatesPage_ShouldOwnTemplateManagement() { var templatesPage = await File.ReadAllTextAsync(FindRepositoryFile("src/GmRelay.Web/Components/Pages/CampaignTemplates.razor")); Assert.Contains("@page \"/templates\"", templatesPage, StringComparison.Ordinal); Assert.Contains("OnValidSubmit=\"CreateCampaignTemplate\"", templatesPage, StringComparison.Ordinal); Assert.Contains("DeleteCampaignTemplate", templatesPage, StringComparison.Ordinal); } private static string FindRepositoryFile(string relativePath) { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null) { var candidate = Path.Combine(directory.FullName, relativePath); if (File.Exists(candidate)) { return candidate; } directory = directory.Parent; } throw new FileNotFoundException($"Could not locate repository file '{relativePath}'."); } /// /// Parse the <Version>...</Version> element from /// Directory.Build.props. Tolerant of whitespace, comments and /// attribute shuffling — the MSBuild schema for Version is just /// a plain element with a string body. /// private static string ReadVersionFromProps(string propsPath) { var doc = System.Xml.Linq.XDocument.Load(propsPath); var versionElement = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "Version"); Assert.NotNull(versionElement); var version = versionElement!.Value.Trim(); Assert.False( string.IsNullOrEmpty(version), $" in {propsPath} is empty"); return version; } }