Fix upcoming events grouping to separate individual bracket matches (#302)
Include playoffMatchId in the grouping key so that each match within a scoring event (e.g. different Round of 32 matchups in snooker) appears as its own calendar entry instead of being merged together.
This commit is contained in:
parent
13bc1e3b5f
commit
e03bdd538f
2 changed files with 68 additions and 8 deletions
|
|
@ -23,7 +23,7 @@ vi.mock("~/database/context", () => ({
|
|||
|
||||
vi.mock("~/database/schema", () => ({
|
||||
scoringEvents: { sportsSeasonId: "se.sports_season_id", isComplete: "se.is_complete", eventDate: "se.event_date", eventType: "se.event_type" },
|
||||
playoffMatches: { scoringEventId: "pm.scoring_event_id", participant1Id: "pm.participant1_id", participant2Id: "pm.participant2_id", round: "pm.round", matchNumber: "pm.match_number", isComplete: "pm.is_complete" },
|
||||
playoffMatches: { id: "pm.id", scoringEventId: "pm.scoring_event_id", participant1Id: "pm.participant1_id", participant2Id: "pm.participant2_id", round: "pm.round", matchNumber: "pm.match_number", isComplete: "pm.is_complete" },
|
||||
playoffMatchGames: { playoffMatchId: "pmg.playoff_match_id", scheduledAt: "pmg.scheduled_at", gameNumber: "pmg.game_number" },
|
||||
}));
|
||||
|
||||
|
|
@ -183,6 +183,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
const rows = [{
|
||||
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "bayern",
|
||||
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -207,6 +208,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
{
|
||||
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "arsenal",
|
||||
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
|
||||
},
|
||||
|
|
@ -226,17 +228,19 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
expect(names).toContain("Arsenal");
|
||||
});
|
||||
|
||||
it("returns multiple events when picks are in different matches", async () => {
|
||||
it("returns separate events when picks are in different matches within the same scoring event", async () => {
|
||||
const rows = [
|
||||
{
|
||||
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "real-madrid",
|
||||
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
|
||||
},
|
||||
{
|
||||
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm2",
|
||||
participant1Id: "arsenal", participant2Id: "psg",
|
||||
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
|
||||
},
|
||||
|
|
@ -249,15 +253,21 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
DATE_FROM, DATE_TO
|
||||
);
|
||||
|
||||
// Both matches are in the same event → one result with both picks
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].relevantParticipants).toHaveLength(2);
|
||||
// Each match is now a separate entry
|
||||
expect(result).toHaveLength(2);
|
||||
const manCityEntry = result.find((r) => r.relevantParticipants.some((p) => p.id === "man-city"));
|
||||
const arsenalEntry = result.find((r) => r.relevantParticipants.some((p) => p.id === "arsenal"));
|
||||
expect(manCityEntry).toBeDefined();
|
||||
expect(arsenalEntry).toBeDefined();
|
||||
expect(manCityEntry?.relevantParticipants).toHaveLength(1);
|
||||
expect(arsenalEntry?.relevantParticipants).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("only includes drafted participants in relevantParticipants, not opponents", async () => {
|
||||
const rows = [{
|
||||
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "real-madrid",
|
||||
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -278,6 +288,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain([{
|
||||
id: "e1", name: "UCL SF", eventDate: "2025-04-29",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "arsenal",
|
||||
round: "Semifinals", gameNumber: 1, scheduledAt: null,
|
||||
}]));
|
||||
|
|
@ -297,6 +308,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
id: "e1", name: "UCL QF",
|
||||
eventDate: null, // ← no date on the event itself
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "barcelona", participant2Id: "sporting",
|
||||
round: "Knockout Stage", gameNumber: 1, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -320,6 +332,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
id: "e1", name: "UCL QF",
|
||||
eventDate: null,
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "barcelona", participant2Id: "sporting",
|
||||
round: "Knockout Stage", gameNumber: 1, scheduledAt: gameTime,
|
||||
}];
|
||||
|
|
@ -342,12 +355,14 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
{
|
||||
id: "e1", name: "UCL QF", eventDate: null,
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "barcelona", participant2Id: "sporting",
|
||||
round: "Knockout Stage", gameNumber: 1, scheduledAt: gameTime1,
|
||||
},
|
||||
{
|
||||
id: "e1", name: "UCL QF", eventDate: null,
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "barcelona", participant2Id: "sporting",
|
||||
round: "Knockout Stage", gameNumber: 2, scheduledAt: gameTime2,
|
||||
},
|
||||
|
|
@ -366,8 +381,8 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
const game1 = result.find((r) => r.id === "e1|1");
|
||||
const game2 = result.find((r) => r.id === "e1|2");
|
||||
const game1 = result.find((r) => r.id === "e1|pm1|1");
|
||||
const game2 = result.find((r) => r.id === "e1|pm1|2");
|
||||
expect(game1?.earliestGameTime).toBe(gameTime1.toISOString());
|
||||
expect(game1?.matchLabel).toBe("Knockout Stage Game #1");
|
||||
expect(game2?.earliestGameTime).toBe(gameTime2.toISOString());
|
||||
|
|
@ -378,6 +393,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
const rows = [{
|
||||
id: "e1", name: "PDC World Championship", eventDate: "2025-04-09",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "arsenal",
|
||||
round: "Semifinals", gameNumber: 1, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -398,6 +414,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
const rows = [{
|
||||
id: "e1", name: "Knockout Stage", eventDate: null,
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "barcelona", participant2Id: "sporting",
|
||||
round: "Knockout Stage", gameNumber: 1, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -416,6 +433,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
const rows = [{
|
||||
id: "e1", name: "PDC World Championship", eventDate: "2025-04-10",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "man-city", participant2Id: "arsenal",
|
||||
round: "Semifinals", gameNumber: 2, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -441,6 +459,7 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
const mainRows = [{
|
||||
id: "e1", name: "NBA Finals", eventDate: "2025-04-10",
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm1",
|
||||
participant1Id: "lakers", participant2Id: "celtics",
|
||||
round: "Finals", gameNumber: 1, scheduledAt: null,
|
||||
}];
|
||||
|
|
@ -461,6 +480,44 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
|||
expect(result[0].matchLabel).toBe("Finals Game #1");
|
||||
});
|
||||
|
||||
it("separates matches within the same scoring event by scheduled time (snooker-style bracket)", async () => {
|
||||
const time1 = new Date("2025-04-18T06:30:00.000Z");
|
||||
const time2 = new Date("2025-04-19T06:30:00.000Z");
|
||||
const rows = [
|
||||
{
|
||||
id: "e1", name: "Snooker World Championship", eventDate: null,
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm-williams",
|
||||
participant1Id: "mark-williams", participant2Id: "opponent-a",
|
||||
round: "Round of 32", gameNumber: 1, scheduledAt: time1,
|
||||
},
|
||||
{
|
||||
id: "e1", name: "Snooker World Championship", eventDate: null,
|
||||
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
|
||||
playoffMatchId: "pm-higgins",
|
||||
participant1Id: "john-higgins", participant2Id: "opponent-b",
|
||||
round: "Round of 32", gameNumber: 1, scheduledAt: time2,
|
||||
},
|
||||
];
|
||||
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
|
||||
|
||||
const result = await getUpcomingEventsForDraftedParticipants(
|
||||
SPORTS_SEASON_ID, "playoff_bracket",
|
||||
[makeParticipant("mark-williams", "Mark Williams"), makeParticipant("john-higgins", "John Higgins")],
|
||||
DATE_FROM, DATE_TO
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
const williamsEntry = result.find((r) => r.relevantParticipants.some((p) => p.id === "mark-williams"));
|
||||
const higginsEntry = result.find((r) => r.relevantParticipants.some((p) => p.id === "john-higgins"));
|
||||
expect(williamsEntry).toBeDefined();
|
||||
expect(higginsEntry).toBeDefined();
|
||||
expect(williamsEntry?.earliestGameTime).toBe(time1.toISOString());
|
||||
expect(higginsEntry?.earliestGameTime).toBe(time2.toISOString());
|
||||
expect(williamsEntry?.matchLabel).toBe("Round of 32");
|
||||
expect(higginsEntry?.matchLabel).toBe("Round of 32");
|
||||
});
|
||||
|
||||
it("all-compete events have null earliestGameTime and matchLabel", async () => {
|
||||
const participants = [makeParticipant("p1", "Verstappen")];
|
||||
mockDb.query.scoringEvents.findMany.mockResolvedValue([
|
||||
|
|
|
|||
|
|
@ -425,6 +425,7 @@ export async function getUpcomingEventsForDraftedParticipants(
|
|||
eventDate: schema.scoringEvents.eventDate,
|
||||
eventType: schema.scoringEvents.eventType,
|
||||
sportsSeasonId: schema.scoringEvents.sportsSeasonId,
|
||||
playoffMatchId: schema.playoffMatches.id,
|
||||
participant1Id: schema.playoffMatches.participant1Id,
|
||||
participant2Id: schema.playoffMatches.participant2Id,
|
||||
round: schema.playoffMatches.round,
|
||||
|
|
@ -512,7 +513,9 @@ export async function getUpcomingEventsForDraftedParticipants(
|
|||
for (const row of rows) {
|
||||
const isSeries = (maxGameNumberByEvent.get(row.id) ?? 1) > 1;
|
||||
const entryKey =
|
||||
isSeries && row.gameNumber !== null ? `${row.id}|${row.gameNumber}` : row.id;
|
||||
isSeries && row.gameNumber !== null
|
||||
? `${row.id}|${row.playoffMatchId}|${row.gameNumber}`
|
||||
: `${row.id}|${row.playoffMatchId}`;
|
||||
|
||||
if (!entryMap.has(entryKey)) {
|
||||
entryMap.set(entryKey, {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue