9f090cec1f
* Pages/Results/ResultsList.razor — completed-events list with date range, sport/winner filter, search, footer count. * Pages/Results/ResultsLoader.razor — driver page with two modes (load all in range / load selected events), live progress reporting via IProgress<PullResultsProgress>, summary line, cancellable. * Replaces the Phase 5 Pages/Results.razor placeholder. Service layer: * IResultsBrowsingService + ResultsBrowsingService (Scoped, mirrors the Event/Anomaly browsing-service pattern). Reads IResultRepository + IEventRepository, projects to immutable view-model records. * UiServicesExtensions: registers ResultsBrowsingService; also fixes an unrelated localization resolver bug (drop ResourcesPath since SharedResource lives in the Marathon.UI.Resources namespace already). Localization: * 41 new Results.* keys (RU+EN parity) covering both pages, filter chips, loader modes, progress states, and footer copy. Tests: * ResultsListTests + ResultsLoaderTests — 22 new bUnit tests covering filter narrowing, mode switching, progress aggregation, and empty states. * FakeResultsBrowsingService support type for tests. * MarathonTestContext registers the fake; TestData adds factories for EventResult/EventResultListItem.
50 lines
1.8 KiB
C#
50 lines
1.8 KiB
C#
using Marathon.Application.Storage;
|
|
using Marathon.UI.Services;
|
|
|
|
namespace Marathon.UI.Tests.Support;
|
|
|
|
/// <summary>
|
|
/// In-memory <see cref="IResultsBrowsingService"/> for bUnit tests.
|
|
/// Seed via <see cref="ResultItems"/> / <see cref="Candidates"/>.
|
|
/// </summary>
|
|
public sealed class FakeResultsBrowsingService : IResultsBrowsingService
|
|
{
|
|
public List<EventResultListItem> ResultItems { get; } = new();
|
|
public List<EventResultCandidate> Candidates { get; } = new();
|
|
public ResultsFilter? LastResultsFilter { get; private set; }
|
|
public DateRange? LastCandidatesRange { get; private set; }
|
|
|
|
public Task<IReadOnlyList<EventResultListItem>> ListResultsAsync(
|
|
ResultsFilter filter,
|
|
CancellationToken ct)
|
|
{
|
|
LastResultsFilter = filter;
|
|
IEnumerable<EventResultListItem> q = ResultItems;
|
|
|
|
if (filter.SportCodes is { Count: > 0 } sports)
|
|
q = q.Where(r => sports.Contains(r.Sport.Value));
|
|
|
|
if (filter.WinnerSide is { } winner)
|
|
q = q.Where(r => r.WinnerSide == winner);
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.SearchTerm))
|
|
{
|
|
var t = filter.SearchTerm.Trim();
|
|
q = q.Where(r =>
|
|
r.LeagueId.Contains(t, StringComparison.OrdinalIgnoreCase) ||
|
|
r.Side1Name.Contains(t, StringComparison.OrdinalIgnoreCase) ||
|
|
r.Side2Name.Contains(t, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
return Task.FromResult<IReadOnlyList<EventResultListItem>>(q.ToList());
|
|
}
|
|
|
|
public Task<IReadOnlyList<EventResultCandidate>> ListLoadCandidatesAsync(
|
|
DateRange range,
|
|
CancellationToken ct)
|
|
{
|
|
LastCandidatesRange = range;
|
|
return Task.FromResult<IReadOnlyList<EventResultCandidate>>(Candidates.ToList());
|
|
}
|
|
}
|