Add Dashboard page (F1)
- DashboardDto, IDashboardService, DashboardService - Summary cards: members, guests, days, open expenses - Recent days list with navigation - Top penalty recipients list - Home redirects to /dashboard - Updated HomePageTests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
005bfebe6d
commit
7c9f3c36d9
|
|
@ -0,0 +1,68 @@
|
|||
namespace Koogle.Application.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Data transfer object for dashboard summary data.
|
||||
/// </summary>
|
||||
public record DashboardDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Total number of active members in the club.
|
||||
/// </summary>
|
||||
public int MemberCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of guests in the club.
|
||||
/// </summary>
|
||||
public int GuestCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of game days this year.
|
||||
/// </summary>
|
||||
public int DaysThisYear { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total open (unpaid) expense amount across all persons.
|
||||
/// </summary>
|
||||
public decimal TotalOpenAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of open expenses.
|
||||
/// </summary>
|
||||
public int OpenExpenseCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Recent game days (last 5).
|
||||
/// </summary>
|
||||
public List<DaySummaryDto> RecentDays { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Top penalty recipients (top 5 by open amount).
|
||||
/// </summary>
|
||||
public List<TopPenaltyRecipientDto> TopPenaltyRecipients { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data transfer object for top penalty recipient.
|
||||
/// </summary>
|
||||
public record TopPenaltyRecipientDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Person ID.
|
||||
/// </summary>
|
||||
public Guid PersonId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Person name.
|
||||
/// </summary>
|
||||
public string PersonName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Total open amount.
|
||||
/// </summary>
|
||||
public decimal OpenAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of open expenses.
|
||||
/// </summary>
|
||||
public int ExpenseCount { get; init; }
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ namespace Koogle.Application
|
|||
services.AddScoped<IExpenseService, ExpenseService>();
|
||||
services.AddScoped<IDayService, DayService>();
|
||||
services.AddScoped<IPersonExpenseService, PersonExpenseService>();
|
||||
services.AddScoped<IDashboardService, DashboardService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
using Koogle.Application.DTOs;
|
||||
|
||||
namespace Koogle.Application.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Service interface for dashboard data aggregation.
|
||||
/// </summary>
|
||||
public interface IDashboardService
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves dashboard summary data for the current club.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Dashboard summary data.</returns>
|
||||
Task<DashboardDto> GetDashboardAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
using Koogle.Application.DTOs;
|
||||
using Koogle.Application.Interfaces;
|
||||
using Koogle.Domain.Enums;
|
||||
using Koogle.Domain.Interfaces;
|
||||
using KoogleApp.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Koogle.Application.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for aggregating dashboard data for the current club.
|
||||
/// </summary>
|
||||
public class DashboardService : IDashboardService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _contextFactory;
|
||||
private readonly ICurrentClubContext _clubContext;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DashboardService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="contextFactory">The database context factory.</param>
|
||||
/// <param name="clubContext">The current club context.</param>
|
||||
public DashboardService(
|
||||
IDbContextFactory<AppDbContext> contextFactory,
|
||||
ICurrentClubContext clubContext)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_clubContext = clubContext;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DashboardDto> GetDashboardAsync(CancellationToken ct = default)
|
||||
{
|
||||
await using var context = await _contextFactory.CreateDbContextAsync(ct);
|
||||
|
||||
var clubId = _clubContext.ClubId;
|
||||
var currentYear = DateTime.Now.Year;
|
||||
|
||||
// Member and guest counts
|
||||
var memberCount = await context.Persons
|
||||
.CountAsync(p => p.ClubId == clubId && !p.IsDeleted && p.PersonStatus == PersonStatus.Member, ct);
|
||||
|
||||
var guestCount = await context.Persons
|
||||
.CountAsync(p => p.ClubId == clubId && !p.IsDeleted && p.PersonStatus == PersonStatus.Guest, ct);
|
||||
|
||||
// Days this year
|
||||
var daysThisYear = await context.Days
|
||||
.CountAsync(d => d.ClubId == clubId && !d.IsDeleted && d.PostDate.Year == currentYear, ct);
|
||||
|
||||
// Open expenses
|
||||
var openExpenses = await context.PersonExpenses
|
||||
.Where(pe => pe.ClubId == clubId && !pe.IsDeleted && pe.PersonExpenseStatus == PersonExpenseStatus.Open)
|
||||
.ToListAsync(ct);
|
||||
|
||||
var totalOpenAmount = openExpenses.Sum(pe => pe.Price);
|
||||
var openExpenseCount = openExpenses.Count;
|
||||
|
||||
// Recent days (last 5)
|
||||
var recentDays = await context.Days
|
||||
.Where(d => d.ClubId == clubId && !d.IsDeleted)
|
||||
.OrderByDescending(d => d.PostDate)
|
||||
.Take(5)
|
||||
.Include(d => d.DayPersons)
|
||||
.Select(d => new DaySummaryDto
|
||||
{
|
||||
Id = d.Id,
|
||||
PostDate = d.PostDate,
|
||||
Status = d.Status,
|
||||
ParticipantCount = d.DayPersons.Count
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
|
||||
// Top penalty recipients (top 5 by open amount this year)
|
||||
var topRecipients = await context.PersonExpenses
|
||||
.Where(pe => pe.ClubId == clubId
|
||||
&& !pe.IsDeleted
|
||||
&& pe.PersonExpenseStatus == PersonExpenseStatus.Open
|
||||
&& pe.Day.PostDate.Year == currentYear)
|
||||
.Include(pe => pe.Person)
|
||||
.GroupBy(pe => new { pe.PersonId, pe.Person.Name })
|
||||
.Select(g => new TopPenaltyRecipientDto
|
||||
{
|
||||
PersonId = g.Key.PersonId,
|
||||
PersonName = g.Key.Name,
|
||||
OpenAmount = g.Sum(pe => pe.Price),
|
||||
ExpenseCount = g.Count()
|
||||
})
|
||||
.OrderByDescending(t => t.OpenAmount)
|
||||
.Take(5)
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new DashboardDto
|
||||
{
|
||||
MemberCount = memberCount,
|
||||
GuestCount = guestCount,
|
||||
DaysThisYear = daysThisYear,
|
||||
TotalOpenAmount = totalOpenAmount,
|
||||
OpenExpenseCount = openExpenseCount,
|
||||
RecentDays = recentDays,
|
||||
TopPenaltyRecipients = topRecipients
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
@page "/dashboard"
|
||||
@attribute [Authorize(Policy = "ClubViewer")]
|
||||
|
||||
@using Koogle.Application.DTOs
|
||||
@using Koogle.Application.Interfaces
|
||||
@using Koogle.Domain.Enums
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@inject IDashboardService DashboardService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Dashboard</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Dashboard</MudText>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
||||
}
|
||||
else if (_error is not null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mb-4">@_error</MudAlert>
|
||||
}
|
||||
else if (_dashboard is not null)
|
||||
{
|
||||
<MudGrid>
|
||||
<!-- Summary Cards -->
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent Class="d-flex flex-column align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.People" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.h4" Class="mt-2">@_dashboard.MemberCount</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Mitglieder</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent Class="d-flex flex-column align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.PersonAdd" Size="Size.Large" Color="Color.Info" />
|
||||
<MudText Typo="Typo.h4" Class="mt-2">@_dashboard.GuestCount</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Gäste</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardContent Class="d-flex flex-column align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarMonth" Size="Size.Large" Color="Color.Success" />
|
||||
<MudText Typo="Typo.h4" Class="mt-2">@_dashboard.DaysThisYear</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Spieltage @DateTime.Now.Year</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2" Style="cursor: pointer" @onclick="NavigateToExpenses">
|
||||
<MudCardContent Class="d-flex flex-column align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.MonetizationOn" Size="Size.Large" Color="Color.Warning" />
|
||||
<MudText Typo="Typo.h4" Class="mt-2">@_dashboard.TotalOpenAmount.ToString("C")</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Offene Strafen (@_dashboard.OpenExpenseCount)</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<!-- Recent Days -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Letzte Spieltage</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowForward" OnClick="NavigateToDays" />
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
@if (_dashboard.RecentDays.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Keine Spieltage vorhanden</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudList T="DaySummaryDto" Dense="true">
|
||||
@foreach (var day in _dashboard.RecentDays)
|
||||
{
|
||||
<MudListItem T="DaySummaryDto" OnClick="@(() => NavigateToDayDetails(day.Id))">
|
||||
<div class="d-flex justify-space-between align-center" style="width: 100%">
|
||||
<div>
|
||||
<MudText Typo="Typo.body1">@day.PostDate.ToString("dd.MM.yyyy")</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@day.ParticipantCount Teilnehmer</MudText>
|
||||
</div>
|
||||
<MudChip T="string" Size="Size.Small" Color="GetStatusColor(day.Status)">
|
||||
@GetStatusLabel(day.Status)
|
||||
</MudChip>
|
||||
</div>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<!-- Top Penalty Recipients -->
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCard Elevation="2">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Top Strafzahler @DateTime.Now.Year</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
@if (_dashboard.TopPenaltyRecipients.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Keine offenen Strafen</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudList T="TopPenaltyRecipientDto" Dense="true">
|
||||
@foreach (var recipient in _dashboard.TopPenaltyRecipients)
|
||||
{
|
||||
<MudListItem T="TopPenaltyRecipientDto">
|
||||
<div class="d-flex justify-space-between align-center" style="width: 100%">
|
||||
<div>
|
||||
<MudText Typo="Typo.body1">@recipient.PersonName</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@recipient.ExpenseCount Strafen</MudText>
|
||||
</div>
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Warning">
|
||||
@recipient.OpenAmount.ToString("C")
|
||||
</MudChip>
|
||||
</div>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
|
||||
@code {
|
||||
private DashboardDto? _dashboard;
|
||||
private bool _isLoading = true;
|
||||
private string? _error;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadDashboardAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDashboardAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
_dashboard = await DashboardService.GetDashboardAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_error = $"Fehler beim Laden des Dashboards: {ex.Message}";
|
||||
Snackbar.Add(_error, Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetStatusLabel(DayStatus status) => status switch
|
||||
{
|
||||
DayStatus.New => "Neu",
|
||||
DayStatus.Started => "Gestartet",
|
||||
DayStatus.Postponed => "Verschoben",
|
||||
DayStatus.Closed => "Abgeschlossen",
|
||||
_ => status.ToString()
|
||||
};
|
||||
|
||||
private static Color GetStatusColor(DayStatus status) => status switch
|
||||
{
|
||||
DayStatus.New => Color.Info,
|
||||
DayStatus.Started => Color.Warning,
|
||||
DayStatus.Postponed => Color.Secondary,
|
||||
DayStatus.Closed => Color.Success,
|
||||
_ => Color.Default
|
||||
};
|
||||
|
||||
private void NavigateToDays()
|
||||
{
|
||||
NavigationManager.NavigateTo("/days");
|
||||
}
|
||||
|
||||
private void NavigateToDayDetails(Guid dayId)
|
||||
{
|
||||
NavigationManager.NavigateTo($"/days/{dayId}");
|
||||
}
|
||||
|
||||
private void NavigateToExpenses()
|
||||
{
|
||||
NavigationManager.NavigateTo("/expenses");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
@page "/"
|
||||
@page "/"
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<AuthTest/>
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NavigationManager.NavigateTo("/dashboard", replace: true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Koogle.Application.Interfaces;
|
|||
using Koogle.Web.Store.AuthState;
|
||||
using Koogle.Web.Store.ClubState;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MudBlazor;
|
||||
|
|
@ -16,7 +15,7 @@ using System.Security.Claims;
|
|||
namespace Koogle.Tests.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Home page component.
|
||||
/// Tests for the Home page component (now redirects to Dashboard).
|
||||
/// </summary>
|
||||
public class HomePageTests : IDisposable
|
||||
{
|
||||
|
|
@ -39,7 +38,7 @@ public class HomePageTests : IDisposable
|
|||
_clubContextMock.Setup(c => c.ClubId).Returns(Guid.NewGuid());
|
||||
_clubContextMock.Setup(c => c.ClubName).Returns("Test Club");
|
||||
|
||||
// Setup Fluxor states - required by Home page and child components
|
||||
// Setup Fluxor states
|
||||
_authStateMock = new Mock<IState<AuthState>>();
|
||||
_authStateMock.Setup(s => s.Value).Returns(AuthState.Initial);
|
||||
|
||||
|
|
@ -55,7 +54,10 @@ public class HomePageTests : IDisposable
|
|||
It.IsAny<string>()))
|
||||
.ReturnsAsync(AuthorizationResult.Success());
|
||||
|
||||
// Register all services before any rendering
|
||||
// Add test authorization first before any other services
|
||||
_ctx.AddTestAuthorization().SetAuthorized("testuser");
|
||||
|
||||
// Register all services
|
||||
_ctx.Services.AddMudServices(options =>
|
||||
{
|
||||
options.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
|
||||
|
|
@ -68,16 +70,16 @@ public class HomePageTests : IDisposable
|
|||
_ctx.Services.AddSingleton(_clubStateMock.Object);
|
||||
_ctx.Services.AddSingleton(_authorizationServiceMock.Object);
|
||||
|
||||
// Additional Fluxor services required by components
|
||||
// Additional Fluxor services
|
||||
_ctx.Services.AddSingleton(new Mock<IActionSubscriber>().Object);
|
||||
_ctx.Services.AddSingleton(new Mock<IStore>().Object);
|
||||
|
||||
// ASP.NET Core services needed by child components
|
||||
// ASP.NET Core services
|
||||
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
|
||||
httpContextAccessorMock.Setup(x => x.HttpContext).Returns(new DefaultHttpContext());
|
||||
_ctx.Services.AddSingleton(httpContextAccessorMock.Object);
|
||||
|
||||
// Stub complex components to avoid deep dependency chains
|
||||
// Stub complex components
|
||||
_ctx.ComponentFactories.AddStub<Koogle.Web.Components.Shared.LogoutButton>();
|
||||
}
|
||||
|
||||
|
|
@ -87,89 +89,30 @@ public class HomePageTests : IDisposable
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void HomePage_RendersWelcomeMessage_WhenAuthorized()
|
||||
public void HomePage_RedirectsToDashboard_WhenAuthorized()
|
||||
{
|
||||
// Arrange
|
||||
var authContext = _ctx.AddTestAuthorization();
|
||||
authContext.SetAuthorized("testuser");
|
||||
_authStateMock.Setup(s => s.Value).Returns(CreateAuthState());
|
||||
|
||||
// Setup authenticated auth state
|
||||
// Act
|
||||
_ctx.RenderComponent<Koogle.Web.Components.Pages.Home>();
|
||||
|
||||
// Assert - Home page should redirect to Dashboard
|
||||
var navMan = _ctx.Services.GetRequiredService<FakeNavigationManager>();
|
||||
navMan.Uri.Should().EndWith("/dashboard");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HomePage_RendersNoContent_BecauseItRedirects()
|
||||
{
|
||||
// Arrange
|
||||
_authStateMock.Setup(s => s.Value).Returns(CreateAuthState());
|
||||
|
||||
// Act
|
||||
var cut = _ctx.RenderComponent<Koogle.Web.Components.Pages.Home>();
|
||||
|
||||
// Assert
|
||||
cut.Markup.Should().Contain("Hello, world!");
|
||||
cut.Markup.Should().Contain("Welcome to your new app");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HomePage_SetsPageTitle()
|
||||
{
|
||||
// Arrange
|
||||
var authContext = _ctx.AddTestAuthorization();
|
||||
authContext.SetAuthorized("testuser");
|
||||
|
||||
// Setup authenticated auth state
|
||||
_authStateMock.Setup(s => s.Value).Returns(CreateAuthState());
|
||||
|
||||
// Act
|
||||
var cut = _ctx.RenderComponent<Koogle.Web.Components.Pages.Home>();
|
||||
|
||||
// Assert
|
||||
var pageTitle = cut.FindComponent<Microsoft.AspNetCore.Components.Web.PageTitle>();
|
||||
pageTitle.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HomePage_DisplaysCurrentClub_WhenAuthorized()
|
||||
{
|
||||
// Arrange
|
||||
var authContext = _ctx.AddTestAuthorization();
|
||||
authContext.SetAuthorized("testuser");
|
||||
|
||||
_authStateMock.Setup(s => s.Value).Returns(CreateAuthState());
|
||||
_clubContextMock.Setup(c => c.ClubName).Returns("Kegel Freunde 2025");
|
||||
|
||||
// Act
|
||||
var cut = _ctx.RenderComponent<Koogle.Web.Components.Pages.Home>();
|
||||
|
||||
// Assert - AuthTest component shows current club name
|
||||
cut.Markup.Should().Contain("Kegel Freunde 2025");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HomePage_ShowsUserDisplayName_WhenAuthenticated()
|
||||
{
|
||||
// Arrange
|
||||
var authContext = _ctx.AddTestAuthorization();
|
||||
authContext.SetAuthorized("testuser");
|
||||
|
||||
var authState = CreateAuthState("Max Mustermann");
|
||||
_authStateMock.Setup(s => s.Value).Returns(authState);
|
||||
|
||||
// Act
|
||||
var cut = _ctx.RenderComponent<Koogle.Web.Components.Pages.Home>();
|
||||
|
||||
// Assert - AuthTest component shows user display name
|
||||
cut.Markup.Should().Contain("Max Mustermann");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HomePage_ShowsAuthTestComponent()
|
||||
{
|
||||
// Arrange
|
||||
var authContext = _ctx.AddTestAuthorization();
|
||||
authContext.SetAuthorized("testuser");
|
||||
|
||||
_authStateMock.Setup(s => s.Value).Returns(CreateAuthState());
|
||||
|
||||
// Act
|
||||
var cut = _ctx.RenderComponent<Koogle.Web.Components.Pages.Home>();
|
||||
|
||||
// Assert - AuthTest component header is rendered
|
||||
cut.Markup.Should().Contain("AuthTest-Component");
|
||||
// Assert - Home page renders no visible content since it redirects
|
||||
cut.Markup.Should().BeEmpty();
|
||||
}
|
||||
|
||||
private static AuthState CreateAuthState(string displayName = "Test User")
|
||||
|
|
|
|||
Loading…
Reference in New Issue