< Summary

Information
Class: Chronicis.Client.Services.TutorialPageTypeResolver
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Services/TutorialPageTypeResolver.cs
Line coverage
100%
Covered lines: 78
Uncovered lines: 0
Coverable lines: 78
Total lines: 189
Line coverage: 100%
Branch coverage
100%
Covered branches: 50
Total branches: 50
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Resolve(...)100%88100%
ResolvePageName(...)100%1414100%
TryResolveMapsPageName(...)100%88100%
TryGetPathSegments(...)100%1212100%
ToPascalCase(...)100%44100%
ToPascalWord(...)100%44100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Services/TutorialPageTypeResolver.cs

#LineLine coverage
 1using System.Collections.Frozen;
 2using Chronicis.Shared.Enums;
 3
 4namespace Chronicis.Client.Services;
 5
 6/// <summary>
 7/// Resolves the canonical tutorial PageType key from the current route context.
 8/// </summary>
 9public sealed class TutorialPageTypeResolver
 10{
 111    private static readonly FrozenDictionary<string, string> RoutePageNames =
 112        new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
 113        {
 114            ["dashboard"] = "Dashboard",
 115            ["settings"] = "Settings",
 116            ["world"] = "WorldDetail",
 117            ["campaign"] = "CampaignDetail",
 118            ["arc"] = "ArcDetail",
 119            ["session"] = "SessionDetail",
 120            ["admin/status"] = "AdminStatus",
 121            ["admin/utilities"] = "AdminUtilities",
 122            ["search"] = "Search",
 123            ["cosmos"] = "Cosmos",
 124            ["about"] = "About",
 125            ["getting-started"] = "GettingStarted",
 126            ["changelog"] = "ChangeLog",
 127            ["change-log"] = "ChangeLog",
 128            ["privacy"] = "Privacy",
 129            ["terms"] = "Terms",
 130            ["licenses"] = "Licenses"
 131        }.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
 32
 33    /// <summary>
 34    /// Resolves the canonical page type for tutorial lookup.
 35    /// </summary>
 36    /// <param name="uri">Current absolute or app-relative URI.</param>
 37    /// <param name="articleType">Current article type when on article detail route.</param>
 38    /// <param name="worldId">Optional world identifier (reserved for future use).</param>
 39    /// <param name="isTutorialWorld">Optional tutorial-world flag (reserved for future use).</param>
 40    public string Resolve(
 41        string uri,
 42        ArticleType? articleType = null,
 43        Guid? worldId = null,
 44        bool? isTutorialWorld = null)
 45    {
 46        _ = worldId;
 47        _ = isTutorialWorld;
 48
 3349        if (!TryGetPathSegments(uri, out var segments))
 50        {
 151            return "Page:Default";
 52        }
 53
 3254        if (segments.Length == 0)
 55        {
 356            return "Page:Default";
 57        }
 58
 2959        if (segments[0].Equals("article", StringComparison.OrdinalIgnoreCase))
 60        {
 261            return articleType.HasValue
 262                ? $"ArticleType:{articleType.Value}"
 263                : "ArticleType:Any";
 64        }
 65
 2766        return $"Page:{ResolvePageName(segments)}";
 67    }
 68
 69    private static string ResolvePageName(string[] segments)
 70    {
 2871        if (segments.Length == 0)
 72        {
 173            return "Default";
 74        }
 75
 2776        if (TryResolveMapsPageName(segments, out var mapsPageName))
 77        {
 478            return mapsPageName;
 79        }
 80
 2381        if (segments[0].Equals("admin", StringComparison.OrdinalIgnoreCase))
 82        {
 483            if (segments.Length >= 2 && RoutePageNames.TryGetValue($"{segments[0]}/{segments[1]}", out var adminPageName
 84            {
 285                return adminPageName;
 86            }
 87
 288            return segments.Length >= 2
 289                ? $"Admin{ToPascalCase(segments[1])}"
 290                : "Admin";
 91        }
 92
 1993        if (RoutePageNames.TryGetValue(segments[0], out var pageName))
 94        {
 1795            return pageName;
 96        }
 97
 298        return ToPascalCase(segments[0]);
 99    }
 100
 101    private static bool TryResolveMapsPageName(string[] segments, out string pageName)
 102    {
 27103        pageName = string.Empty;
 104
 27105        if (segments.Length < 3)
 106        {
 21107            return false;
 108        }
 109
 6110        if (!segments[0].Equals("world", StringComparison.OrdinalIgnoreCase))
 111        {
 1112            return false;
 113        }
 114
 5115        if (!segments[2].Equals("maps", StringComparison.OrdinalIgnoreCase))
 116        {
 1117            return false;
 118        }
 119
 4120        pageName = segments.Length == 3 ? "MapListing" : "MapDetail";
 4121        return true;
 122    }
 123
 124    private static bool TryGetPathSegments(string uri, out string[] segments)
 125    {
 35126        segments = Array.Empty<string>();
 127
 35128        if (string.IsNullOrWhiteSpace(uri))
 129        {
 1130            return false;
 131        }
 132
 34133        Uri? parsedUri = null;
 34134        if (Uri.TryCreate(uri, UriKind.Absolute, out var absoluteUri) &&
 34135            (absoluteUri.Scheme == Uri.UriSchemeHttp || absoluteUri.Scheme == Uri.UriSchemeHttps))
 136        {
 2137            parsedUri = absoluteUri;
 138        }
 32139        else if (Uri.TryCreate($"https://local/{uri.TrimStart('/')}", UriKind.Absolute, out var relativeAsAbsolute))
 140        {
 31141            parsedUri = relativeAsAbsolute;
 142        }
 143
 34144        if (parsedUri == null)
 145        {
 1146            return false;
 147        }
 148
 33149        segments = parsedUri.AbsolutePath
 33150            .Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
 151
 33152        return true;
 153    }
 154
 155    private static string ToPascalCase(string value)
 156    {
 5157        if (string.IsNullOrWhiteSpace(value))
 158        {
 1159            return "Default";
 160        }
 161
 4162        var parts = value
 4163            .Split(new[] { '-', '_', ' ' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
 164
 4165        if (parts.Length == 0)
 166        {
 1167            return "Default";
 168        }
 169
 3170        return string.Concat(parts.Select(ToPascalWord));
 171    }
 172
 173    private static string ToPascalWord(string word)
 174    {
 8175        if (string.IsNullOrEmpty(word))
 176        {
 1177            return string.Empty;
 178        }
 179
 7180        if (word.Length == 1)
 181        {
 1182            return word.ToUpperInvariant();
 183        }
 184
 6185        return char.ToUpperInvariant(word[0]) + word[1..];
 186    }
 187}
 188
 189