< Summary

Information
Class: Chronicis.Client.Components.Shared.PublicArticleTreeItem
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Shared/PublicArticleTreeItem.razor
Line coverage
0%
Covered lines: 0
Uncovered lines: 60
Coverable lines: 60
Total lines: 213
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 70
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
BuildRenderTree(...)0%272160%
get_Article()100%210%
get_PublicSlug()100%210%
get_CurrentPath()100%210%
get_ParentPath()100%210%
get_Level()100%210%
get_OnArticleSelected()100%210%
get_IsSelected()0%2040%
OnParametersSet()0%210140%
HasMatchingDescendant()0%4260%
CheckDescendantsRecursively(...)0%506220%
GetArticlePath()0%620%
BuildPathForArticle(...)0%620%
GetPathForChildren()0%620%
ToggleExpand()100%210%
OnItemClick()0%620%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Shared/PublicArticleTreeItem.razor

#LineLine coverage
 1@using Chronicis.Shared.DTOs
 2@using Chronicis.Client.Components.Shared
 3
 4<div class="public-tree-item @(_isExpanded ? "expanded" : "") @(IsSelected ? "selected" : "")"
 5     style="padding-left: @(Level * 12)px;">
 6
 7    <div class="public-tree-item-content" @onclick="OnItemClick">
 08        @if (Article.HasChildren)
 9        {
 10            <span class="public-tree-expand-btn" @onclick="ToggleExpand" @onclick:stopPropagation="true">
 11                <MudIcon Icon="@(_isExpanded ? Icons.Material.Filled.ExpandMore : Icons.Material.Filled.ChevronRight)"
 12                         Size="Size.Small" />
 13            </span>
 14        }
 15        else
 16        {
 17            <span class="public-tree-spacer"></span>
 18        }
 19
 20        <span class="public-tree-icon">
 21            <IconDisplay Icon="@Article.IconEmoji" DefaultIcon="" CssClass="tree-icon" />
 22        </span>
 23
 024        <span class="public-tree-title" title="@Article.Title">@Article.Title</span>
 25    </div>
 26
 027    @if (_isExpanded && Article.Children?.Any() == true)
 28    {
 29        <div class="public-tree-children">
 030            @foreach (var child in Article.Children.OrderBy(c => c.Title))
 31            {
 32                @* Virtual groups don't contribute to path - pass through the parent's real path *@
 33                <PublicArticleTreeItem Article="@child"
 34                                       PublicSlug="@PublicSlug"
 35                                       CurrentPath="@CurrentPath"
 36                                       ParentPath="@GetPathForChildren()"
 37                                       Level="@(Level + 1)"
 38                                       OnArticleSelected="OnArticleSelected" />
 39            }
 40        </div>
 41    }
 42</div>
 43
 44@code {
 45    [Parameter]
 046    public ArticleTreeDto Article { get; set; } = null!;
 47
 48    [Parameter]
 049    public string PublicSlug { get; set; } = string.Empty;
 50
 51    [Parameter]
 052    public string? CurrentPath { get; set; }
 53
 54    [Parameter]
 055    public string? ParentPath { get; set; }
 56
 57    [Parameter]
 058    public int Level { get; set; } = 0;
 59
 60    [Parameter]
 061    public EventCallback<string> OnArticleSelected { get; set; }
 62
 63    private bool _isExpanded = false;
 64
 65    private bool IsSelected
 66    {
 67        get
 68        {
 69            // Virtual groups can't be selected
 070            if (Article.IsVirtualGroup) return false;
 71
 072            var itemPath = GetArticlePath();
 073            return !string.IsNullOrEmpty(CurrentPath) &&
 074                   CurrentPath.Equals(itemPath, StringComparison.OrdinalIgnoreCase);
 75        }
 76    }
 77
 78    protected override void OnParametersSet()
 79    {
 80        // Auto-expand if this node or any descendant matches the current path
 081        if (!string.IsNullOrEmpty(CurrentPath))
 82        {
 83            // Check if THIS node is selected (for non-virtual groups)
 084            if (!Article.IsVirtualGroup)
 85            {
 086                var myPath = GetArticlePath();
 087                if (CurrentPath.StartsWith(myPath, StringComparison.OrdinalIgnoreCase))
 88                {
 89                    // Current path starts with our path - we're on the path to the selected article
 90                    // Expand if we have children and the path continues past us
 091                    if (Article.HasChildren &&
 092                        (CurrentPath.Length > myPath.Length || HasMatchingDescendant()))
 93                    {
 094                        _isExpanded = true;
 95                    }
 96                }
 97            }
 98            else
 99            {
 100                // Virtual groups: check if any descendant matches
 0101                if (HasMatchingDescendant())
 102                {
 0103                    _isExpanded = true;
 104                }
 105            }
 106        }
 0107    }
 108
 109    private bool HasMatchingDescendant()
 110    {
 0111        if (Article.Children == null || string.IsNullOrEmpty(CurrentPath))
 0112            return false;
 113
 114        // Get the path prefix for children of this node
 0115        var childPathPrefix = GetPathForChildren() ?? "";
 116
 0117        return CheckDescendantsRecursively(Article.Children, childPathPrefix);
 118    }
 119
 120    private bool CheckDescendantsRecursively(IEnumerable<ArticleTreeDto> children, string pathPrefix)
 121    {
 0122        foreach (var child in children)
 123        {
 0124            if (!child.IsVirtualGroup)
 125            {
 126                // Build the child's full path
 0127                var childPath = string.IsNullOrEmpty(pathPrefix)
 0128                    ? child.Slug
 0129                    : $"{pathPrefix}/{child.Slug}";
 130
 0131                if (CurrentPath!.Equals(childPath, StringComparison.OrdinalIgnoreCase) ||
 0132                    CurrentPath.StartsWith(childPath + "/", StringComparison.OrdinalIgnoreCase))
 133                {
 0134                    return true;
 135                }
 136
 137                // Check this child's descendants
 0138                if (child.Children?.Any() == true)
 139                {
 0140                    if (CheckDescendantsRecursively(child.Children, childPath))
 0141                        return true;
 142                }
 143            }
 144            else
 145            {
 146                // Virtual groups pass through the path prefix
 0147                if (child.Children?.Any() == true)
 148                {
 0149                    if (CheckDescendantsRecursively(child.Children, pathPrefix))
 0150                        return true;
 151                }
 152            }
 153        }
 154
 0155        return false;
 0156    }
 157
 158    /// <summary>
 159    /// Get the path for this article (only for real articles, not virtual groups)
 160    /// </summary>
 161    private string GetArticlePath()
 162    {
 0163        if (Article.IsVirtualGroup) return string.Empty;
 0164        return BuildPathForArticle(Article);
 165    }
 166
 167    /// <summary>
 168    /// Build path for a specific article, using ParentPath as base
 169    /// </summary>
 170    private string BuildPathForArticle(ArticleTreeDto article)
 171    {
 0172        if (string.IsNullOrEmpty(ParentPath))
 173        {
 0174            return article.Slug;
 175        }
 0176        return $"{ParentPath}/{article.Slug}";
 177    }
 178
 179    /// <summary>
 180    /// Get the path to pass to children.
 181    /// Virtual groups don't contribute to the path - pass through ParentPath.
 182    /// Real articles add their slug to the path.
 183    /// </summary>
 184    private string? GetPathForChildren()
 185    {
 0186        if (Article.IsVirtualGroup)
 187        {
 188            // Virtual groups don't contribute to path - pass through parent's path
 0189            return ParentPath;
 190        }
 191
 192        // Real articles add their slug
 0193        return GetArticlePath();
 194    }
 195
 196    private void ToggleExpand()
 197    {
 0198        _isExpanded = !_isExpanded;
 0199    }
 200
 201    private async Task OnItemClick()
 202    {
 203        // Virtual groups only expand/collapse, don't navigate
 0204        if (Article.IsVirtualGroup)
 205        {
 0206            _isExpanded = !_isExpanded;
 0207            return;
 208        }
 209
 0210        var path = GetArticlePath();
 0211        await OnArticleSelected.InvokeAsync(path);
 0212    }
 213}