< Summary

Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 248
Line coverage: 100%
Branch coverage
100%
Covered branches: 14
Total branches: 14
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: BuildRenderTree(...)100%1212100%
File 2: .ctor()100%11100%
File 2: CanDeleteUpdate(...)100%22100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Quests/ArcQuestTimeline.razor

#LineLine coverage
 1@using Chronicis.Shared.DTOs.Quests
 2@using Chronicis.Shared.Enums
 3
 4<div class="arc-quest-timeline">
 315    @if (_quest != null)
 6    {
 7        <div class="d-flex align-center justify-space-between mb-3">
 8            <MudText Typo="Typo.h6" Style="color: var(--chronicis-beige-gold);">
 9                <MudIcon Icon="@Icons.Material.Filled.Timeline" Size="Size.Small" Class="mr-2" />
 10                Quest Updates
 11            </MudText>
 12
 2313            @if (_quest.UpdateCount > 0)
 14            {
 15                <MudText Typo="Typo.caption" Class="text-muted">
 16                    @_quest.UpdateCount update@(_quest.UpdateCount == 1 ? "" : "s")
 17                </MudText>
 18            }
 19        </div>
 20
 2321        @if (_isLoading && !_updates.Any())
 22        {
 23            <MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Small" />
 24        }
 2225        else if (_updates.Any())
 26        {
 27            <MudTimeline TimelinePosition="TimelinePosition.Start" TimelineOrientation="TimelineOrientation.Vertical">
 28                @foreach (var update in _updates)
 29                {
 30                    <MudTimelineItem Color="Color.Info" Size="Size.Small">
 31                        <ItemOpposite>
 32                            <MudText Typo="Typo.caption" Class="text-muted">
 33                                @update.CreatedAt.ToString("MMM d, yyyy h:mm tt")
 34                            </MudText>
 35                        </ItemOpposite>
 36                        <ItemContent>
 37                            <MudCard Elevation="2" Class="quest-update-card">
 38                                <MudCardContent>
 39                                    <!-- Author and Session Info -->
 40                                    <div class="d-flex align-center justify-space-between mb-2">
 41                                        <div class="d-flex align-center gap-2">
 42                                            @if (!string.IsNullOrEmpty(update.CreatedByAvatarUrl))
 43                                            {
 44                                                <MudAvatar Size="Size.Small">
 45                                                    <MudImage Src="@update.CreatedByAvatarUrl" />
 46                                                </MudAvatar>
 47                                            }
 48                                            else
 49                                            {
 50                                                <MudAvatar Size="Size.Small" Color="Color.Primary">
 51                                                    @update.CreatedByName.FirstOrDefault()
 52                                                </MudAvatar>
 53                                            }
 54                                            <MudText Typo="Typo.body2">@update.CreatedByName</MudText>
 55
 56                                            @if (update.SessionId.HasValue && !string.IsNullOrEmpty(update.SessionTitle)
 57                                            {
 58                                                <MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Colo
 59                                                    Session: @update.SessionTitle
 60                                                </MudChip>
 61                                            }
 62                                        </div>
 63
 64                                        @if (CanDeleteUpdate(update))
 65                                        {
 66                                            <MudIconButton Icon="@Icons.Material.Filled.Delete"
 67                                                           Size="Size.Small"
 68                                                           Color="Color.Error"
 69                                                           OnClick="@(() => DeleteUpdate(update))" />
 70                                        }
 71                                    </div>
 72
 73                                    <!-- Update Body (HTML from TipTap) -->
 74                                    <div class="quest-update-body">
 75                                        @((MarkupString)update.Body)
 76                                    </div>
 77                                </MudCardContent>
 78                            </MudCard>
 79                        </ItemContent>
 80                    </MudTimelineItem>
 81                }
 82            </MudTimeline>
 83
 84            <!-- Load More Button -->
 1185            @if (_hasMore)
 86            {
 87                <div class="text-center mt-3">
 88                    <MudButton Variant="Variant.Outlined"
 89                               Color="Color.Primary"
 90                               OnClick="LoadMoreAsync"
 91                               Disabled="_isLoadingMore">
 92                        @if (_isLoadingMore)
 93                        {
 94                            <MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
 95                        }
 96                        Load More
 97                    </MudButton>
 98                </div>
 99            }
 100        }
 101        else
 102        {
 103            <MudAlert Severity="Severity.Info">
 104                No updates yet. Quest updates will appear here as progress is made.
 105            </MudAlert>
 106        }
 107    }
 108    else
 109    {
 110        <MudAlert Severity="Severity.Info">
 111            Select a quest to view updates
 112        </MudAlert>
 113    }
 114</div>

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Quests/ArcQuestTimeline.razor.cs

#LineLine coverage
 1using Chronicis.Client.Services;
 2using Chronicis.Shared.DTOs.Quests;
 3using Microsoft.AspNetCore.Components;
 4using MudBlazor;
 5
 6namespace Chronicis.Client.Components.Quests;
 7
 8public partial class ArcQuestTimeline : ComponentBase
 9{
 10    [Parameter]
 11    public QuestDto? Quest { get; set; }
 12
 13    [Parameter]
 14    public bool IsGm { get; set; }
 15
 16    [Parameter]
 17    public Guid CurrentUserId { get; set; }
 18
 19    [Inject] private IQuestApiService QuestApi { get; set; } = default!;
 20    [Inject] private IDialogService DialogService { get; set; } = default!;
 21    [Inject] private ISnackbar Snackbar { get; set; } = default!;
 22
 23    private QuestDto? _quest;
 2224    private List<QuestUpdateEntryDto> _updates = new();
 25    private bool _isLoading;
 26    private bool _isLoadingMore;
 27    private bool _hasMore;
 28    private int _skip = 0;
 29    private const int _pageSize = 20;
 30
 31    protected override async Task OnParametersSetAsync()
 32    {
 33        // Quest changed - reset and reload
 34        if (_quest?.Id != Quest?.Id)
 35        {
 36            _quest = Quest;
 37            _updates.Clear();
 38            _skip = 0;
 39            _hasMore = false;
 40
 41            if (_quest != null)
 42            {
 43                await LoadUpdatesAsync();
 44            }
 45        }
 46    }
 47
 48    private async Task LoadUpdatesAsync()
 49    {
 50        if (_quest == null)
 51            return;
 52
 53        _isLoading = true;
 54        StateHasChanged();
 55
 56        try
 57        {
 58            var result = await QuestApi.GetQuestUpdatesAsync(_quest.Id, _skip, _pageSize);
 59
 60            _updates.AddRange(result.Items);
 61            _hasMore = result.TotalCount > _updates.Count;
 62            _skip = _updates.Count;
 63        }
 64        catch (Exception ex)
 65        {
 66            Snackbar.Add($"Failed to load quest updates: {ex.Message}", Severity.Error);
 67        }
 68        finally
 69        {
 70            _isLoading = false;
 71            StateHasChanged();
 72        }
 73    }
 74
 75    private async Task LoadMoreAsync()
 76    {
 77        if (_quest == null || _isLoadingMore)
 78            return;
 79
 80        _isLoadingMore = true;
 81        StateHasChanged();
 82
 83        try
 84        {
 85            var result = await QuestApi.GetQuestUpdatesAsync(_quest.Id, _skip, _pageSize);
 86
 87            _updates.AddRange(result.Items);
 88            _hasMore = result.TotalCount > _updates.Count;
 89            _skip = _updates.Count;
 90        }
 91        catch (Exception ex)
 92        {
 93            Snackbar.Add($"Failed to load more updates: {ex.Message}", Severity.Error);
 94        }
 95        finally
 96        {
 97            _isLoadingMore = false;
 98            StateHasChanged();
 99        }
 100    }
 101
 102    private bool CanDeleteUpdate(QuestUpdateEntryDto update)
 103    {
 104        // GM can delete any update, Player can delete own updates only
 21105        return IsGm || update.CreatedBy == CurrentUserId;
 106    }
 107
 108    private async Task DeleteUpdate(QuestUpdateEntryDto update)
 109    {
 110        if (_quest == null)
 111            return;
 112
 113        var confirmed = await DialogService.ShowMessageBox(
 114            "Delete Update",
 115            "Are you sure you want to delete this quest update?",
 116            yesText: "Delete",
 117            cancelText: "Cancel");
 118
 119        if (confirmed == true)
 120        {
 121            var success = await QuestApi.DeleteQuestUpdateAsync(_quest.Id, update.Id);
 122            if (success)
 123            {
 124                _updates.Remove(update);
 125                Snackbar.Add("Quest update deleted", Severity.Success);
 126                StateHasChanged();
 127            }
 128            else
 129            {
 130                Snackbar.Add("Failed to delete quest update", Severity.Error);
 131            }
 132        }
 133    }
 134}