diff --git a/apps/web/src/lib/formatBytes.test.ts b/apps/web/src/lib/formatBytes.test.ts new file mode 100644 index 0000000000000..5b5d948bb59c2 --- /dev/null +++ b/apps/web/src/lib/formatBytes.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { formatBytes } from "./formatBytes"; + +describe("formatBytes", () => { + it.each([ + [0, "0 B"], + [1_023, "1023 B"], + [1_024, "1.00 KB"], + [1_536, "1.50 KB"], + [1_047_552, "1023 KB"], + [1_048_576, "1.00 MB"], + [5 * 1_024 ** 4, "5.00 TB"], + [2_048 * 1_024 ** 4, "2048 TB"], + ])("formats %d bytes as %s", (value, label) => { + expect(formatBytes(value)).toBe(label); + }); + + it.each([ + [1_023.6, "1.00 KB"], + [1_048_100, "1.00 MB"], + [1_073_217_536, "1.00 GB"], + ])("moves %d bytes to the next unit when rounding reaches 1024", (value, label) => { + expect(formatBytes(value)).toBe(label); + }); +}); diff --git a/apps/web/src/lib/formatBytes.ts b/apps/web/src/lib/formatBytes.ts index 306302db6761c..fe6de1cdb2a12 100644 --- a/apps/web/src/lib/formatBytes.ts +++ b/apps/web/src/lib/formatBytes.ts @@ -1,11 +1,16 @@ +function formatScaled(value: number): string { + return value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2); +} + export function formatBytes(value: number): string { - if (value < 1_024) return `${Math.round(value)} B`; + if (Math.round(value) < 1_024) return `${Math.round(value)} B`; const units = ["KB", "MB", "GB", "TB"] as const; - let next = value; - let unitIndex = -1; - do { + let next = value / 1_024; + let unitIndex = 0; + // Pick the unit after rounding, so 1023.6 KB reads "1.00 MB", not "1024 KB". + while (Number(formatScaled(next)) >= 1_024 && unitIndex < units.length - 1) { next /= 1_024; unitIndex += 1; - } while (next >= 1_024 && unitIndex < units.length - 1); - return `${next.toFixed(next >= 100 ? 0 : next >= 10 ? 1 : 2)} ${units[unitIndex]}`; + } + return `${formatScaled(next)} ${units[unitIndex]}`; }