Files
avoidwork-filesize.js/tests/unit/filesize.test.js
T
dualfroz 7b781162ac fix(helpers): keep fullform singular for negative one-magnitude values
decorateResult() captured numericValue (used to pick the singular vs
plural fullform suffix) after the negative sign was already prefixed
onto result[0], so filesize(-1, {fullform: true}) compared -1 === 1
and wrongly pluralized to "-1 bytes" instead of "-1 byte". Capture the
magnitude before the sign is applied instead.
2026-09-04 04:49:30 +02:00

1109 lines
43 KiB
JavaScript

/**
* Unit tests for filesize.js
* Tests the main filesize function and partial function
*/
import assert from "node:assert";
import { describe, it } from "node:test";
import { filesize, partial } from "../../src/filesize.js";
describe("filesize", () => {
describe("Basic functionality", () => {
it("should convert bytes to human readable format", () => {
assert.strictEqual(filesize(1000), "1 kB");
assert.strictEqual(filesize(1000000), "1 MB");
assert.strictEqual(filesize(1000000000), "1 GB");
});
it("should handle zero bytes", () => {
assert.strictEqual(filesize(0), "0 B");
});
it("should handle small numbers", () => {
assert.strictEqual(filesize(1), "1 B");
assert.strictEqual(filesize(512), "512 B");
});
it("should handle negative numbers", () => {
assert.strictEqual(filesize(-1000), "-1 kB");
assert.strictEqual(filesize(-1000000), "-1 MB");
});
it("should throw TypeError for Infinity", () => {
assert.throws(() => filesize(Infinity), TypeError);
});
it("should throw TypeError for -Infinity", () => {
assert.throws(() => filesize(-Infinity), TypeError);
});
});
describe("Bits option", () => {
it("should convert to bits when bits option is true", () => {
assert.strictEqual(filesize(1000, { bits: true }), "8 kbit");
assert.strictEqual(filesize(1000000, { bits: true }), "8 Mbit");
});
it("should handle zero with bits option", () => {
assert.strictEqual(filesize(0, { bits: true }), "0 bit");
});
it("should respect forced exponent with bits", () => {
// Forced exponent should prevent auto-increment
assert.strictEqual(filesize(1024, { exponent: 0, bits: true }), "8192 bit");
assert.strictEqual(filesize(1024, { exponent: 1, bits: true }), "8.19 kbit");
});
});
describe("Zero value edge cases", () => {
it("should handle zero with exponent output", () => {
assert.strictEqual(filesize(0, { output: "exponent" }), 0);
});
it("should pad zero like any other value", () => {
assert.strictEqual(filesize(0, { pad: true, round: 2 }), "0.00 B");
assert.strictEqual(filesize(0, { pad: true, round: 3 }), "0.000 B");
assert.strictEqual(filesize(0, { pad: true, round: 0 }), "0 B");
});
it("should prefer precision over pad for zero", () => {
assert.strictEqual(filesize(0, { precision: 3, pad: true, round: 5 }), "0.00 B");
});
it("should handle zero with custom symbols", () => {
assert.strictEqual(filesize(0, { symbols: { B: "Bytes" } }), "0 Bytes");
});
it("should handle zero with fullform", () => {
assert.strictEqual(filesize(0, { fullform: true }), "0 byte");
});
it("should handle zero with fullform and bits", () => {
assert.strictEqual(filesize(0, { fullform: true, bits: true }), "0 bit");
});
it("should handle zero with array output", () => {
const result = filesize(0, { output: "array" });
assert.deepStrictEqual(result, [0, "B"]);
});
it("should handle zero with object output", () => {
const result = filesize(0, { output: "object" });
assert.deepStrictEqual(result, { value: 0, symbol: "B", exponent: 0, unit: "B" });
});
});
describe("Standards", () => {
it("should use IEC standard", () => {
assert.strictEqual(filesize(1024, { standard: "iec" }), "1 KiB");
assert.strictEqual(filesize(1048576, { standard: "iec" }), "1 MiB");
});
it("should use JEDEC standard", () => {
assert.strictEqual(filesize(1024, { standard: "jedec" }), "1 KB");
assert.strictEqual(filesize(1048576, { standard: "jedec" }), "1 MB");
});
it("should use SI standard", () => {
assert.strictEqual(filesize(1000, { standard: "si" }), "1 kB");
assert.strictEqual(filesize(1000000, { standard: "si" }), "1 MB");
});
});
describe("Output formats", () => {
it("should return string by default", () => {
const result = filesize(1000);
assert.strictEqual(typeof result, "string");
assert.strictEqual(result, "1 kB");
});
it('should return array when output is "array"', () => {
const result = filesize(1000, { output: "array" });
assert(Array.isArray(result));
assert.strictEqual(result[0], 1);
assert.strictEqual(result[1], "kB");
});
it('should return object when output is "object"', () => {
const result = filesize(1000, { output: "object" });
assert.strictEqual(typeof result, "object");
assert.strictEqual(result.value, 1);
assert.strictEqual(result.symbol, "kB");
assert.strictEqual(result.exponent, 1);
assert.strictEqual(result.unit, "kB");
});
it('should return exponent when output is "exponent"', () => {
const result = filesize(1000, { output: "exponent" });
assert.strictEqual(typeof result, "number");
assert.strictEqual(result, 1);
});
it("should match the object exponent when rounding overflows the unit", () => {
// 999999 renders as "1 MB", so the exponent is 2, not 1
assert.strictEqual(filesize(999999, { output: "exponent" }), 2);
assert.strictEqual(
filesize(999999, { output: "exponent" }),
filesize(999999, { output: "object" }).exponent,
);
// IEC: 1048575 renders as "1 MiB"
assert.strictEqual(filesize(1048575, { standard: "iec", output: "exponent" }), 2);
});
it("should match the object exponent when bits auto-increment the unit", () => {
// 125000 bytes = 1 Mbit, so the exponent is 2, not 1
assert.strictEqual(filesize(125000, { bits: true, output: "exponent" }), 2);
assert.strictEqual(
filesize(125000, { bits: true, output: "exponent" }),
filesize(125000, { bits: true, output: "object" }).exponent,
);
});
});
describe("Rounding", () => {
it("should round to specified decimal places", () => {
assert.strictEqual(filesize(1536, { round: 1 }), "1.5 kB");
assert.strictEqual(filesize(1536, { round: 0 }), "2 kB");
assert.strictEqual(filesize(1536, { round: 3 }), "1.536 kB");
});
it("should use different rounding methods", () => {
assert.strictEqual(filesize(1536, { roundingMethod: "floor" }), "1.53 kB");
assert.strictEqual(filesize(1536, { roundingMethod: "ceil" }), "1.54 kB");
assert.strictEqual(filesize(1536, { roundingMethod: "round" }), "1.54 kB");
});
});
describe("Padding", () => {
it("should pad decimal places when pad is true", () => {
assert.strictEqual(filesize(1000, { pad: true, round: 2 }), "1.00 kB");
assert.strictEqual(filesize(1536, { pad: true, round: 1 }), "1.5 kB");
});
it("should pad decimal places for negative numbers", () => {
assert.strictEqual(filesize(-1000, { pad: true, round: 2 }), "-1.00 kB");
assert.strictEqual(filesize(-1536, { pad: true, round: 2 }), "-1.54 kB");
assert.strictEqual(filesize(-1024, { pad: true, round: 3 }), "-1.024 kB");
});
it("should pad decimal places with locale formatting for negative numbers", () => {
assert.strictEqual(filesize(-1536, { locale: "de", pad: true, round: 2 }), "-1,54 kB");
assert.strictEqual(filesize(-1000, { locale: "en-US", pad: true, round: 2 }), "-1.00 kB");
});
it("should pad decimal places with locale formatting and grouping separators", () => {
// German locale: comma as decimal separator, dot as grouping separator
// Verify comma is correctly detected as decimal separator (not grouping separator)
assert.strictEqual(filesize(1234567, { locale: "de-DE", pad: true, round: 2 }), "1,23 MB");
// Padding with German locale (3 decimal places)
assert.strictEqual(filesize(1536, { locale: "de-DE", pad: true, round: 3 }), "1,536 kB");
// German locale with padding that adds decimal places
assert.strictEqual(filesize(1536, { locale: "de", pad: true, round: 4 }), "1,5360 kB");
// German locale with grouping separator in integer part (1.234.567,89 kB)
assert.strictEqual(
filesize(1234567890, { locale: "de", exponent: 1, pad: true, round: 2 }),
"1.234.567,89 kB",
);
// English locale with larger numbers
assert.strictEqual(filesize(1234567, { locale: "en-US", pad: true, round: 2 }), "1.23 MB");
assert.strictEqual(filesize(1234567890, { locale: "en-US", pad: true, round: 2 }), "1.23 GB");
});
it("should keep every digit group and pad when an integer value has grouping separators", () => {
// en: comma grouping with no decimal in the raw value. Previously the
// trailing grouping comma was mistaken for the decimal separator, which
// dropped digit groups and skipped padding.
assert.strictEqual(
filesize(1234500e24, { locale: "en", pad: true, round: 2 }),
"1,234,500.00 YB",
);
assert.strictEqual(filesize(1000e24, { locale: "en", pad: true, round: 2 }), "1,000.00 YB");
});
});
describe("Custom separators and spacers", () => {
it("should use custom decimal separator", () => {
assert.strictEqual(filesize(1536, { separator: "," }), "1,54 kB");
});
it("should use custom spacer between value and unit", () => {
assert.strictEqual(filesize(1000, { spacer: "_" }), "1_kB");
assert.strictEqual(filesize(1000, { spacer: "" }), "1kB");
});
});
describe("Custom symbols", () => {
it("should use custom symbols when provided", () => {
const customSymbols = { kB: "kilobyte" };
assert.strictEqual(filesize(1000, { symbols: customSymbols }), "1 kilobyte");
});
});
describe("Locale formatting", () => {
it("should format using system locale when locale is true", () => {
const result = filesize(1000, { locale: true });
assert(typeof result === "string");
assert(result.includes("kB"));
});
it("should format using specific locale", () => {
const result = filesize(1536, { locale: "en-US" });
assert(typeof result === "string");
assert(result.includes("1.5"));
});
});
describe("Full form names", () => {
it("should use full form names when fullform is true", () => {
assert.strictEqual(filesize(1000, { fullform: true }), "1 kilobyte");
assert.strictEqual(filesize(2000, { fullform: true }), "2 kilobytes");
assert.strictEqual(filesize(1, { fullform: true }), "1 byte");
});
it("should use custom fullforms when provided", () => {
const customFullforms = ["", "thousand-byte"];
assert.strictEqual(
filesize(1000, { fullform: true, fullforms: customFullforms }),
"1 thousand-byte",
);
});
it("should keep the singular fullform name for negative one-magnitude values", () => {
assert.strictEqual(filesize(-1, { fullform: true }), "-1 byte");
assert.strictEqual(filesize(-0.125, { bits: true, fullform: true }), "-1 bit");
});
it("should keep the plural fullform name for negative values", () => {
assert.strictEqual(filesize(-2, { fullform: true }), "-2 bytes");
assert.strictEqual(filesize(-0.25, { bits: true, fullform: true }), "-2 bits");
});
it("should handle bits fullform singular", () => {
assert.strictEqual(filesize(0.125, { bits: true, fullform: true }), "1 bit");
});
it("should handle bits fullform plural", () => {
assert.strictEqual(filesize(0.25, { bits: true, fullform: true }), "2 bits");
});
it("should handle fullform with precision (string value path)", () => {
assert.strictEqual(
filesize(1058223158, { standard: "iec", precision: 3, fullform: true }),
"0.990 gibibytes",
);
assert.strictEqual(filesize(1234567890, { precision: 2, fullform: true }), "1.2 gigabytes");
});
it("should keep the plural fullform when a comma decimal separator is used", () => {
assert.strictEqual(filesize(1500000, { fullform: true, separator: "," }), "1,5 megabytes");
assert.strictEqual(filesize(1500000, { fullform: true, locale: "de-DE" }), "1,5 megabytes");
});
});
describe("Base and exponent options", () => {
it("should use base 2 calculations", () => {
assert.strictEqual(filesize(1024, { base: 2 }), "1 KiB");
});
it("should use base 10 calculations", () => {
assert.strictEqual(filesize(1000, { base: 10 }), "1 kB");
});
it("should use forced exponent", () => {
assert.strictEqual(filesize(1024, { exponent: 0 }), "1024 B");
assert.strictEqual(filesize(1024, { exponent: 2 }), "0 MB");
});
});
describe("Precision option", () => {
it("should use specified precision", () => {
assert.strictEqual(filesize(1536, { precision: 3 }), "1.54 kB");
assert.strictEqual(filesize(1536000, { precision: 2 }), "1.5 MB");
});
it("should remove scientific notation from precision results", () => {
// Test cases where toPrecision would normally produce scientific notation
// but our implementation removes it by splitting on "e"
// Very large numbers that would produce scientific notation
assert.strictEqual(filesize(1234567890123, { precision: 2 }), "1.2 TB");
assert.strictEqual(filesize(9876543210987, { precision: 3 }), "9.88 TB");
// Test with different precision values on large numbers
assert.strictEqual(filesize(1e15, { precision: 1 }), "1 PB");
assert.strictEqual(filesize(1e15, { precision: 2 }), "1.0 PB");
assert.strictEqual(filesize(1e15, { precision: 3 }), "1.00 PB");
});
it("should handle precision with very small numbers", () => {
// Very small numbers (less than 1 byte)
assert.strictEqual(filesize(0.001, { precision: 1 }), "0 B");
assert.strictEqual(filesize(0.1, { precision: 2 }), "0.0 B");
assert.strictEqual(filesize(0.5, { precision: 1 }), "1 B");
});
it("should handle precision with numbers that would produce e+ notation", () => {
// Numbers that produce e+ notation with toPrecision
const largeNum = 1234567890;
assert.strictEqual(filesize(largeNum, { precision: 2 }), "1.2 GB");
assert.strictEqual(filesize(largeNum, { precision: 3 }), "1.23 GB");
assert.strictEqual(filesize(largeNum, { precision: 1 }), "1 GB");
});
it("should handle precision with numbers that would produce e- notation", () => {
// Very small decimal numbers that would produce e- notation
assert.strictEqual(filesize(0.0001, { precision: 1 }), "0 B");
assert.strictEqual(filesize(0.00001, { precision: 2 }), "0.0 B");
});
it("should work with precision and different standards", () => {
const largeNum = 1234567890;
assert.strictEqual(filesize(largeNum, { precision: 2, standard: "iec" }), "1.1 GiB");
assert.strictEqual(filesize(largeNum, { precision: 2, standard: "jedec" }), "1.1 GB");
assert.strictEqual(filesize(largeNum, { precision: 2, standard: "si" }), "1.2 GB");
});
it("should work with precision and bits option", () => {
const largeNum = 1234567890;
assert.strictEqual(filesize(largeNum, { precision: 2, bits: true }), "9.9 Gbit");
assert.strictEqual(filesize(largeNum, { precision: 3, bits: true }), "9.88 Gbit");
});
it("should work with precision and array output", () => {
const result = filesize(1234567890, { precision: 2, output: "array" });
assert(Array.isArray(result));
assert.strictEqual(result[0], "1.2");
assert.strictEqual(result[1], "GB");
});
it("should work with precision and object output", () => {
const result = filesize(1234567890, { precision: 2, output: "object" });
assert.strictEqual(typeof result, "object");
assert.strictEqual(result.value, "1.2");
assert.strictEqual(result.symbol, "GB");
});
it("should handle precision with extremely large numbers", () => {
// Test with numbers that exceed normal exponent range
const extremeNumber = Math.pow(1024, 15);
const result = filesize(extremeNumber, { precision: 2 });
assert(typeof result === "string");
// Note: For extremely large numbers that exceed JavaScript's normal number range,
// the number itself may be in scientific notation, which is expected behavior
assert(result.includes("YB") || result.includes("YiB"));
});
it("should handle precision edge cases", () => {
// Test precision with zero
assert.strictEqual(filesize(0, { precision: 3 }), "0.00 B");
// Test precision with exactly 1 byte
assert.strictEqual(filesize(1, { precision: 2 }), "1.0 B");
// Test precision with negative numbers
assert.strictEqual(filesize(-1234567890, { precision: 2 }), "-1.2 GB");
});
it("should keep precision trailing zeros for negative values", () => {
// A negative value must carry the same significant digits as its positive counterpart
assert.strictEqual(filesize(1500, { precision: 3 }), "1.50 kB");
assert.strictEqual(filesize(-1500, { precision: 3 }), "-1.50 kB");
assert.strictEqual(filesize(-1000, { precision: 3 }), "-1.00 kB");
assert.strictEqual(filesize(-1, { precision: 3 }), "-1.00 B");
assert.deepStrictEqual(filesize(-1500, { precision: 3, output: "array" }), ["-1.50", "kB"]);
assert.strictEqual(filesize(-1500, { precision: 3, output: "object" }).value, "-1.50");
});
it("should ensure no scientific notation in any precision result", () => {
// Test a range of numbers that would normally produce scientific notation from toPrecision
// but should have it removed by our implementation
const testNumbers = [1e3, 1e6, 1e9, 1e12, 1234567890123456, Math.pow(10, 10)];
testNumbers.forEach((num) => {
[1, 2, 3, 4].forEach((precision) => {
const result = filesize(num, { precision });
// Result should not contain scientific notation markers when using toPrecision
// Note: We exclude extremely large numbers that naturally contain 'e' in JavaScript
if (!num.toString().includes("e")) {
assert(
!result.includes("e"),
`Result "${result}" contains 'e' for number ${num} with precision ${precision}`,
);
assert(
!result.includes("E"),
`Result "${result}" contains 'E' for number ${num} with precision ${precision}`,
);
}
});
});
});
it("should handle precision with base 2 when scientific notation is produced (regular number)", () => {
// Test case that triggers base === 2 path in line 168 precision handling
// We need a number that will produce scientific notation with toPrecision
// and uses base 2 (IEC standard)
const testNumber = 999999999999999; // Large number that triggers scientific notation
const result = filesize(testNumber, { precision: 1, standard: "iec" });
assert(typeof result === "string");
assert(!result.includes("e"));
assert(!result.includes("E"));
// Should use IEC units (base 2)
assert(result.includes("TiB") || result.includes("PiB"));
});
it("should handle precision with base 2 when scientific notation is produced (BigInt)", () => {
// Test case that triggers base === 2 path in line 168 precision handling using BigInt
// This ensures both number types are tested for base 2 path
const testNumber = BigInt("999999999999999"); // Large BigInt that triggers scientific notation
const result = filesize(testNumber, { precision: 1, standard: "iec" });
assert(typeof result === "string");
assert(!result.includes("e"));
assert(!result.includes("E"));
// Should use IEC units (base 2)
assert(result.includes("TiB") || result.includes("PiB"));
});
it("should handle precision with base 10 when scientific notation is produced (regular number)", () => {
// Test case that triggers base === 10 path in line 168 precision handling
// We need a number that will produce scientific notation with toPrecision
// and uses base 10 (SI/JEDEC standard)
const testNumber = 999999999999999; // Large number that triggers scientific notation
const result = filesize(testNumber, { precision: 1, standard: "jedec" });
assert(typeof result === "string");
assert(!result.includes("e"));
assert(!result.includes("E"));
// Should use JEDEC units (base 10 logic)
assert(result.includes("TB") || result.includes("PB"));
});
it("should handle precision with base 10 when scientific notation is produced (BigInt)", () => {
// Test case that triggers base === 10 path in line 168 precision handling using BigInt
// This ensures both number types are tested for base 10 path
const testNumber = BigInt("999999999999999"); // Large BigInt that triggers scientific notation
const result = filesize(testNumber, { precision: 1, standard: "jedec" });
assert(typeof result === "string");
assert(!result.includes("e"));
assert(!result.includes("E"));
// Should use JEDEC units (base 10 logic)
assert(result.includes("TB") || result.includes("PB"));
});
it("should handle precision with YiB-level numbers where e >= 8 (regular number)", () => {
// Test case that covers line 166 when e >= 8 (YiB level) using regular number
// This triggers result[0].includes(E) but e < 8 is false, so increment logic is skipped
const yibNumber = Math.pow(1024, 8); // 1 YiB in bytes using regular number
const result = filesize(yibNumber, { precision: 1, standard: "iec" });
assert(typeof result === "string");
// For extremely large regular numbers, some scientific notation may be expected
assert(result.includes("YiB"));
});
it("should handle precision with YiB-level numbers where e >= 8 (BigInt)", () => {
// Test case that covers line 166 when e >= 8 (YiB level) using BigInt
// This triggers result[0].includes(E) but e < 8 is false, so increment logic is skipped
// YiB is at exponent 8, so we need a number large enough to reach this level
const yibNumber = BigInt(1024) ** BigInt(8); // 1 YiB in bytes using BigInt
const result = filesize(yibNumber, { precision: 1, standard: "iec" });
assert(typeof result === "string");
assert(!result.includes("e"));
assert(!result.includes("E"));
assert(result.includes("YiB"));
});
it("should handle precision with extremely large numbers that exceed YiB (regular number)", () => {
// Test case for numbers that are larger than YiB using regular number
// This ensures we test the e >= 8 condition thoroughly with regular numbers
const extremeNumber = Math.pow(1024, 8) * 1000; // 1000 YiB using regular number
const result = filesize(extremeNumber, { precision: 2, standard: "iec" });
assert(typeof result === "string");
// For extremely large regular numbers, some scientific notation may be expected
assert(result.includes("YiB"));
});
it("should handle precision with extremely large numbers that exceed YiB (BigInt)", () => {
// Test case for numbers that are larger than YiB using BigInt
// This ensures we test the e >= 8 condition thoroughly with BigInt
const extremeNumber = BigInt(1024) ** BigInt(8) * BigInt(1000); // 1000 YiB using BigInt
const result = filesize(extremeNumber, { precision: 2, standard: "iec" });
assert(typeof result === "string");
// For extremely large BigInt numbers, the result should still use YiB units
// Some scientific notation in the final result may be acceptable for such large numbers
assert(result.includes("YiB"));
});
it("should handle precision with specific test case for GiB values", () => {
// Test specific precision case: filesize(1058223158, {base: 2, standard: 'iec', precision: 3})
// This should output '0.990 GiB'
assert.strictEqual(
filesize(1058223158, { base: 2, standard: "iec", precision: 3 }),
"0.990 GiB",
);
});
it("should return string value in object output when precision > 0", () => {
const result = filesize(1234567890, { precision: 2, output: "object" });
assert.strictEqual(typeof result.value, "string");
assert.strictEqual(result.value, "1.2");
});
it("should skip padding when round is 0", () => {
assert.strictEqual(filesize(1000, { pad: true, round: 0 }), "1 kB");
});
it("should fall back to JEDEC default for unknown standard", () => {
assert.strictEqual(filesize(1024, { standard: "foobar" }), "1.02 kB");
});
});
describe("Edge cases", () => {
it("should handle very large numbers", () => {
const largeNumber = Math.pow(1024, 8);
const result = filesize(largeNumber);
assert(typeof result === "string");
assert(result.includes("YB") || result.includes("YiB"));
});
it("should handle BigInt input", () => {
const result = filesize(BigInt(1024), { standard: "jedec" });
assert.strictEqual(result, "1 KB");
});
it("should handle numbers exceeding maximum exponent", () => {
const hugeNumber = Math.pow(1024, 10);
const result = filesize(hugeNumber);
assert(typeof result === "string");
});
it("should handle numbers exceeding lookup table size (decimal)", () => {
// Test number requiring Math.pow fallback for decimal powers
const hugeNumber = Math.pow(1000, 15); // Exceeds DECIMAL_POWERS array
const result = filesize(hugeNumber, { base: 10 });
assert.ok(result.includes("YB"));
});
it("should handle numbers exceeding lookup table size (binary)", () => {
// Test number requiring Math.pow fallback for binary powers
const hugeNumber = Math.pow(2, 100); // Exceeds BINARY_POWERS array
const result = filesize(hugeNumber, { base: 2 });
assert.ok(result.includes("YiB"));
});
it("should handle precision with numbers exceeding lookup table", () => {
const hugeNumber = Math.pow(1000, 12);
const result = filesize(hugeNumber, { precision: 2, base: 10 });
assert.ok(result.includes("YB"));
});
it("should handle precision with round=0 and scientific notation", () => {
// Test case where p === 1 in the precision recalculation branch
const result = filesize(1000000000000000000, { precision: 1, round: 0 });
assert.ok(typeof result === "string");
});
it("should cover p===1 branch in precision recalculation", () => {
// Try to force the p===1 branch in the precision recalculation
// This needs a very specific edge case where scientific notation is produced
// and after increment e=0 or round=0 makes p=1
const result = filesize(0.0001, { precision: 20, round: 0 });
assert.ok(typeof result === "string");
});
it("should trigger precision recalculation with bytes and round=0", () => {
// Another attempt: very small number that might produce scientific notation in bytes
const result = filesize(0.000000000000001, { precision: 30, round: 0 });
assert.ok(typeof result === "string");
});
it("should cover p===1 branch with input=1 and extreme precision", () => {
// When input=1, e=0, so p=1. High precision might trigger scientific notation
// and the recalculation with p===1 branch
const result = filesize(1, { precision: 50 });
assert.ok(typeof result === "string");
});
it("should attempt to trigger the elusive p===1 branch in recalculation", () => {
// Numbers that would produce scientific notation with precision=1
// When e=0 (bytes), p=1, and toPrecision(1) might produce "1e+1" etc.
const result = filesize(10, { precision: 1, round: 0 });
assert.ok(typeof result === "string");
});
it("should handle extremely large numbers with precision adjustment", () => {
const extremeNumber = Math.pow(1024, 15); // Much larger than supported exponent
const result = filesize(extremeNumber, { precision: 3 });
assert(typeof result === "string");
assert(result.includes("YB") || result.includes("YiB"));
});
it("should handle bit conversion auto-increment near boundary", () => {
// Test edge case where bits conversion causes auto-increment to next unit
const result = filesize(127.5, { bits: true }); // Should auto-increment
assert(typeof result === "string");
});
it("should handle result equal to ceil boundary for auto-increment", () => {
// Test case where result equals ceil and triggers increment
const result = filesize(999.5, { round: 0 }); // Should round to 1000 and increment
assert(typeof result === "string");
});
it("should handle padding with custom separator edge cases", () => {
// Test padding when separator is found via regex
const result = filesize(1536, { pad: true, round: 2, locale: "de-DE" });
assert(typeof result === "string");
});
it("should handle NaN exponent edge case", () => {
const result = filesize(1000, { exponent: NaN });
assert.strictEqual(result, "1 kB");
});
it("should handle NaN exponent with precision", () => {
// NaN exponent should behave like auto (-1) and allow scientific notation normalization
const result = filesize(1e15, { exponent: NaN, precision: 2 });
assert(typeof result === "string");
assert(!result.includes("e"), "Should not contain scientific notation");
});
it("should handle NaN exponent with auto-increment", () => {
// NaN exponent should allow auto-increment when result equals ceil (1024)
const result = filesize(1024, { exponent: NaN, standard: "iec" });
assert.strictEqual(result, "1 KiB");
});
it("should clamp an out-of-range negative exponent to 0 instead of returning NaN", () => {
// Only -1 is the documented "auto" sentinel. Any other negative exponent
// indexed the power lookup tables out of bounds and produced "NaN undefined".
assert.strictEqual(filesize(1024, { exponent: -2 }), "1024 B");
assert.strictEqual(filesize(1024, { exponent: -100 }), "1024 B");
});
it("should clamp a negative exponent to 0 for object output", () => {
const result = filesize(1024, { exponent: -3, output: "object" });
assert.deepStrictEqual(result, { value: 1024, symbol: "B", exponent: 0, unit: "B" });
});
it("should clamp a negative exponent to 0 for array output", () => {
const result = filesize(1024, { exponent: -3, output: "array" });
assert.deepStrictEqual(result, [1024, "B"]);
});
});
describe("Error handling", () => {
it("should throw TypeError for invalid number input", () => {
assert.throws(() => filesize("invalid"), TypeError);
assert.throws(() => filesize(NaN), TypeError);
assert.throws(() => filesize({}), TypeError);
});
it("should throw TypeError for invalid rounding method", () => {
assert.throws(() => filesize(1024, { roundingMethod: "invalid" }), TypeError);
assert.throws(() => filesize(1024, { roundingMethod: "nonexistent" }), TypeError);
});
});
describe("Input type handling", () => {
describe("Number input", () => {
describe("filesize() with number input", () => {
it("should handle integer numbers", () => {
assert.strictEqual(filesize(1024), "1.02 kB");
assert.strictEqual(filesize(0), "0 B");
assert.strictEqual(filesize(-1024), "-1.02 kB");
});
it("should handle floating point numbers", () => {
assert.strictEqual(filesize(1536.5), "1.54 kB");
assert.strictEqual(filesize(0.5), "1 B");
});
it("should handle very large numbers", () => {
const largeNumber = Number.MAX_SAFE_INTEGER;
const result = filesize(largeNumber);
assert(typeof result === "string");
});
});
describe("partial() with number input", () => {
it("should handle integer numbers", () => {
const formatIEC = partial({ standard: "iec" });
assert.strictEqual(formatIEC(1024), "1 KiB");
assert.strictEqual(formatIEC(0), "0 B");
assert.strictEqual(formatIEC(-1024), "-1 KiB");
});
it("should handle floating point numbers", () => {
const formatRounded = partial({ round: 1 });
assert.strictEqual(formatRounded(1536.7), "1.5 kB");
assert.strictEqual(formatRounded(0.5), "1 B");
});
it("should handle very large numbers", () => {
const formatBits = partial({ bits: true });
const largeNumber = Number.MAX_SAFE_INTEGER;
const result = formatBits(largeNumber);
assert(typeof result === "string");
});
});
});
describe("String input", () => {
describe("filesize() with string input", () => {
it("should handle valid integer string", () => {
assert.strictEqual(filesize("1024"), "1.02 kB");
assert.strictEqual(filesize("0"), "0 B");
assert.strictEqual(filesize("-1024"), "-1.02 kB");
});
it("should handle valid float string", () => {
assert.strictEqual(filesize("1536.5"), "1.54 kB");
assert.strictEqual(filesize("0.5"), "1 B");
});
it("should handle scientific notation string", () => {
assert.strictEqual(filesize("1e3"), "1 kB");
assert.strictEqual(filesize("1.024e3"), "1.02 kB");
});
it("should handle string with leading/trailing whitespace", () => {
assert.strictEqual(filesize(" 1024 "), "1.02 kB");
assert.strictEqual(filesize("\t1024\n"), "1.02 kB");
});
it("should work with string input and different options", () => {
assert.strictEqual(filesize("1024", { standard: "iec" }), "1 KiB");
assert.strictEqual(filesize("1000", { bits: true }), "8 kbit");
assert.strictEqual(filesize("1024", { output: "array" })[0], 1.02);
});
});
describe("partial() with string input", () => {
it("should handle valid integer string", () => {
const formatJEDEC = partial({ standard: "jedec" });
assert.strictEqual(formatJEDEC("1024"), "1 KB");
assert.strictEqual(formatJEDEC("0"), "0 B");
assert.strictEqual(formatJEDEC("-1024"), "-1 KB");
});
it("should handle valid float string", () => {
const formatPadded = partial({ pad: true, round: 2 });
assert.strictEqual(formatPadded("1536.5"), "1.54 kB");
assert.strictEqual(formatPadded("0.5"), "1.00 B");
});
it("should handle scientific notation string", () => {
const formatBits = partial({ bits: true });
assert.strictEqual(formatBits("1e3"), "8 kbit");
assert.strictEqual(formatBits("1.024e3"), "8.19 kbit");
});
it("should handle string with leading/trailing whitespace", () => {
const formatFullForm = partial({ fullform: true, standard: "iec" });
assert.strictEqual(formatFullForm(" 1024 "), "1 kibibyte");
assert.strictEqual(formatFullForm("\t2048\n"), "2 kibibytes");
});
it("should work with string input and complex options", () => {
const formatComplex = partial({
standard: "si",
round: 1,
spacer: "_",
output: "array",
});
const result = formatComplex("1000");
assert(Array.isArray(result));
assert.strictEqual(result[0], 1);
assert.strictEqual(result[1], "kB");
});
});
});
describe("BigInt input", () => {
describe("filesize() with BigInt input", () => {
it("should handle regular BigInt values", () => {
assert.strictEqual(filesize(BigInt(1024)), "1.02 kB");
assert.strictEqual(filesize(BigInt(0)), "0 B");
assert.strictEqual(filesize(BigInt(-1024)), "-1.02 kB");
});
it("should handle large BigInt values", () => {
const largeBigInt = BigInt("9007199254740992"); // 2^53
const result = filesize(largeBigInt);
assert(typeof result === "string");
assert(result.includes("PB") || result.includes("PiB"));
});
it("should handle very large BigInt values", () => {
const veryLargeBigInt = BigInt("1208925819614629174706176"); // 1024^8
const result = filesize(veryLargeBigInt);
assert(typeof result === "string");
assert(result.includes("YB") || result.includes("YiB"));
});
it("should work with BigInt input and different options", () => {
assert.strictEqual(filesize(BigInt(1024), { standard: "jedec" }), "1 KB");
assert.strictEqual(filesize(BigInt(1000), { bits: true }), "8 kbit");
const objectResult = filesize(BigInt(1024), { output: "object" });
assert.strictEqual(objectResult.value, 1.02);
assert.strictEqual(objectResult.symbol, "kB");
});
});
describe("partial() with BigInt input", () => {
it("should handle regular BigInt values", () => {
const formatCustomSymbols = partial({ symbols: { KiB: "kibibyte" }, standard: "iec" });
assert.strictEqual(formatCustomSymbols(BigInt(1024)), "1 kibibyte");
assert.strictEqual(formatCustomSymbols(BigInt(0)), "0 B");
assert.strictEqual(formatCustomSymbols(BigInt(-1024)), "-1 kibibyte");
});
it("should handle large BigInt values", () => {
const formatPrecision = partial({ precision: 3 });
const largeBigInt = BigInt("9007199254740992"); // 2^53
const result = formatPrecision(largeBigInt);
assert(typeof result === "string");
assert(result.includes("PB") || result.includes("PiB"));
});
it("should handle very large BigInt values", () => {
const formatExponent = partial({ exponent: -1 }); // Auto-select exponent
const veryLargeBigInt = BigInt("1208925819614629174706176"); // 1024^8
const result = formatExponent(veryLargeBigInt);
assert(typeof result === "string");
assert(result.includes("YB") || result.includes("YiB"));
});
it("should work with BigInt input and different output formats", () => {
const formatObject = partial({ output: "object", standard: "iec" });
const objectResult = formatObject(BigInt(1024));
assert.strictEqual(typeof objectResult, "object");
assert.strictEqual(objectResult.value, 1);
assert.strictEqual(objectResult.symbol, "KiB");
const formatExponentOnly = partial({ output: "exponent" });
const exponentResult = formatExponentOnly(BigInt(1024));
assert.strictEqual(typeof exponentResult, "number");
assert.strictEqual(exponentResult, 1);
});
it("should work with BigInt input and bits option", () => {
const formatBits = partial({ bits: true, round: 1 });
assert.strictEqual(formatBits(BigInt(1024)), "8.2 kbit");
assert.strictEqual(formatBits(BigInt(1000)), "8 kbit");
});
it("should work with BigInt input and locale formatting", () => {
const formatLocale = partial({ locale: "en-US", round: 2 });
const result = formatLocale(BigInt(1536));
assert(typeof result === "string");
assert(result.includes("1.5") || result.includes("1.54"));
});
});
});
});
});
describe("partial", () => {
describe("Basic functionality", () => {
it("should create a function with preset options", () => {
const formatKiB = partial({ standard: "iec" });
assert.strictEqual(typeof formatKiB, "function");
assert.strictEqual(formatKiB(1024), "1 KiB");
});
it("should preserve all options in partial application", () => {
const formatWithOptions = partial({
round: 1,
standard: "iec",
spacer: "_",
});
assert.strictEqual(formatWithOptions(1536), "1.5_KiB");
});
});
describe("Options inheritance", () => {
it("should apply bits option from partial", () => {
const formatBits = partial({ bits: true });
assert.strictEqual(formatBits(1000), "8 kbit");
});
it("should apply pad option from partial", () => {
const formatPadded = partial({ pad: true, round: 2 });
assert.strictEqual(formatPadded(1000), "1.00 kB");
});
it("should apply output format from partial", () => {
const formatArray = partial({ output: "array" });
const result = formatArray(1000);
assert(Array.isArray(result));
assert.strictEqual(result[0], 1);
assert.strictEqual(result[1], "kB");
});
it("should apply custom symbols from partial", () => {
const formatCustom = partial({ symbols: { kB: "kilobyte" } });
assert.strictEqual(formatCustom(1000), "1 kilobyte");
});
});
describe("Multiple partial functions", () => {
it("should create independent partial functions", () => {
const formatIEC = partial({ standard: "iec" });
const formatJEDEC = partial({ standard: "jedec" });
assert.strictEqual(formatIEC(1024), "1 KiB");
assert.strictEqual(formatJEDEC(1024), "1 KB");
});
it("should handle complex option combinations", () => {
const formatComplex = partial({
bits: true,
standard: "iec",
round: 1,
fullform: true,
});
assert.strictEqual(formatComplex(1024), "8 kibibits");
});
});
describe("Precision with partial", () => {
it("should apply precision option from partial", () => {
const formatPrecision = partial({ precision: 2 });
assert.strictEqual(formatPrecision(1234567890), "1.2 GB");
assert.strictEqual(formatPrecision(9876543210), "9.9 GB");
});
it("should remove scientific notation in partial functions", () => {
const formatWithPrecision = partial({ precision: 3 });
const largeNum = 1234567890123;
const result = formatWithPrecision(largeNum);
assert(!result.includes("e"));
assert(!result.includes("E"));
assert.strictEqual(result, "1.23 TB");
});
it("should work with precision and other options combined", () => {
const formatComplex = partial({
precision: 2,
standard: "iec",
bits: true,
});
const result = formatComplex(1234567890);
assert(!result.includes("e"));
assert(!result.includes("E"));
assert.strictEqual(result, "9.2 Gibit");
});
});
describe("Default behavior", () => {
it("should work with no options provided", () => {
const defaultFormat = partial();
assert.strictEqual(typeof defaultFormat, "function");
assert.strictEqual(defaultFormat(1000), "1 kB");
});
it("should work with empty options object", () => {
const emptyOptions = partial({});
assert.strictEqual(emptyOptions(1000), "1 kB");
});
});
describe("Immutability", () => {
it("should not be affected by mutations to top-level primitive options", () => {
const opts = { standard: "iec", round: 1 };
const format = partial(opts);
// First call should work normally
assert.strictEqual(format(1536), "1.5 KiB");
// Mutate the original options object
opts.standard = "jedec";
opts.round = 0;
// The partial function should still use the destructured primitive values
assert.strictEqual(format(1536), "1.5 KiB");
});
it("should not affect other partial functions when modifying one", () => {
const format1 = partial({ symbols: { kB: "first_kB" } });
const format2 = partial({ symbols: { kB: "second_kB" } });
// Both should work independently
assert.strictEqual(format1(1000), "1 first_kB");
assert.strictEqual(format2(1000), "1 second_kB");
});
it("should not be affected by mutations to localeOptions reference", () => {
const opts = { localeOptions: { useGrouping: true } };
const format = partial(opts);
assert.strictEqual(typeof format(1234567), "string");
opts.localeOptions.useGrouping = false;
assert.strictEqual(typeof format(1234567), "string");
});
it("should not be affected by mutations to symbols reference", () => {
const opts = { symbols: { kB: "original_kB" } };
const format = partial(opts);
assert.strictEqual(format(1000), "1 original_kB");
opts.symbols.kB = "mutated_kB";
assert.strictEqual(format(1000), "1 original_kB");
});
it("should not be affected by mutations to fullforms reference", () => {
const opts = { fullforms: ["custom"], fullform: true };
const format = partial(opts);
assert.strictEqual(format(1), "1 custom");
opts.fullforms[0] = "mutated";
assert.strictEqual(format(1), "1 custom");
});
});
});
describe("partial structuredClone", () => {
it("should use structuredClone when available for localeOptions", () => {
const opts = { localeOptions: { minimumFractionDigits: 2 } };
const format = partial(opts);
const result = format(1234.5);
assert(typeof result === "string");
// Mutate original - should not affect partial
opts.localeOptions.minimumFractionDigits = 5;
const result2 = format(1234.5);
assert(typeof result2 === "string");
});
it("should use structuredClone when available for symbols", () => {
const opts = { symbols: { kB: "custom_kB" } };
const format = partial(opts);
assert.strictEqual(format(1000), "1 custom_kB");
opts.symbols.kB = "mutated_kB";
assert.strictEqual(format(1000), "1 custom_kB");
});
it("should use structuredClone when available for fullforms", () => {
const opts = { fullforms: ["my_byte"], fullform: true };
const format = partial(opts);
assert.strictEqual(format(1), "1 my_byte");
opts.fullforms[0] = "mutated";
assert.strictEqual(format(1), "1 my_byte");
});
it("should maintain deep independence between partial instances", () => {
const opts1 = { symbols: { kB: "first" } };
const opts2 = { symbols: { kB: "second" } };
const format1 = partial(opts1);
const format2 = partial(opts2);
assert.strictEqual(format1(1000), "1 first");
assert.strictEqual(format2(1000), "1 second");
opts1.symbols.kB = "mutated_first";
opts2.symbols.kB = "mutated_second";
assert.strictEqual(format1(1000), "1 first");
assert.strictEqual(format2(1000), "1 second");
});
});