From f952ca62af88b680501d1494b7bbe3e3eeee2d13 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:38:20 +0300 Subject: [PATCH] Handle 1 map only played in tournament without NaN --- .../routes/u.$identifier.seasons.stats.tsx | 25 +++++++++++-------- app/utils/number.ts | 17 +++++++++++++ app/utils/numbers.test.ts | 16 ++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/app/features/user-page/routes/u.$identifier.seasons.stats.tsx b/app/features/user-page/routes/u.$identifier.seasons.stats.tsx index eb4e327c3..c3b0f4f2a 100644 --- a/app/features/user-page/routes/u.$identifier.seasons.stats.tsx +++ b/app/features/user-page/routes/u.$identifier.seasons.stats.tsx @@ -18,7 +18,7 @@ import { modesShort } from "~/modules/in-game-lists/modes"; import { stageIds } from "~/modules/in-game-lists/stage-ids"; import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; import invariant from "~/utils/invariant"; -import { cutToNDecimalPlaces } from "~/utils/number"; +import { cutToNDecimalPlaces, winPercentage } from "~/utils/number"; import type { SendouRouteHandle } from "~/utils/remix.server"; import { loader, @@ -277,12 +277,10 @@ function Players({ return (
{players.map((player) => { - const setWinRate = Math.round( - (player.setWins / (player.setWins + player.setLosses)) * 100, - ); - const mapWinRate = Math.round( - (player.mapWins / (player.mapWins + player.mapLosses)) * 100, - ); + // a player only met on maps of a set someone else's team was fielded + // for has no set record to show a win rate of + const setWinRate = winPercentage(player.setWins, player.setLosses); + const mapWinRate = winPercentage(player.mapWins, player.mapLosses); return (
= 50, - "text-warning": setWinRate < 50, + "text-success": + typeof setWinRate === "number" && setWinRate >= 50, + "text-warning": + typeof setWinRate === "number" && setWinRate < 50, })} > - {setWinRate}% ({mapWinRate}%) + {typeof setWinRate === "number" + ? `${Math.round(setWinRate)}% ` + : null} + {typeof mapWinRate === "number" + ? `(${Math.round(mapWinRate)}%)` + : null}
{player.setWins} ({player.mapWins}) {t("user:seasons.win.short")} diff --git a/app/utils/number.ts b/app/utils/number.ts index a788f80f4..d7073842f 100644 --- a/app/utils/number.ts +++ b/app/utils/number.ts @@ -78,3 +78,20 @@ export function safeNumberParse(value: string | null) { const result = Number(trimmed); return Number.isNaN(result) ? null : result; } + +/** + * Share of games won as a percentage, or `null` when no games were played. + * Callers decide how to round it and what to show in place of a missing rate. + * + * @example + * ```typescript + * winPercentage(3, 1); // returns 75 + * winPercentage(0, 0); // returns null + * ``` + */ +export function winPercentage(wins: number, losses: number) { + const played = wins + losses; + if (played === 0) return null; + + return (wins / played) * 100; +} diff --git a/app/utils/numbers.test.ts b/app/utils/numbers.test.ts index a6da62b6e..91c7847db 100644 --- a/app/utils/numbers.test.ts +++ b/app/utils/numbers.test.ts @@ -4,6 +4,7 @@ import { cutToNDecimalPlaces, roundToNDecimalPlaces, safeNumberParse, + winPercentage, } from "./number"; describe("roundToNDecimalPlaces()", () => { @@ -75,3 +76,18 @@ describe("safeNumberParse()", () => { expect(safeNumberParse(input)).toBe(expected); }); }); + +describe("winPercentage()", () => { + test.each([ + [3, 1, 75], + [1, 1, 50], + [0, 4, 0], + [4, 0, 100], + ])("%d wins and %d losses is %d%%", (wins, losses, expected) => { + expect(winPercentage(wins, losses)).toBe(expected); + }); + + test("returns null when no games were played", () => { + expect(winPercentage(0, 0)).toBeNull(); + }); +});