Files
alexei.dolgolyov 9f090cec1f feat(phase-8-frontend): results loader UI + browsing list + 41 localization keys
* 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.
2026-05-09 15:10:49 +03:00

134 lines
4.0 KiB
C#

using Marathon.Domain.Entities;
using Marathon.Domain.Enums;
using Marathon.Domain.ValueObjects;
using Marathon.UI.Services;
namespace Marathon.UI.Tests.Support;
internal static class TestData
{
public static readonly TimeSpan Moscow = TimeSpan.FromHours(3);
public static EventListItem ListItem(
string id = "100001",
int sport = 11,
string country = "ENG",
string league = "Premier League",
string side1 = "Arsenal",
string side2 = "Chelsea",
decimal? win1 = 1.85m,
decimal? draw = 3.40m,
decimal? win2 = 4.20m,
DateTimeOffset? scheduled = null)
=> new(
new EventId(id),
new SportCode(sport),
country,
league,
side1,
side2,
scheduled ?? MoscowToday(20),
win1,
draw,
win2,
DateTimeOffset.UtcNow,
OddsSource.PreMatch);
/// <summary>Today at <paramref name="hour"/>:00 in Moscow (+03:00).</summary>
public static DateTimeOffset MoscowToday(int hour)
{
var nowMoscow = DateTimeOffset.UtcNow.ToOffset(Moscow);
var midnight = new DateTimeOffset(
nowMoscow.Year, nowMoscow.Month, nowMoscow.Day,
0, 0, 0, Moscow);
return midnight.AddHours(hour);
}
public static EventResultListItem ResultListItem(
string id = "100001",
int sport = 11,
string country = "ENG",
string league = "Premier League",
string side1 = "Arsenal",
string side2 = "Chelsea",
int side1Score = 2,
int side2Score = 1,
Side winner = Side.Side1,
DateTimeOffset? scheduled = null,
DateTimeOffset? completed = null)
=> new(
new EventId(id),
new SportCode(sport),
country,
league,
side1,
side2,
scheduled ?? MoscowToday(20),
side1Score,
side2Score,
winner,
completed ?? MoscowToday(22));
public static EventResultCandidate ResultCandidate(
string id = "100001",
int sport = 11,
string country = "ENG",
string league = "Premier League",
string side1 = "Arsenal",
string side2 = "Chelsea",
DateTimeOffset? scheduled = null)
=> new(
new EventId(id),
new SportCode(sport),
country,
league,
side1,
side2,
scheduled ?? MoscowToday(20));
public static EventDetail Detail(
string id = "100001",
params OddsTimelinePoint[] timeline)
{
var moscow = Moscow;
var scheduled = MoscowToday(20);
var timelinePts = timeline.Length > 0
? timeline.ToList()
: new List<OddsTimelinePoint>
{
new(scheduled.AddMinutes(-30), 1.90m, 3.30m, 4.10m),
new(scheduled.AddMinutes(-15), 1.85m, 3.40m, 4.20m),
};
var matchBoard = new EventScopeBoard(MatchScope.Instance, new List<BetRow>
{
new(BetType.Win, Side.Side1, null, 1.85m),
new(BetType.Draw, Side.Draw, null, 3.40m),
new(BetType.Win, Side.Side2, null, 4.20m),
});
var period1Board = new EventScopeBoard(new PeriodScope(1), new List<BetRow>
{
new(BetType.Win, Side.Side1, null, 2.10m),
new(BetType.Win, Side.Side2, null, 3.10m),
new(BetType.Total, Side.More, 1.5m, 1.95m),
new(BetType.Total, Side.Less, 1.5m, 1.95m),
});
var history = timelinePts.Select(p =>
new SnapshotHistoryEntry(p.At, OddsSource.PreMatch, 8, p.Win1Rate, p.DrawRate, p.Win2Rate)
).ToList();
return new EventDetail(
new EventId(id),
new SportCode(11),
"ENG",
"Premier League",
"Arsenal",
"Chelsea",
scheduled,
new[] { matchBoard, period1Board },
timelinePts,
history);
}
}