diff --git a/shared/utils/files.test.ts b/shared/utils/files.test.ts index b9aa073a01..66bc822fbf 100644 --- a/shared/utils/files.test.ts +++ b/shared/utils/files.test.ts @@ -1,18 +1,72 @@ import { bytesToHumanReadable, getFileNameFromUrl } from "./files"; +import * as browser from "./browser"; + +// Mock the browser detection +jest.mock("./browser", () => ({ + isMac: jest.fn(), +})); + +const mockIsMac = browser.isMac as jest.MockedFunction; describe("bytesToHumanReadable", () => { - it("outputs readable string", () => { - expect(bytesToHumanReadable(0)).toBe("0 Bytes"); - expect(bytesToHumanReadable(0.0)).toBe("0 Bytes"); - expect(bytesToHumanReadable(33)).toBe("33 Bytes"); - expect(bytesToHumanReadable(500)).toBe("500 Bytes"); - expect(bytesToHumanReadable(1000)).toBe("1 kB"); - expect(bytesToHumanReadable(15000)).toBe("15 kB"); - expect(bytesToHumanReadable(12345)).toBe("12.34 kB"); - expect(bytesToHumanReadable(123456)).toBe("123.45 kB"); - expect(bytesToHumanReadable(1234567)).toBe("1.23 MB"); - expect(bytesToHumanReadable(1234567890)).toBe("1.23 GB"); - expect(bytesToHumanReadable(undefined)).toBe("0 Bytes"); + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("on macOS (decimal units)", () => { + beforeEach(() => { + mockIsMac.mockReturnValue(true); + }); + + it("outputs readable string using decimal units", () => { + expect(bytesToHumanReadable(0)).toBe("0 Bytes"); + expect(bytesToHumanReadable(0.0)).toBe("0 Bytes"); + expect(bytesToHumanReadable(33)).toBe("33 Bytes"); + expect(bytesToHumanReadable(500)).toBe("500 Bytes"); + expect(bytesToHumanReadable(1000)).toBe("1 KB"); + expect(bytesToHumanReadable(15000)).toBe("15 KB"); + expect(bytesToHumanReadable(12345)).toBe("12.35 KB"); + expect(bytesToHumanReadable(123456)).toBe("123.46 KB"); + expect(bytesToHumanReadable(1234567)).toBe("1.23 MB"); + expect(bytesToHumanReadable(1234567890)).toBe("1.23 GB"); + expect(bytesToHumanReadable(undefined)).toBe("0 Bytes"); + }); + }); + + describe("on Windows/other platforms (binary units)", () => { + beforeEach(() => { + mockIsMac.mockReturnValue(false); + }); + + it("outputs readable string using binary units", () => { + expect(bytesToHumanReadable(0)).toBe("0 Bytes"); + expect(bytesToHumanReadable(0.0)).toBe("0 Bytes"); + expect(bytesToHumanReadable(33)).toBe("33 Bytes"); + expect(bytesToHumanReadable(500)).toBe("500 Bytes"); + expect(bytesToHumanReadable(1000)).toBe("1000 Bytes"); + expect(bytesToHumanReadable(1024)).toBe("1 KB"); + expect(bytesToHumanReadable(1536)).toBe("1.5 KB"); + expect(bytesToHumanReadable(15360)).toBe("15 KB"); + expect(bytesToHumanReadable(12345)).toBe("12.06 KB"); + expect(bytesToHumanReadable(126464)).toBe("123.5 KB"); + expect(bytesToHumanReadable(1048576)).toBe("1 MB"); + expect(bytesToHumanReadable(1073741824)).toBe("1 GB"); + expect(bytesToHumanReadable(undefined)).toBe("0 Bytes"); + }); + }); + + describe("platform-specific behavior for issue #10085", () => { + const fileSize = 91435827; // 87.2MB in binary, ~91.44MB in decimal + + it("displays correctly on macOS (decimal)", () => { + mockIsMac.mockReturnValue(true); + expect(bytesToHumanReadable(fileSize)).toBe("91.44 MB"); + }); + + it("displays correctly on Windows (binary)", () => { + mockIsMac.mockReturnValue(false); + expect(bytesToHumanReadable(fileSize)).toBe("87.2 MB"); + }); }); }); diff --git a/shared/utils/files.ts b/shared/utils/files.ts index 4ebaae6193..2ea43f8d00 100644 --- a/shared/utils/files.ts +++ b/shared/utils/files.ts @@ -1,5 +1,8 @@ +import { isMac } from "./browser"; + /** * Converts bytes to human readable string for display + * Uses binary units (1024-based) on Windows and decimal units (1000-based) on macOS * * @param bytes filesize in bytes * @returns Human readable filesize as a string @@ -9,19 +12,23 @@ export function bytesToHumanReadable(bytes: number | undefined) { return "0 Bytes"; } - const out = ("0".repeat((bytes.toString().length * 2) % 3) + bytes).match( - /.{3}/g - ); + // Use decimal units (base 1000) on macOS, binary units (base 1024) on other platforms + const useMacUnits = isMac(); + const base = useMacUnits ? 1000 : 1024; + const threshold = useMacUnits ? 1000 : 1024; - if (!out || bytes < 1000) { + if (bytes < threshold) { return bytes + " Bytes"; } - const f = (out[1] ?? "").substring(0, 2); + const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const exponent = Math.floor(Math.log(bytes) / Math.log(base)); + const value = bytes / Math.pow(base, exponent); - return `${Number(out[0])}${f === "00" ? "" : `.${f}`} ${ - " kMGTPEZY"[out.length] - }B`; + // Format to 2 decimal places and remove trailing zeros + const formatted = parseFloat(value.toFixed(2)); + + return `${formatted} ${units[exponent]}`; } /**