fix(lolschedule): group week by league → day instead of day → league

Users expect to scan a single region's upcoming week rather than a daily
fixture board. Reverses the nesting: /lolschedule_week now lists each
major league as a top-level section, then italicised ICT date headers
with that league's matches beneath.
This commit is contained in:
2026-04-21 10:44:35 +07:00
committed by tiennm99
parent de625aebf5
commit e3de1be973
3 changed files with 36 additions and 30 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ LoL esports match schedule via the **lolesports.com** esports-api (the data feed
| Command | Description |
|---|---|
| `/lolschedule_today` | Today's matches (ICT), grouped by league. Scores for played + live, times for upcoming. |
| `/lolschedule_week` | Next 7 days, grouped by day → league. |
| `/lolschedule_week` | Next 7 days, grouped by league → day. |
| `/lolschedule_subscribe` | Opt the current chat into the daily 08:00 ICT digest. |
| `/lolschedule_unsubscribe` | Stop receiving the digest. |
@@ -63,7 +63,7 @@ Cache-first with KV. Key is `matches:{fromIso}:{toIso}`.
## Grouping
- `/lolschedule_today` — one section per league (header + match lines).
- `/lolschedule_week` — one section per ICT day; within each day, leagues are sub-grouped.
- `/lolschedule_week` — one section per league; within each league, matches are sub-grouped by ICT day (italic date headers).
- League ordering follows `LEAGUE_ORDER` in `format.js` (worlds / msi / first_stand first, then LCK / LPL / LEC / LCS, then the rest).
## Subscribers
+22 -19
View File
@@ -151,7 +151,9 @@ export function renderToday(events, day) {
}
/**
* Render week reply — grouped by ICT day league.
* Render week reply — grouped by league → ICT day. The league ordering is
* determined by LEAGUE_ORDER; within each league, days appear chronologically
* and each day section lists its matches.
*
* @param {ScheduleEvent[]} events
* @param {Date} from
@@ -164,24 +166,25 @@ export function renderWeek(events, from, to) {
const header = `<b>LoL — ${fromLbl}${toLbl}</b> (ICT)`;
if (events.length === 0) return `${header}\nNo matches this week.`;
/** @type {Map<string, { label: string, events: ScheduleEvent[] }>} */
const days = new Map();
for (const event of events) {
const d = new Date(event.startTime);
const key = ictDayKey(d);
let g = days.get(key);
if (!g) {
g = { label: formatIctDayLabel(d), events: [] };
days.set(key, g);
const leagueBlocks = groupByLeague(events).map((league) => {
/** @type {Map<string, { label: string, lines: string[] }>} */
const daysInLeague = new Map();
for (const event of league.events) {
const d = new Date(event.startTime);
const key = ictDayKey(d);
let g = daysInLeague.get(key);
if (!g) {
g = { label: formatIctDayLabel(d), lines: [] };
daysInLeague.set(key, g);
}
g.lines.push(formatEventLine(event));
}
g.events.push(event);
}
const daySections = [...daysInLeague.keys()].sort().map((key) => {
const day = daysInLeague.get(key);
return `<i>${escapeHtml(day.label)}</i>\n${day.lines.join("\n")}`;
});
return `<b>${escapeHtml(league.name)}</b>\n${daySections.join("\n")}`;
});
const dayBlocks = [];
for (const key of [...days.keys()].sort()) {
const day = days.get(key);
const leagueSections = groupByLeague(day.events).map(renderLeagueSection);
dayBlocks.push(`<b>${escapeHtml(day.label)}</b>\n${leagueSections.join("\n")}`);
}
return `${header}\n\n${dayBlocks.join("\n\n")}`;
return `${header}\n\n${leagueBlocks.join("\n\n")}`;
}
+12 -9
View File
@@ -153,12 +153,8 @@ describe("renderWeek", () => {
).toContain("No matches this week.");
});
it("nests leagues under each ICT day in chronological order", () => {
it("groups by league first then nests days chronologically", () => {
const events = [
evt({
startTime: "2026-04-21T09:00:00Z",
league: { name: "LCK", slug: "lck" },
}),
evt({
startTime: "2026-04-22T09:00:00Z",
league: { name: "LPL", slug: "lpl" },
@@ -167,15 +163,22 @@ describe("renderWeek", () => {
startTime: "2026-04-22T11:00:00Z",
league: { name: "LCK", slug: "lck" },
}),
evt({
startTime: "2026-04-21T09:00:00Z",
league: { name: "LCK", slug: "lck" },
}),
];
const out = renderWeek(
events,
new Date("2026-04-21T00:00:00Z"),
new Date("2026-04-28T00:00:00Z"),
);
expect(out.indexOf("Apr 21")).toBeLessThan(out.indexOf("Apr 22"));
// Apr 22: LCK section appears before LPL (per LEAGUE_ORDER)
const apr22Block = out.split("Apr 22")[1] || "";
expect(apr22Block.indexOf("<b>LCK</b>")).toBeLessThan(apr22Block.indexOf("<b>LPL</b>"));
// Top-level: LCK section appears before LPL (per LEAGUE_ORDER).
expect(out.indexOf("<b>LCK</b>")).toBeLessThan(out.indexOf("<b>LPL</b>"));
// Inside LCK: Apr 21 before Apr 22.
const lckBlock = out.split("<b>LCK</b>")[1].split("<b>LPL</b>")[0];
expect(lckBlock.indexOf("Apr 21")).toBeLessThan(lckBlock.indexOf("Apr 22"));
// Day labels use italic inside league sections (distinct from league bold).
expect(lckBlock).toContain("<i>Tue Apr 21</i>");
});
});