brackt/app/services/__tests__/discord.test.ts

445 lines
14 KiB
TypeScript
Raw Normal View History

import { describe, it, expect, vi, beforeEach } from "vitest";
import { sendDiscordWebhook, sendStandingsUpdateNotification } from "../discord";
const WEBHOOK_URL = "https://discord.com/api/webhooks/123/abc";
function mockFetch(status: number, body = "") {
return vi.fn().mockResolvedValue({
ok: status >= 200 && status < 300,
status,
text: async () => body,
});
}
function getDescription(): string {
const body = JSON.parse(
(fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].body
);
return body.embeds[0].description as string;
}
Add opt-in Discord pings to standings notifications (#429) * Add opt-in Discord ping to standings notifications Users who have linked their Discord account can enable a ping preference in Settings > Notifications. When enabled, their bracket username is replaced with a Discord @mention (<@userId>) in league standings update messages, sending them a push notification and showing their Discord display name. Adds discordPingEnabled column to users, a findDiscordIdsByUserIds model helper, allowed_mentions support to the Discord webhook payload, and a NotificationsSection settings component. https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs * Remove Display Name field from user profile settings Username is the only user-facing name field. The display_name column remains in the database since BetterAuth writes the OAuth provider name there, and it serves as a programmatic fallback via getUserDisplayName. https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs * Address code review feedback on Discord ping feature - Fix broken Account settings link in NotificationsSection: replace the non-functional ?section=account href with an onNavigateToAccount callback that drives the parent's useState-based section switcher - Replace hand-rolled toggle button with the project's Switch component (Radix UI) for consistent sizing, accessibility, and dark-mode support - Guard update-discord-ping action: return an error if the user attempts to enable pings without a Discord account linked - Surface action error in NotificationsSection UI - Cap allowed_mentions.users at 100 to avoid Discord rejecting the payload in large leagues - Remove the showWinner alias in scoring-calculator; inline winnerScoreChanged - Add comment to league settings test notification explaining why discordUserId is omitted - Clarify database schema comment: displayName is write-only from BetterAuth https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 10:06:54 -07:00
function getPayload(): ReturnType<typeof JSON.parse> {
return JSON.parse((fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].body);
}
describe("sendDiscordWebhook", () => {
beforeEach(() => {
vi.stubGlobal("fetch", mockFetch(204));
});
it("POSTs JSON to the webhook URL", async () => {
await sendDiscordWebhook(WEBHOOK_URL, { content: "hello" });
expect(fetch).toHaveBeenCalledWith(WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: "hello" }),
});
});
it("throws on non-2xx response", async () => {
vi.stubGlobal("fetch", mockFetch(400, "Bad Request"));
await expect(sendDiscordWebhook(WEBHOOK_URL, {})).rejects.toThrow(
"Discord webhook failed: 400"
);
});
});
describe("sendStandingsUpdateNotification", () => {
beforeEach(() => {
vi.stubGlobal("fetch", mockFetch(204));
});
it("sends an embed with the season name as title", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 150, rank: 1 }],
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
previousStandings: new Map([["a", 125]]),
});
const body = JSON.parse((fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].body);
expect(body.embeds[0].title).toBe("📊 Standings Update — My League 2025");
});
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
it("shows point deltas as integers in standings changes", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "Test League",
standings: [
{ teamId: "a", teamName: "Alpha", totalPoints: 150, rank: 1 },
{ teamId: "b", teamName: "Beta", totalPoints: 100, rank: 2 },
],
previousStandings: new Map([
["a", 125],
["b", 100],
]),
});
const desc = getDescription();
expect(desc).toContain("Alpha");
expect(desc).toContain("+25 pts");
expect(desc).not.toContain("+25.0");
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
// Beta didn't change — not shown in standings changes
expect(desc).not.toContain("Beta");
});
it("includes sport name and event name in header when both provided", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
sportName: "UEFA Champions League",
eventName: "Knockout Stage",
});
const desc = getDescription();
expect(desc).toContain("UEFA Champions League — Knockout Stage");
});
it("shows only event name when no sport name provided", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
eventName: "Quarter Finals",
});
const desc = getDescription();
expect(desc).toContain("Quarter Finals");
expect(desc).not.toContain("—\n");
});
it("omits header section when neither sport name nor event name provided", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
});
const desc = getDescription();
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
expect(desc).toContain("**Standings Changes**");
expect(desc).not.toContain("**Scored Matches**");
});
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
it("lists scored matches above standings changes", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
scoredMatches: [
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
{ winnerName: "Real Madrid", loserName: "Bayern Munich", winnerUsername: "manager1" },
{ winnerName: "Arsenal", loserName: "PSG", loserUsername: "manager2" },
],
});
const desc = getDescription();
expect(desc).toContain("**Scored Matches**");
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
expect(desc).toContain("• **Real Madrid (manager1)** def. Bayern Munich");
expect(desc).toContain("• **Arsenal** def. PSG (manager2)");
// Scored matches section should appear before standings
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
expect(desc.indexOf("Scored Matches")).toBeLessThan(desc.indexOf("Standings Changes"));
});
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
it("shows username in parentheses in standings changes when provided", async () => {
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Alpha FC", username: "christhrowsrocks", totalPoints: 150, rank: 1 },
{ teamId: "b", teamName: "Beta United", totalPoints: 100, rank: 2 },
],
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
previousStandings: new Map([
["a", 125],
["b", 100],
]),
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
});
const desc = getDescription();
expect(desc).toContain("1\\. Alpha FC (christhrowsrocks) — 150 pts");
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
// Beta didn't change points or rank, so it's not shown
expect(desc).not.toContain("Beta United");
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
});
it("shows usernames in parentheses in match results when both are drafted", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
scoredMatches: [
{
winnerName: "Sporting",
loserName: "Bodø/Glimt",
winnerUsername: "christhrowsrocks",
loserUsername: "apatel",
},
],
});
const desc = getDescription();
expect(desc).toContain("• **Sporting (christhrowsrocks)** def. Bodø/Glimt (apatel)");
});
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
it("omits scored matches where neither side has a username", async () => {
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
scoredMatches: [{ winnerName: "Real Madrid", loserName: "Bayern Munich" }],
});
const desc = getDescription();
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
expect(desc).not.toContain("**Scored Matches**");
expect(desc).not.toContain("Real Madrid");
expect(desc).not.toContain("Bayern Munich");
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
});
it("omits username parenthesis only for the undrafted side in match results", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
Add Discord usernames, fix calculateTeamScore partial-score bug, and add admin re-score action (#160) **Discord webhook improvements** - Show team owner username in parentheses in standings (e.g. "1. Alpha FC (christhrowsrocks) — 150 pts") - Show username in parentheses in match results for drafted participants (e.g. "Sporting (christhrowsrocks) def. Bodø/Glimt (apatel)"), omitting the parenthesis for the undrafted side - Username lookup only runs after the webhook URL guard to avoid wasted DB queries - 4 new tests covering all username display combinations **Fix calculateTeamScore partial-score counting bug** - isPartialScore participants were incorrectly counted in participantsCompleted and placementCounts, causing standings to show wrong remaining count and phantom placement badges (e.g. "5th×1") for still-alive bracket participants - Floor points still flow into totalPoints (guaranteed value for ranking) - 3 new unit tests covering partial vs finalized behavior in calculateTeamScore **Admin Force Re-score action** - New "Fantasy Standings" card on every sports season admin page with a Force Re-score button that triggers recalculateStandings on all linked fantasy seasons — useful after data corrections without waiting for a new scoring event - Auth guard added to the action (was missing — layout loader only protects GET) - Returns a meaningful message when no linked seasons exist - Intent discriminant on action returns so success banners use actionData.intent instead of fragile message.includes() checks Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:34:09 -07:00
scoredMatches: [
{
winnerName: "Sporting",
loserName: "Bodø/Glimt",
winnerUsername: "christhrowsrocks",
},
],
});
const desc = getDescription();
expect(desc).toContain("• **Sporting (christhrowsrocks)** def. Bodø/Glimt");
expect(desc).not.toContain("Bodø/Glimt (");
});
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
it("shows teams that changed rank even without a point change", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Alpha", totalPoints: 150, rank: 1 },
{ teamId: "b", teamName: "Beta", totalPoints: 125, rank: 2 },
{ teamId: "c", teamName: "Gamma", totalPoints: 100, rank: 3 },
],
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
previousStandings: new Map([
["a", 125],
["b", 125],
["c", 100],
]),
previousRanks: new Map([
["a", 2],
["b", 1],
["c", 3],
]),
});
const desc = getDescription();
// Alpha gained points and moved up
expect(desc).toContain("Alpha");
expect(desc).toContain("+25 pts");
expect(desc).toContain("↑1");
// Beta didn't gain points but was displaced — rank changed
expect(desc).toContain("Beta");
expect(desc).toContain("↓1");
// Gamma unchanged — not shown
expect(desc).not.toContain("Gamma");
});
it("omits rank delta when no previousRanks provided", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Alpha", totalPoints: 150, rank: 1 },
],
previousStandings: new Map([["a", 125]]),
});
const desc = getDescription();
expect(desc).toContain("Alpha");
expect(desc).not.toContain("↑");
expect(desc).not.toContain("↓");
});
it("escapes Discord markdown characters in team names and usernames", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Team__Underline", username: "user__name", totalPoints: 150, rank: 1 },
],
previousStandings: new Map([["a", 125]]),
scoredMatches: [
{
winnerName: "Winner*Bold*",
loserName: "Loser~Strike~",
winnerUsername: "user_one",
loserUsername: "user_two",
},
],
});
const desc = getDescription();
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
expect(desc).toContain("Team\\_\\_Underline");
expect(desc).toContain("user\\_\\_name");
expect(desc).toContain("Winner\\*Bold\\*");
expect(desc).toContain("Loser\\~Strike\\~");
expect(desc).toContain("user\\_one");
expect(desc).toContain("user\\_two");
});
Add opt-in Discord pings to standings notifications (#429) * Add opt-in Discord ping to standings notifications Users who have linked their Discord account can enable a ping preference in Settings > Notifications. When enabled, their bracket username is replaced with a Discord @mention (<@userId>) in league standings update messages, sending them a push notification and showing their Discord display name. Adds discordPingEnabled column to users, a findDiscordIdsByUserIds model helper, allowed_mentions support to the Discord webhook payload, and a NotificationsSection settings component. https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs * Remove Display Name field from user profile settings Username is the only user-facing name field. The display_name column remains in the database since BetterAuth writes the OAuth provider name there, and it serves as a programmatic fallback via getUserDisplayName. https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs * Address code review feedback on Discord ping feature - Fix broken Account settings link in NotificationsSection: replace the non-functional ?section=account href with an onNavigateToAccount callback that drives the parent's useState-based section switcher - Replace hand-rolled toggle button with the project's Switch component (Radix UI) for consistent sizing, accessibility, and dark-mode support - Guard update-discord-ping action: return an error if the user attempts to enable pings without a Discord account linked - Surface action error in NotificationsSection UI - Cap allowed_mentions.users at 100 to avoid Discord rejecting the payload in large leagues - Remove the showWinner alias in scoring-calculator; inline winnerScoreChanged - Add comment to league settings test notification explaining why discordUserId is omitted - Clarify database schema comment: displayName is write-only from BetterAuth https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 10:06:54 -07:00
it("uses Discord mention in standings for opted-in user", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{
teamId: "a",
teamName: "Alpha FC",
username: "christhrowsrocks",
discordUserId: "111222333",
totalPoints: 150,
rank: 1,
},
],
previousStandings: new Map([["a", 125]]),
});
const desc = getDescription();
expect(desc).toContain("Alpha FC (<@111222333>)");
expect(desc).not.toContain("christhrowsrocks");
});
it("uses Discord mention in scored match for opted-in winner", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
scoredMatches: [
{
winnerName: "Real Madrid",
loserName: "Bayern Munich",
winnerUsername: "manager1",
winnerDiscordUserId: "111222333",
loserUsername: "manager2",
},
],
});
const desc = getDescription();
expect(desc).toContain("Real Madrid (<@111222333>)");
expect(desc).not.toContain("manager1");
expect(desc).toContain("Bayern Munich (manager2)");
});
it("sets content and allowed_mentions when opted-in users are present", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{
teamId: "a",
teamName: "Alpha FC",
username: "christhrowsrocks",
discordUserId: "111222333",
totalPoints: 150,
rank: 1,
},
],
previousStandings: new Map([["a", 125]]),
});
const payload = getPayload();
expect(payload.content).toBe("<@111222333>");
expect(payload.allowed_mentions).toEqual({ parse: [], users: ["111222333"] });
});
it("omits content and allowed_mentions when no users opted in", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Alpha FC", username: "christhrowsrocks", totalPoints: 150, rank: 1 },
],
previousStandings: new Map([["a", 125]]),
});
const payload = getPayload();
expect(payload.content).toBeUndefined();
expect(payload.allowed_mentions).toBeUndefined();
});
it("collects Discord IDs from both standings and scored matches", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Alpha", username: "user1", discordUserId: "111", totalPoints: 150, rank: 1 },
],
previousStandings: new Map([["a", 125]]),
scoredMatches: [
{
winnerName: "Real Madrid",
loserName: "Bayern Munich",
winnerUsername: "user2",
winnerDiscordUserId: "222",
loserUsername: "user1",
loserDiscordUserId: "111",
},
],
});
const payload = getPayload();
const ids: string[] = payload.allowed_mentions.users;
expect(ids).toHaveLength(2);
expect(ids).toContain("111");
expect(ids).toContain("222");
});
it("does not show fields on the embed", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
standings: [{ teamId: "a", teamName: "Alpha", totalPoints: 125, rank: 1 }],
previousStandings: new Map([["a", 100]]),
});
const body = JSON.parse((fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].body);
expect(body.embeds[0].fields).toBeUndefined();
});
Refactor standings notifications to show only changed teams (#182) * Refine Discord webhook scoring notifications (fixes #180, #181) - Filter scored matches to only show matchups where a manager scores Brackt points (winner drafted) or has a team eliminated (loser drafted); matches with no manager involvement are suppressed entirely. - Escape Discord markdown characters (_*~`|\) in team names and usernames to prevent formatting issues (e.g. double-underscore names causing unintended underlines). - Replace full standings with a "Standings Changes" section that shows only teams whose points changed, plus any teams whose rank shifted as a result, with ↑N / ↓N indicators for rank movement. - Pass previousRanks map from scoring-calculator to the notification so rank-displaced teams (who didn't score points themselves) are included. - Update test webhook in league settings to demonstrate rank changes and a sample scored match. - Update all discord service tests to cover the new filtering, escaping, and standings-change behaviour. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * fix: only set winnerUsername when winning team's score actually changed Previously, winnerUsername was set for any drafted winner regardless of whether points were actually scored. This caused R64 wins (where the scoring system may not award points until later rounds) to appear in Discord's Scored Matches section even when no Brackt points were earned. Now winnerUsername is only set when the winner's team's totalPoints changed after recalculation. loserUsername (eliminations) is always set when the loser is drafted, since being knocked out is always notable. https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 * Refactor Discord notification logic in scoring calculator - Compute changedTeamIds once up front; derive hasChanges from it instead of duplicating the same iteration - Merge usernameForParticipant and winnerUsernameForParticipant into a single function with a requireScoreChange flag - Replace hasDraftedParticipantMatches with hasScoredMatchesToShow, which checks that at least one scoredMatch has a displayable username — prevents sending a contentless Discord embed when a drafted winner doesn't score any points and the loser isn't drafted - Update inline comment to be sport-agnostic https://claude.ai/code/session_01FciwfdG9Sfr5ZrXUHPeB18 --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-03-19 15:52:57 -07:00
it("omits standings changes section when no teams changed", async () => {
await sendStandingsUpdateNotification({
webhookUrl: WEBHOOK_URL,
seasonName: "My League 2025",
standings: [
{ teamId: "a", teamName: "Alpha", totalPoints: 100, rank: 1 },
{ teamId: "b", teamName: "Beta", totalPoints: 75, rank: 2 },
],
previousStandings: new Map([
["a", 100],
["b", 75],
]),
previousRanks: new Map([
["a", 1],
["b", 2],
]),
});
const desc = getDescription();
expect(desc).not.toContain("**Standings Changes**");
expect(desc).not.toContain("Alpha");
expect(desc).not.toContain("Beta");
});
});