diff --git a/packages/react-native/Libraries/Utilities/__tests__/formatFileSize-test.js b/packages/react-native/Libraries/Utilities/__tests__/formatFileSize-test.js new file mode 100644 index 00000000000000..b00ea2b9db0546 --- /dev/null +++ b/packages/react-native/Libraries/Utilities/__tests__/formatFileSize-test.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import formatFileSize from '../formatFileSize'; + +describe('formatFileSize', () => { + it('formats zero bytes', () => { + expect(formatFileSize(0)).toEqual('0 Bytes'); + }); + + it('formats bytes less than 1 KB', () => { + expect(formatFileSize(512)).toEqual('512.00 Bytes'); + expect(formatFileSize(1023)).toEqual('1023.00 Bytes'); + }); + + it('formats kilobytes', () => { + expect(formatFileSize(1024)).toEqual('1.00 KB'); + expect(formatFileSize(1536)).toEqual('1.50 KB'); + expect(formatFileSize(2048)).toEqual('2.00 KB'); + }); + + it('formats megabytes', () => { + expect(formatFileSize(1048576)).toEqual('1.00 MB'); + expect(formatFileSize(1572864)).toEqual('1.50 MB'); + expect(formatFileSize(2097152)).toEqual('2.00 MB'); + }); + + it('formats gigabytes', () => { + expect(formatFileSize(1073741824)).toEqual('1.00 GB'); + expect(formatFileSize(1610612736)).toEqual('1.50 GB'); + }); + + it('formats terabytes', () => { + expect(formatFileSize(1099511627776)).toEqual('1.00 TB'); + }); + + it('respects custom decimal places', () => { + expect(formatFileSize(1536, 0)).toEqual('2 KB'); + expect(formatFileSize(1536, 1)).toEqual('1.5 KB'); + expect(formatFileSize(1536, 3)).toEqual('1.500 KB'); + }); + + it('handles very large numbers', () => { + const largeNumber = Math.pow(1024, 8); // YB + const result = formatFileSize(largeNumber); + expect(result).toContain('YB'); + }); + + it('throws error for negative numbers', () => { + expect(() => formatFileSize(-1)).toThrow( + 'Bytes must be a non-negative number', + ); + expect(() => formatFileSize(-100)).toThrow( + 'Bytes must be a non-negative number', + ); + }); + + it('throws error for non-finite numbers', () => { + expect(() => formatFileSize(Infinity)).toThrow( + 'Bytes must be a finite number', + ); + expect(() => formatFileSize(-Infinity)).toThrow( + 'Bytes must be a finite number', + ); + expect(() => formatFileSize(NaN)).toThrow('Bytes must be a finite number'); + }); +}); diff --git a/packages/react-native/Libraries/Utilities/formatFileSize.js b/packages/react-native/Libraries/Utilities/formatFileSize.js new file mode 100644 index 00000000000000..b5a589c1f06ee4 --- /dev/null +++ b/packages/react-native/Libraries/Utilities/formatFileSize.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +/** + * Formats a number of bytes into a human-readable string. + * + * @param bytes - The number of bytes to format + * @param decimals - The number of decimal places to show (default: 2) + * @returns A formatted string like "1.5 KB", "2.3 MB", etc. + * + * @example + * formatFileSize(1024) // "1.00 KB" + * formatFileSize(1536) // "1.50 KB" + * formatFileSize(1048576) // "1.00 MB" + */ +export default function formatFileSize( + bytes: number, + decimals: number = 2, +): string { + if (bytes === 0) { + return '0 Bytes'; + } + + if (!Number.isFinite(bytes)) { + throw new Error('Bytes must be a finite number'); + } + + if (bytes < 0) { + throw new Error('Bytes must be a non-negative number'); + } + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + // Ensure we don't go beyond the available sizes + const sizeIndex = Math.min(i, sizes.length - 1); + const size = sizes[sizeIndex]; + const value = bytes / Math.pow(k, sizeIndex); + + return `${value.toFixed(decimals)} ${size}`; +}