From bac837d823e742adbb2fddf5d2952a4f221a90b7 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Mon, 19 Feb 2024 14:46:02 +0100 Subject: [PATCH] fix: use selected typing speed unit on personal best popup (fehmer) (#5070) * fix: Use selected typing speed unit on personal best popup * refactor * refactor * test coverage * use Format in more places * Make config mockable * dependency injection * wip * fix * test * touch --- frontend/__tests__/utils/format.spec.ts | 201 ++++++++++++++++++++ frontend/src/ts/account/pb-tables.ts | 70 ++----- frontend/src/ts/elements/leaderboards.ts | 41 ++-- frontend/src/ts/elements/modes-notice.ts | 42 ++-- frontend/src/ts/pages/account.ts | 162 +++------------- frontend/src/ts/popups/pb-tables-popup.ts | 15 +- frontend/src/ts/test/live-burst.ts | 9 +- frontend/src/ts/test/live-wpm.ts | 10 +- frontend/src/ts/test/result.ts | 82 ++++---- frontend/src/ts/test/test-ui.ts | 6 +- frontend/src/ts/types/types.d.ts | 1 - frontend/src/ts/utils/format.ts | 67 +++++++ frontend/src/ts/utils/typing-speed-units.ts | 10 - frontend/static/html/popups.html | 2 +- 14 files changed, 416 insertions(+), 302 deletions(-) create mode 100644 frontend/__tests__/utils/format.spec.ts create mode 100644 frontend/src/ts/utils/format.ts diff --git a/frontend/__tests__/utils/format.spec.ts b/frontend/__tests__/utils/format.spec.ts new file mode 100644 index 000000000000..b7ebdd5cdbb5 --- /dev/null +++ b/frontend/__tests__/utils/format.spec.ts @@ -0,0 +1,201 @@ +import { Formatting } from "../../src/ts/utils/format"; +import * as MockConfig from "../../src/ts/config"; +import DefaultConfig from "../../src/ts/constants/default-config"; + +describe("format.ts", () => { + afterEach(() => { + jest.resetAllMocks(); + }); + describe("typingsSpeed", () => { + it("should format with typing speed and decimalPlaces from configuration", () => { + //wpm, no decimals + const wpmNoDecimals = getInstance({ + typingSpeedUnit: "wpm", + alwaysShowDecimalPlaces: false, + }); + expect(wpmNoDecimals.typingSpeed(12.5)).toEqual("13"); + expect(wpmNoDecimals.typingSpeed(0)).toEqual("0"); + + //cpm, no decimals + const cpmNoDecimals = getInstance({ + typingSpeedUnit: "cpm", + alwaysShowDecimalPlaces: false, + }); + expect(cpmNoDecimals.typingSpeed(12.5)).toEqual("63"); + expect(cpmNoDecimals.typingSpeed(0)).toEqual("0"); + + //wpm, with decimals + const wpmWithDecimals = getInstance({ + typingSpeedUnit: "wpm", + alwaysShowDecimalPlaces: true, + }); + expect(wpmWithDecimals.typingSpeed(12.5)).toEqual("12.50"); + expect(wpmWithDecimals.typingSpeed(0)).toEqual("0.00"); + + //cpm, with decimals + const cpmWithDecimals = getInstance({ + typingSpeedUnit: "cpm", + alwaysShowDecimalPlaces: true, + }); + expect(cpmWithDecimals.typingSpeed(12.5)).toEqual("62.50"); + expect(cpmWithDecimals.typingSpeed(0)).toEqual("0.00"); + }); + + it("should format with fallback", () => { + //default fallback + const format = getInstance(); + expect(format.typingSpeed(null)).toEqual("-"); + expect(format.typingSpeed(undefined)).toEqual("-"); + + //provided fallback + expect(format.typingSpeed(null, { fallback: "none" })).toEqual("none"); + expect(format.typingSpeed(null, { fallback: "" })).toEqual(""); + expect(format.typingSpeed(undefined, { fallback: "none" })).toEqual( + "none" + ); + + expect(format.typingSpeed(undefined, { fallback: "" })).toEqual(""); + expect(format.typingSpeed(undefined, { fallback: undefined })).toEqual( + "" + ); + }); + + it("should format with decimals", () => { + //force with decimals + const wpmNoDecimals = getInstance({ + typingSpeedUnit: "wpm", + alwaysShowDecimalPlaces: false, + }); + expect( + wpmNoDecimals.typingSpeed(100, { showDecimalPlaces: true }) + ).toEqual("100.00"); + //force without decimals + const wpmWithDecimals = getInstance({ + typingSpeedUnit: "wpm", + alwaysShowDecimalPlaces: true, + }); + expect( + wpmWithDecimals.typingSpeed(100, { showDecimalPlaces: false }) + ).toEqual("100"); + }); + + it("should format with suffix", () => { + const format = getInstance({ + typingSpeedUnit: "wpm", + alwaysShowDecimalPlaces: false, + }); + expect(format.typingSpeed(100, { suffix: " raw" })).toEqual("100 raw"); + expect(format.typingSpeed(100, { suffix: undefined })).toEqual("100"); + expect(format.typingSpeed(0, { suffix: " raw" })).toEqual("0 raw"); + expect(format.typingSpeed(null, { suffix: " raw" })).toEqual("-"); + expect(format.typingSpeed(undefined, { suffix: " raw" })).toEqual("-"); + }); + }); + describe("percentage", () => { + it("should format with decimalPlaces from configuration", () => { + //no decimals + const noDecimals = getInstance({ alwaysShowDecimalPlaces: false }); + expect(noDecimals.percentage(12.5)).toEqual("13%"); + expect(noDecimals.percentage(0)).toEqual("0%"); + + //with decimals + const withDecimals = getInstance({ alwaysShowDecimalPlaces: true }); + expect(withDecimals.percentage(12.5)).toEqual("12.50%"); + expect(withDecimals.percentage(0)).toEqual("0.00%"); + }); + + it("should format with fallback", () => { + //default fallback + const format = getInstance(); + expect(format.percentage(null)).toEqual("-"); + expect(format.percentage(undefined)).toEqual("-"); + + //provided fallback + expect(format.percentage(null, { fallback: "none" })).toEqual("none"); + expect(format.percentage(null, { fallback: "" })).toEqual(""); + expect(format.percentage(undefined, { fallback: "none" })).toEqual( + "none" + ); + + expect(format.percentage(undefined, { fallback: "" })).toEqual(""); + expect(format.percentage(undefined, { fallback: undefined })).toEqual(""); + }); + + it("should format with decimals", () => { + //force with decimals + const noDecimals = getInstance({ alwaysShowDecimalPlaces: false }); + expect(noDecimals.percentage(100, { showDecimalPlaces: true })).toEqual( + "100.00%" + ); + //force without decimals + const withDecimals = getInstance({ alwaysShowDecimalPlaces: true }); + expect( + withDecimals.percentage(100, { showDecimalPlaces: false }) + ).toEqual("100%"); + }); + + it("should format with suffix", () => { + const format = getInstance({ alwaysShowDecimalPlaces: false }); + expect(format.percentage(100, { suffix: " raw" })).toEqual("100% raw"); + expect(format.percentage(100, { suffix: undefined })).toEqual("100%"); + expect(format.percentage(0, { suffix: " raw" })).toEqual("0% raw"); + expect(format.percentage(null, { suffix: " raw" })).toEqual("-"); + expect(format.percentage(undefined, { suffix: " raw" })).toEqual("-"); + }); + }); + describe("decimals", () => { + it("should format with decimalPlaces from configuration", () => { + //no decimals + const noDecimals = getInstance({ alwaysShowDecimalPlaces: false }); + expect(noDecimals.decimals(12.5)).toEqual("13"); + expect(noDecimals.decimals(0)).toEqual("0"); + + //with decimals + const withDecimals = getInstance({ alwaysShowDecimalPlaces: true }); + expect(withDecimals.decimals(12.5)).toEqual("12.50"); + expect(withDecimals.decimals(0)).toEqual("0.00"); + }); + + it("should format with fallback", () => { + //default fallback + const format = getInstance(); + expect(format.decimals(null)).toEqual("-"); + expect(format.decimals(undefined)).toEqual("-"); + + //provided fallback + expect(format.decimals(null, { fallback: "none" })).toEqual("none"); + expect(format.decimals(null, { fallback: "" })).toEqual(""); + expect(format.decimals(undefined, { fallback: "none" })).toEqual("none"); + + expect(format.decimals(undefined, { fallback: "" })).toEqual(""); + expect(format.decimals(undefined, { fallback: undefined })).toEqual(""); + }); + + it("should format with decimals", () => { + //force with decimals + const noDecimals = getInstance({ alwaysShowDecimalPlaces: false }); + expect(noDecimals.decimals(100, { showDecimalPlaces: true })).toEqual( + "100.00" + ); + //force without decimals + const withDecimals = getInstance({ alwaysShowDecimalPlaces: true }); + expect(withDecimals.decimals(100, { showDecimalPlaces: false })).toEqual( + "100" + ); + }); + + it("should format with suffix", () => { + const format = getInstance({ alwaysShowDecimalPlaces: false }); + expect(format.decimals(100, { suffix: " raw" })).toEqual("100 raw"); + expect(format.decimals(100, { suffix: undefined })).toEqual("100"); + expect(format.decimals(0, { suffix: " raw" })).toEqual("0 raw"); + expect(format.decimals(null, { suffix: " raw" })).toEqual("-"); + expect(format.decimals(undefined, { suffix: " raw" })).toEqual("-"); + }); + }); +}); + +function getInstance(config?: Partial): Formatting { + const target: SharedTypes.Config = { ...DefaultConfig, ...config }; + return new Formatting(target); +} diff --git a/frontend/src/ts/account/pb-tables.ts b/frontend/src/ts/account/pb-tables.ts index 4fd9e6f328ec..4f1bcf0d67fd 100644 --- a/frontend/src/ts/account/pb-tables.ts +++ b/frontend/src/ts/account/pb-tables.ts @@ -1,7 +1,6 @@ import Config from "../config"; -import format from "date-fns/format"; -import * as Misc from "../utils/misc"; -import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; +import dateFormat from "date-fns/format"; +import Format from "../utils/format"; function clearTables(isProfile: boolean): void { const source = isProfile ? "Profile" : "Account"; @@ -140,7 +139,6 @@ function buildPbHtml( let dateText = ""; const modeString = `${mode2} ${mode === "time" ? "seconds" : "words"}`; const speedUnit = Config.typingSpeedUnit; - const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); try { const pbData = (pbs[mode][mode2] ?? []).sort((a, b) => b.wpm - a.wpm)[0]; @@ -148,62 +146,28 @@ function buildPbHtml( const date = new Date(pbData.timestamp); if (pbData.timestamp) { - dateText = format(date, "dd MMM yyyy"); + dateText = dateFormat(date, "dd MMM yyyy"); } - let speedString: number | string = typingSpeedUnit.fromWpm(pbData.wpm); - if (Config.alwaysShowDecimalPlaces) { - speedString = Misc.roundTo2(speedString).toFixed(2); - } else { - speedString = Math.round(speedString); - } - speedString += ` ${speedUnit}`; - - let rawString: number | string = typingSpeedUnit.fromWpm(pbData.raw); - if (Config.alwaysShowDecimalPlaces) { - rawString = Misc.roundTo2(rawString).toFixed(2); - } else { - rawString = Math.round(rawString); - } - rawString += ` raw`; - - let accString: number | string = pbData.acc; - if (accString === undefined) { - accString = "-"; - } else { - if (Config.alwaysShowDecimalPlaces) { - accString = Misc.roundTo2(accString).toFixed(2); - } else { - accString = Math.floor(accString); - } - } - accString += ` acc`; - - let conString: number | string = pbData.consistency; - if (conString === undefined) { - conString = "-"; - } else { - if (Config.alwaysShowDecimalPlaces) { - conString = Misc.roundTo2(conString).toFixed(2); - } else { - conString = Math.round(conString); - } - } - conString += ` con`; - retval = `
${modeString}
-
${Math.round(typingSpeedUnit.fromWpm(pbData.wpm))}
-
${ - pbData.acc === undefined ? "-" : Math.floor(pbData.acc) + "%" - }
+
${Format.typingSpeed(pbData.wpm, { + showDecimalPlaces: false, + })}
+
${Format.percentage(pbData.acc, { + showDecimalPlaces: false, + })}
${modeString}
-
${speedString}
-
${rawString}
-
${accString}
-
${conString}
+
${Format.typingSpeed(pbData.wpm, { + suffix: ` ${speedUnit}`, + })}
+
${Format.typingSpeed(pbData.raw, { suffix: " raw" })}
+
${Format.percentage(pbData.acc, { suffix: " acc" })}
+
${Format.percentage(pbData.consistency, { + suffix: " con", + })}
${dateText}
`; } catch (e) { diff --git a/frontend/src/ts/elements/leaderboards.ts b/frontend/src/ts/elements/leaderboards.ts index d261b81abeee..ab5de4744958 100644 --- a/frontend/src/ts/elements/leaderboards.ts +++ b/frontend/src/ts/elements/leaderboards.ts @@ -2,7 +2,6 @@ import Ape from "../ape"; import * as DB from "../db"; import Config from "../config"; import * as Misc from "../utils/misc"; -import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; import * as Notifications from "./notifications"; import format from "date-fns/format"; import { isAuthenticated } from "../firebase"; @@ -11,6 +10,7 @@ import { getHTMLById as getBadgeHTMLbyId } from "../controllers/badge-controller import * as ConnectionState from "../states/connection"; import * as Skeleton from "../popups/skeleton"; import { debounce } from "throttle-debounce"; +import Format from "../utils/format"; import SlimSelect from "slim-select"; const wrapperId = "leaderboardsWrapper"; @@ -165,7 +165,6 @@ function updateFooter(lb: LbKey): void { return; } - const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); if (DB.getSnapshot()?.lbOptOut === true) { $(`#leaderboardsWrapper table.${side} tfoot`).html(` @@ -211,12 +210,18 @@ function updateFooter(lb: LbKey): void { ${lbRank.rank} You${toppercent ? toppercent : ""} - ${typingSpeedUnit.fromWpm(entry.wpm).toFixed(2)}
-
${entry.acc.toFixed(2)}%
- ${typingSpeedUnit.fromWpm(entry.raw).toFixed(2)}
-
${ - entry.consistency === undefined ? "-" : entry.consistency.toFixed(2) + "%" - }
+ ${Format.typingSpeed(entry.wpm, { + showDecimalPlaces: true, + })}
+
${Format.percentage(entry.acc, { + showDecimalPlaces: true, + })}%
+ ${Format.typingSpeed(entry.raw, { + showDecimalPlaces: true, + })}
+
${Format.percentage(entry.consistency, { + showDecimalPlaces: true, + })}
${format(date, "dd MMM yyyy")}
${format(date, "HH:mm")}
@@ -296,8 +301,6 @@ async function fillTable(lb: LbKey): Promise { "No results found" ); } - - const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); const loggedInUserName = DB.getSnapshot()?.name; let html = ""; @@ -336,12 +339,18 @@ async function fillTable(lb: LbKey): Promise { ${entry.badgeId ? getBadgeHTMLbyId(entry.badgeId) : ""} - ${typingSpeedUnit.fromWpm(entry.wpm).toFixed(2)}
-
${entry.acc.toFixed(2)}%
- ${typingSpeedUnit.fromWpm(entry.raw).toFixed(2)}
-
${ - entry.consistency === undefined ? "-" : entry.consistency.toFixed(2) + "%" - }
+ ${Format.typingSpeed(entry.wpm, { + showDecimalPlaces: true, + })}
+
${Format.percentage(entry.acc, { + showDecimalPlaces: true, + })}
+ ${Format.typingSpeed(entry.raw, { + showDecimalPlaces: true, + })}
+
${Format.percentage(entry.consistency, { + showDecimalPlaces: true, + })}
${format(date, "dd MMM yyyy")}
${format(date, "HH:mm")}
diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts index d813172e6c9c..75fb6bcdf374 100644 --- a/frontend/src/ts/elements/modes-notice.ts +++ b/frontend/src/ts/elements/modes-notice.ts @@ -7,8 +7,8 @@ import * as TestWords from "../test/test-words"; import * as ConfigEvent from "../observables/config-event"; import { isAuthenticated } from "../firebase"; import * as CustomTextState from "../states/custom-text-name"; -import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; -import { getLanguageDisplayString, roundTo2 } from "../utils/misc"; +import { getLanguageDisplayString } from "../utils/misc"; +import Format from "../utils/format"; ConfigEvent.subscribe((eventKey) => { if ( @@ -123,14 +123,11 @@ export async function update(): Promise { Config.paceCaret !== "off" || (Config.repeatedPace && TestState.isPaceRepeat) ) { - let speed = ""; - try { - speed = ` (${roundTo2( - getTypingSpeedUnit(Config.typingSpeedUnit).fromWpm( - PaceCaret.settings?.wpm ?? 0 - ) - )} ${Config.typingSpeedUnit})`; - } catch {} + const speed = Format.typingSpeed(PaceCaret.settings?.wpm ?? 0, { + showDecimalPlaces: true, + suffix: ` ${Config.typingSpeedUnit}`, + }); + $(".pageTest #testModesNotice").append( `
${ Config.paceCaret === "average" @@ -142,7 +139,7 @@ export async function update(): Promise { : Config.paceCaret === "daily" ? "daily" : "custom" - } pace${speed}
` + } pace ${speed}` ); } @@ -157,14 +154,11 @@ export async function update(): Promise { if (isAuthenticated() && avgWPM > 0) { const avgWPMText = ["speed", "both"].includes(Config.showAverage) - ? getTypingSpeedUnit(Config.typingSpeedUnit).convertWithUnitSuffix( - avgWPM, - Config.alwaysShowDecimalPlaces - ) + ? Format.typingSpeed(avgWPM, { suffix: ` ${Config.typingSpeedUnit}` }) : ""; const avgAccText = ["acc", "both"].includes(Config.showAverage) - ? `${avgAcc}% acc` + ? Format.percentage(avgAcc, { suffix: " acc" }) : ""; const text = `${avgWPMText} ${avgAccText}`.trim(); @@ -177,11 +171,10 @@ export async function update(): Promise { if (Config.minWpm !== "off") { $(".pageTest #testModesNotice").append( - `
min ${roundTo2( - getTypingSpeedUnit(Config.typingSpeedUnit).fromWpm( - Config.minWpmCustomSpeed - ) - )} ${Config.typingSpeedUnit}
` + `
min ${Format.typingSpeed( + Config.minWpmCustomSpeed, + { showDecimalPlaces: true, suffix: ` ${Config.typingSpeedUnit}` } + )}
` ); } @@ -193,10 +186,9 @@ export async function update(): Promise { if (Config.minBurst !== "off") { $(".pageTest #testModesNotice").append( - `
min ${roundTo2( - getTypingSpeedUnit(Config.typingSpeedUnit).fromWpm( - Config.minBurstCustomSpeed - ) + `
min ${Format.typingSpeed( + Config.minBurstCustomSpeed, + { showDecimalPlaces: true } )} ${Config.typingSpeedUnit} burst ${ Config.minBurst === "flex" ? "(flex)" : "" }
` diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index aed4c4e14bfe..f524bfb2e9c7 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -23,6 +23,7 @@ import * as ActivePage from "../states/active-page"; import { Auth } from "../firebase"; import * as Loader from "../elements/loader"; import * as ResultBatches from "../elements/result-batches"; +import Format from "../utils/format"; let filterDebug = false; //toggle filterdebug @@ -37,7 +38,6 @@ let filteredResults: SharedTypes.Result[] = []; let visibleTableLines = 0; function loadMoreLines(lineIndex?: number): void { - const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); if (filteredResults === undefined || filteredResults.length === 0) return; let newVisibleLines; if (lineIndex && lineIndex > visibleTableLines) { @@ -53,16 +53,6 @@ function loadMoreLines(lineIndex?: number): void { diff = "normal"; } - let raw; - try { - raw = typingSpeedUnit.fromWpm(result.rawWpm).toFixed(2); - if (raw === undefined) { - raw = "-"; - } - } catch (e) { - raw = "-"; - } - let icons = ` ${pb} - ${typingSpeedUnit.fromWpm(result.wpm).toFixed(2)} - ${raw} - ${result.acc.toFixed(2)}% - ${consistency} + ${Format.typingSpeed(result.wpm, { showDecimalPlaces: true })} + ${Format.typingSpeed(result.rawWpm, { showDecimalPlaces: true })} + ${Format.percentage(result.acc, { showDecimalPlaces: true })} + ${Format.percentage(result.consistency, { + showDecimalPlaces: true, + })} ${charStats} ${result.mode} ${result.mode2} ${icons} @@ -860,144 +846,58 @@ async function fillContent(): Promise { Misc.secondsToString(Math.round(totalSecondsFiltered), true, true) ); - let highestSpeed: number | string = typingSpeedUnit.fromWpm(topWpm); - - if (Config.alwaysShowDecimalPlaces) { - highestSpeed = Misc.roundTo2(highestSpeed).toFixed(2); - } else { - highestSpeed = Math.round(highestSpeed); - } - const speedUnit = Config.typingSpeedUnit; $(".pageAccount .highestWpm .title").text(`highest ${speedUnit}`); - $(".pageAccount .highestWpm .val").text(highestSpeed); - - let averageSpeed: number | string = typingSpeedUnit.fromWpm(totalWpm); - if (Config.alwaysShowDecimalPlaces) { - averageSpeed = Misc.roundTo2(averageSpeed / testCount).toFixed(2); - } else { - averageSpeed = Math.round(averageSpeed / testCount); - } + $(".pageAccount .highestWpm .val").text(Format.typingSpeed(topWpm)); $(".pageAccount .averageWpm .title").text(`average ${speedUnit}`); - $(".pageAccount .averageWpm .val").text(averageSpeed); - - let averageSpeedLast10: number | string = - typingSpeedUnit.fromWpm(wpmLast10total); - if (Config.alwaysShowDecimalPlaces) { - averageSpeedLast10 = Misc.roundTo2(averageSpeedLast10 / last10).toFixed(2); - } else { - averageSpeedLast10 = Math.round(averageSpeedLast10 / last10); - } + $(".pageAccount .averageWpm .val").text( + Format.typingSpeed(totalWpm / testCount) + ); $(".pageAccount .averageWpm10 .title").text( `average ${speedUnit} (last 10 tests)` ); - $(".pageAccount .averageWpm10 .val").text(averageSpeedLast10); - - let highestRawSpeed: number | string = typingSpeedUnit.fromWpm(rawWpm.max); - if (Config.alwaysShowDecimalPlaces) { - highestRawSpeed = Misc.roundTo2(highestRawSpeed).toFixed(2); - } else { - highestRawSpeed = Math.round(highestRawSpeed); - } + $(".pageAccount .averageWpm10 .val").text( + Format.typingSpeed(wpmLast10total / last10) + ); $(".pageAccount .highestRaw .title").text(`highest raw ${speedUnit}`); - $(".pageAccount .highestRaw .val").text(highestRawSpeed); - - let averageRawSpeed: number | string = typingSpeedUnit.fromWpm(rawWpm.total); - if (Config.alwaysShowDecimalPlaces) { - averageRawSpeed = Misc.roundTo2(averageRawSpeed / rawWpm.count).toFixed(2); - } else { - averageRawSpeed = Math.round(averageRawSpeed / rawWpm.count); - } + $(".pageAccount .highestRaw .val").text(Format.typingSpeed(rawWpm.max)); $(".pageAccount .averageRaw .title").text(`average raw ${speedUnit}`); - $(".pageAccount .averageRaw .val").text(averageRawSpeed); - - let averageRawSpeedLast10: number | string = typingSpeedUnit.fromWpm( - rawWpm.last10Total + $(".pageAccount .averageRaw .val").text( + Format.typingSpeed(rawWpm.total / rawWpm.count) ); - if (Config.alwaysShowDecimalPlaces) { - averageRawSpeedLast10 = Misc.roundTo2( - averageRawSpeedLast10 / rawWpm.last10Count - ).toFixed(2); - } else { - averageRawSpeedLast10 = Math.round( - averageRawSpeedLast10 / rawWpm.last10Count - ); - } $(".pageAccount .averageRaw10 .title").text( `average raw ${speedUnit} (last 10 tests)` ); - $(".pageAccount .averageRaw10 .val").text(averageRawSpeedLast10); + $(".pageAccount .averageRaw10 .val").text( + Format.typingSpeed(rawWpm.last10Total / rawWpm.last10Count) + ); $(".pageAccount .highestWpm .mode").html(topMode); $(".pageAccount .testsTaken .val").text(testCount); - let highestAcc: string | number = topAcc; - if (Config.alwaysShowDecimalPlaces) { - highestAcc = Misc.roundTo2(highestAcc).toFixed(2); - } else { - highestAcc = Math.floor(highestAcc); - } - - $(".pageAccount .highestAcc .val").text(highestAcc + "%"); - - let averageAcc: number | string = totalAcc; - if (Config.alwaysShowDecimalPlaces) { - averageAcc = Misc.roundTo2(averageAcc / testCount); - } else { - averageAcc = Math.floor(averageAcc / testCount); - } - - $(".pageAccount .avgAcc .val").text(averageAcc + "%"); - - let averageAccLast10: number | string = totalAcc10; - if (Config.alwaysShowDecimalPlaces) { - averageAccLast10 = Misc.roundTo2(averageAccLast10 / last10); - } else { - averageAccLast10 = Math.floor(averageAccLast10 / last10); - } - - $(".pageAccount .avgAcc10 .val").text(averageAccLast10 + "%"); + $(".pageAccount .highestAcc .val").text(Format.percentage(topAcc)); + $(".pageAccount .avgAcc .val").text(Format.percentage(totalAcc / testCount)); + $(".pageAccount .avgAcc10 .val").text(Format.percentage(totalAcc10 / last10)); if (totalCons === 0 || totalCons === undefined) { $(".pageAccount .avgCons .val").text("-"); $(".pageAccount .avgCons10 .val").text("-"); } else { - let highestCons: number | string = topCons; - if (Config.alwaysShowDecimalPlaces) { - highestCons = Misc.roundTo2(highestCons).toFixed(2); - } else { - highestCons = Math.round(highestCons); - } - - $(".pageAccount .highestCons .val").text(highestCons + "%"); - - let averageCons: number | string = totalCons; - if (Config.alwaysShowDecimalPlaces) { - averageCons = Misc.roundTo2(averageCons / consCount).toFixed(2); - } else { - averageCons = Math.round(averageCons / consCount); - } + $(".pageAccount .highestCons .val").text(Format.percentage(topCons)); - $(".pageAccount .avgCons .val").text(averageCons + "%"); - - let averageConsLast10: number | string = totalCons10; - if (Config.alwaysShowDecimalPlaces) { - averageConsLast10 = Misc.roundTo2( - averageConsLast10 / Math.min(last10, consCount) - ).toFixed(2); - } else { - averageConsLast10 = Math.round( - averageConsLast10 / Math.min(last10, consCount) - ); - } + $(".pageAccount .avgCons .val").text( + Format.percentage(totalCons / consCount) + ); - $(".pageAccount .avgCons10 .val").text(averageConsLast10 + "%"); + $(".pageAccount .avgCons10 .val").text( + Format.percentage(totalCons10 / Math.min(last10, consCount)) + ); } $(".pageAccount .testsStarted .val").text(`${testCount + testRestarts}`); @@ -1020,7 +920,7 @@ async function fillContent(): Promise { const plus = wpmChangePerHour > 0 ? "+" : ""; $(".pageAccount .group.chart .below .text").text( `Speed change per hour spent typing: ${ - plus + Misc.roundTo2(typingSpeedUnit.fromWpm(wpmChangePerHour)) + plus + Format.typingSpeed(wpmChangePerHour, { showDecimalPlaces: true }) } ${Config.typingSpeedUnit}` ); } diff --git a/frontend/src/ts/popups/pb-tables-popup.ts b/frontend/src/ts/popups/pb-tables-popup.ts index ae48e9574f12..c85e1baa915c 100644 --- a/frontend/src/ts/popups/pb-tables-popup.ts +++ b/frontend/src/ts/popups/pb-tables-popup.ts @@ -2,6 +2,8 @@ import * as DB from "../db"; import format from "date-fns/format"; import * as Skeleton from "./skeleton"; import { getLanguageDisplayString, isPopupVisible } from "../utils/misc"; +import Config from "../config"; +import Format from "../utils/format"; type PersonalBest = { mode2: SharedTypes.Config.Mode2; @@ -12,6 +14,9 @@ const wrapperId = "pbTablesPopupWrapper"; function update(mode: SharedTypes.Config.Mode): void { $("#pbTablesPopup table tbody").empty(); $($("#pbTablesPopup table thead tr td")[0] as HTMLElement).text(mode); + $($("#pbTablesPopup table thead tr td span.unit")[0] as HTMLElement).text( + Config.typingSpeedUnit + ); const snapshot = DB.getSnapshot(); if (!snapshot) return; @@ -56,16 +61,14 @@ function update(mode: SharedTypes.Config.Mode): void { ${mode2memory === pb.mode2 ? "" : pb.mode2} - ${pb.wpm.toFixed(2)} + ${Format.typingSpeed(pb.wpm)}
- ${pb.acc ? pb.acc + "%" : "-"} + ${Format.percentage(pb.acc)} - ${pb.raw ? pb.raw : "-"} + ${Format.typingSpeed(pb.raw)}
- ${ - pb.consistency ? pb.consistency + "%" : "-" - } + ${Format.percentage(pb.consistency)} ${pb.difficulty} ${pb.language ? getLanguageDisplayString(pb.language) : "-"} diff --git a/frontend/src/ts/test/live-burst.ts b/frontend/src/ts/test/live-burst.ts index 5fb88c28b449..0387e7e48cbb 100644 --- a/frontend/src/ts/test/live-burst.ts +++ b/frontend/src/ts/test/live-burst.ts @@ -1,15 +1,14 @@ import Config from "../config"; import * as TestState from "../test/test-state"; import * as ConfigEvent from "../observables/config-event"; -import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; +import Format from "../utils/format"; export async function update(burst: number): Promise { if (!Config.showLiveBurst) return; - burst = Math.round(getTypingSpeedUnit(Config.typingSpeedUnit).fromWpm(burst)); + const burstText = Format.typingSpeed(burst, { showDecimalPlaces: false }); (document.querySelector("#miniTimerAndLiveWpm .burst") as Element).innerHTML = - burst.toString(); - (document.querySelector("#liveBurst") as Element).innerHTML = - burst.toString(); + burstText; + (document.querySelector("#liveBurst") as Element).innerHTML = burstText; } let state = false; diff --git a/frontend/src/ts/test/live-wpm.ts b/frontend/src/ts/test/live-wpm.ts index e36acb11a9d8..a68106e64a84 100644 --- a/frontend/src/ts/test/live-wpm.ts +++ b/frontend/src/ts/test/live-wpm.ts @@ -1,7 +1,7 @@ import Config from "../config"; import * as TestState from "../test/test-state"; import * as ConfigEvent from "../observables/config-event"; -import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; +import Format from "../utils/format"; const liveWpmElement = document.querySelector("#liveWpm") as Element; const miniLiveWpmElement = document.querySelector( @@ -13,11 +13,9 @@ export function update(wpm: number, raw: number): void { if (Config.blindMode) { number = raw; } - number = Math.round( - getTypingSpeedUnit(Config.typingSpeedUnit).fromWpm(number) - ); - miniLiveWpmElement.innerHTML = number.toString(); - liveWpmElement.innerHTML = number.toString(); + const numberText = Format.typingSpeed(number, { showDecimalPlaces: false }); + miniLiveWpmElement.innerHTML = numberText; + liveWpmElement.innerHTML = numberText; } let state = false; diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index cc6906164b42..0fe10e2970f1 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -1,3 +1,4 @@ +//TODO: use Format import { Chart, type PluginChartOptions } from "chart.js"; import Config from "../config"; import * as AdController from "../controllers/ad-controller"; @@ -25,6 +26,7 @@ import * as Focus from "./focus"; import * as CustomText from "./custom-text"; import * as CustomTextState from "./../states/custom-text-name"; import * as Funbox from "./funbox/funbox"; +import Format from "../utils/format"; import confetti from "canvas-confetti"; import type { AnnotationOptions } from "chartjs-plugin-annotation"; @@ -213,24 +215,23 @@ export async function updateGraphPBLine(): Promise { function updateWpmAndAcc(): void { let inf = false; - const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); if (result.wpm >= 1000) { inf = true; } - if (Config.alwaysShowDecimalPlaces) { - $("#result .stats .wpm .top .text").text(Config.typingSpeedUnit); - if (inf) { - $("#result .stats .wpm .bottom").text("Infinite"); - } else { - $("#result .stats .wpm .bottom").text( - Misc.roundTo2(typingSpeedUnit.fromWpm(result.wpm)).toFixed(2) - ); - } - $("#result .stats .raw .bottom").text( - Misc.roundTo2(typingSpeedUnit.fromWpm(result.rawWpm)).toFixed(2) - ); + $("#result .stats .wpm .top .text").text(Config.typingSpeedUnit); + if (inf) { + $("#result .stats .wpm .bottom").text("Infinite"); + } else { + $("#result .stats .wpm .bottom").text(Format.typingSpeed(result.wpm)); + } + $("#result .stats .raw .bottom").text(Format.typingSpeed(result.rawWpm)); + $("#result .stats .acc .bottom").text( + result.acc === 100 ? "100%" : Format.percentage(result.acc) + ); + + if (Config.alwaysShowDecimalPlaces) { if (Config.typingSpeedUnit != "wpm") { $("#result .stats .wpm .bottom").attr( "aria-label", @@ -245,9 +246,6 @@ function updateWpmAndAcc(): void { $("#result .stats .raw .bottom").removeAttr("aria-label"); } - $("#result .stats .acc .bottom").text( - result.acc === 100 ? "100%" : Misc.roundTo2(result.acc).toFixed(2) + "%" - ); let time = Misc.roundTo2(result.testDuration).toFixed(2) + "s"; if (result.testDuration > 61) { time = Misc.secondsToString(Misc.roundTo2(result.testDuration)); @@ -261,53 +259,47 @@ function updateWpmAndAcc(): void { ); } else { //not showing decimal places - let wpmHover = typingSpeedUnit.convertWithUnitSuffix(result.wpm, true); - let rawWpmHover = typingSpeedUnit.convertWithUnitSuffix( - result.rawWpm, - true - ); + const decimalsAndSuffix = { + showDecimalPlaces: true, + suffix: ` ${Config.typingSpeedUnit}`, + }; + let wpmHover = Format.typingSpeed(result.wpm, decimalsAndSuffix); + let rawWpmHover = Format.typingSpeed(result.rawWpm, decimalsAndSuffix); + if (Config.typingSpeedUnit != "wpm") { wpmHover += " (" + result.wpm.toFixed(2) + " wpm)"; rawWpmHover += " (" + result.rawWpm.toFixed(2) + " wpm)"; } - $("#result .stats .wpm .top .text").text(Config.typingSpeedUnit); $("#result .stats .wpm .bottom").attr("aria-label", wpmHover); - if (inf) { - $("#result .stats .wpm .bottom").text("Infinite"); - } else { - $("#result .stats .wpm .bottom").text( - Math.round(typingSpeedUnit.fromWpm(result.wpm)) - ); - } - $("#result .stats .raw .bottom").text( - Math.round(typingSpeedUnit.fromWpm(result.rawWpm)) - ); $("#result .stats .raw .bottom").attr("aria-label", rawWpmHover); - $("#result .stats .acc .bottom").text(Math.floor(result.acc) + "%"); $("#result .stats .acc .bottom").attr( "aria-label", - `${result.acc === 100 ? "100" : Misc.roundTo2(result.acc).toFixed(2)}% (${ - TestInput.accuracy.correct - } correct / ${TestInput.accuracy.incorrect} incorrect)` + `${ + result.acc === 100 + ? "100" + : Format.percentage(result.acc, { showDecimalPlaces: true }) + } (${TestInput.accuracy.correct} correct / ${ + TestInput.accuracy.incorrect + } incorrect)` ); } } function updateConsistency(): void { + $("#result .stats .consistency .bottom").text( + Format.percentage(result.consistency) + ); if (Config.alwaysShowDecimalPlaces) { - $("#result .stats .consistency .bottom").text( - Misc.roundTo2(result.consistency).toFixed(2) + "%" - ); $("#result .stats .consistency .bottom").attr( "aria-label", - `${result.keyConsistency.toFixed(2)}% key` + Format.percentage(result.keyConsistency, { + showDecimalPlaces: true, + suffix: " key", + }) ); } else { - $("#result .stats .consistency .bottom").text( - Math.round(result.consistency) + "%" - ); $("#result .stats .consistency .bottom").attr( "aria-label", `${result.consistency}% (${result.keyConsistency}% key)` @@ -327,6 +319,7 @@ function updateTime(): void { "aria-label", `${result.afkDuration}s afk ${afkSecondsPercent}%` ); + if (Config.alwaysShowDecimalPlaces) { let time = Misc.roundTo2(result.testDuration).toFixed(2) + "s"; if (result.testDuration > 61) { @@ -416,11 +409,10 @@ export async function updateCrown(): Promise { Config.lazyMode, Config.funbox ); - const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit); pbDiff = Math.abs(result.wpm - lpb); $("#result .stats .wpm .crown").attr( "aria-label", - "+" + Misc.roundTo2(typingSpeedUnit.fromWpm(pbDiff)) + "+" + Format.typingSpeed(pbDiff, { showDecimalPlaces: true }) ); } diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 9a0aaac080ca..794f14aa9ae4 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -22,6 +22,7 @@ import { debounce } from "throttle-debounce"; import * as ResultWordHighlight from "../elements/result-word-highlight"; import * as ActivePage from "../states/active-page"; import html2canvas from "html2canvas"; +import Format from "../utils/format"; const debouncedZipfCheck = debounce(250, async () => { const supports = await Misc.checkIfLanguageSupportsZipf(Config.language); @@ -1299,9 +1300,8 @@ $(".pageTest #resultWordsHistory").on("mouseenter", ".words .word", (e) => { .replace(/>/g, ">")}
- ${Math.round( - getTypingSpeedUnit(Config.typingSpeedUnit).fromWpm(burst) - )}${Config.typingSpeedUnit} + ${Format.typingSpeed(burst, { showDecimalPlaces: false })} + ${Config.typingSpeedUnit}
` ); diff --git a/frontend/src/ts/types/types.d.ts b/frontend/src/ts/types/types.d.ts index fe64adfaefb5..181578198b76 100644 --- a/frontend/src/ts/types/types.d.ts +++ b/frontend/src/ts/types/types.d.ts @@ -474,7 +474,6 @@ declare namespace MonkeyTypes { type TypingSpeedUnitSettings = { fromWpm: (number: number) => number; toWpm: (number: number) => number; - convertWithUnitSuffix: (number: number, withDecimals: boolean) => string; fullUnitString: string; histogramDataBucketSize: number; historyStepSize: number; diff --git a/frontend/src/ts/utils/format.ts b/frontend/src/ts/utils/format.ts new file mode 100644 index 000000000000..b9c63ae4a616 --- /dev/null +++ b/frontend/src/ts/utils/format.ts @@ -0,0 +1,67 @@ +import * as Misc from "./misc"; +import Config from "../config"; +import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; + +export type FormatOptions = { + showDecimalPlaces?: boolean; + suffix?: string; + fallback?: string; +}; + +const FORMAT_DEFAULT_OPTIONS: FormatOptions = { + suffix: "", + fallback: "-", + showDecimalPlaces: undefined, +}; + +export class Formatting { + constructor(private config: SharedTypes.Config) {} + + typingSpeed( + wpm: number | null | undefined, + formatOptions: FormatOptions = FORMAT_DEFAULT_OPTIONS + ): string { + const options = { ...FORMAT_DEFAULT_OPTIONS, ...formatOptions }; + if (wpm === undefined || wpm === null) return options.fallback ?? ""; + + const result = getTypingSpeedUnit(this.config.typingSpeedUnit).fromWpm(wpm); + + return this.number(result, options); + } + percentage( + percentage: number | null | undefined, + formatOptions: FormatOptions = {} + ): string { + const options = { ...FORMAT_DEFAULT_OPTIONS, ...formatOptions }; + options.suffix = "%" + (options.suffix ?? ""); + + return this.number(percentage, options); + } + + decimals( + value: number | null | undefined, + formatOptions: FormatOptions = {} + ): string { + const options = { ...FORMAT_DEFAULT_OPTIONS, ...formatOptions }; + return this.number(value, options); + } + private number( + value: number | null | undefined, + formatOptions: FormatOptions + ): string { + if (value === undefined || value === null) + return formatOptions.fallback ?? ""; + const suffix = formatOptions.suffix ?? ""; + + if ( + formatOptions.showDecimalPlaces !== undefined + ? formatOptions.showDecimalPlaces + : this.config.alwaysShowDecimalPlaces + ) { + return Misc.roundTo2(value).toFixed(2) + suffix; + } + return Math.round(value).toString() + suffix; + } +} + +export default new Formatting(Config); diff --git a/frontend/src/ts/utils/typing-speed-units.ts b/frontend/src/ts/utils/typing-speed-units.ts index 1e031ddd5796..c31b97f7c623 100644 --- a/frontend/src/ts/utils/typing-speed-units.ts +++ b/frontend/src/ts/utils/typing-speed-units.ts @@ -1,5 +1,3 @@ -import { roundTo2 } from "../utils/misc"; - class Unit implements MonkeyTypes.TypingSpeedUnitSettings { unit: SharedTypes.Config.TypingSpeedUnit; convertFactor: number; @@ -28,14 +26,6 @@ class Unit implements MonkeyTypes.TypingSpeedUnitSettings { toWpm(val: number): number { return val / this.convertFactor; } - - convertWithUnitSuffix(wpm: number, withDecimals: boolean): string { - if (withDecimals) { - return roundTo2(this.fromWpm(wpm)).toFixed(2) + " " + this.unit; - } else { - return Math.round(this.fromWpm(wpm)) + " " + this.unit; - } - } } const typingSpeedUnits: Record = { diff --git a/frontend/static/html/popups.html b/frontend/static/html/popups.html index 37d8356374c7..4c945d213b2e 100644 --- a/frontend/static/html/popups.html +++ b/frontend/static/html/popups.html @@ -244,7 +244,7 @@ words - wpm + wpm
accuracy