UNPKG

@fractorysolutions/safe-units

Version:
424 lines (362 loc) 13.5 kB
import { feet, kilo } from "../.."; import { MeasureFormatter } from "../genericMeasure"; import { Measure } from "../numberMeasure"; describe("Number measures", () => { const meters = Measure.dimension("length", "m"); const seconds = Measure.dimension("time", "s"); const kilograms = Measure.dimension("mass", "kg"); const mps = meters.per(seconds); const mps2 = mps.per(seconds); describe("dimension", () => { it("should create dimensions with value 1", () => { expect(Measure.dimension("foo", "f")).toEqual({ value: 1, unit: { foo: ["f", "1"] }, symbol: "f", }); }); }); describe("construction", () => { it("should construct from a number of and a unit", () => { const measure = Measure.of(10, mps2); expect(measure.value).toBe(10); expect(measure.unit).toEqual(mps2.unit); }); it("should construct from a number and another measure", () => { const kilometer = Measure.of(1000, meters); const measure = Measure.of(5.2, kilometer); expect(measure.value).toBe(5200); expect(measure.unit).toEqual(meters.unit); }); it("should construct dimensionless values", () => { const dimensionless = Measure.dimensionless(3); expect(dimensionless.value).toBe(3); expect(dimensionless.unit).toEqual({}); }); }); describe("prefixes", () => { const kilo = Measure.prefix("k", 1000); it("should scale the base unit", () => { const km = kilo(meters); expect(km.unit).toEqual(meters.unit); expect(km.value).toBe(1000); }); it("should apply a prefix when a symbol is present on the base unit", () => { expect(kilo(meters).symbol).toBe("km"); }); it("should not apply a prefix when a symbol is not present on the base unit", () => { const blargs = Measure.of(1e-3, meters); const kblargs = kilo(blargs); expect(kblargs.symbol).toBeUndefined(); expect(kblargs.value).toBe(1); }); }); describe("math", () => { it("arithmetic", () => { expect(Measure.add(Measure.of(5, mps), Measure.of(-5, mps))).toEqual( Measure.of(0, mps), ); expect(Measure.subtract(Measure.of(5, mps), Measure.of(-5, mps))).toEqual( Measure.of(10, mps), ); expect( Measure.multiply(Measure.of(5, mps), Measure.of(10, seconds)), ).toEqual(Measure.of(50, meters)); expect( Measure.divide(Measure.of(50, meters), Measure.of(10, seconds)), ).toEqual(Measure.of(5, mps)); }); it("abs", () => { expect(Measure.abs(Measure.of(-10, mps))).toEqual(Measure.of(10, mps)); }); it("cbrt", () => { expect(Measure.cbrt(Measure.of(64, seconds.cubed()))).toEqual( Measure.of(4, seconds), ); }); it("ceil", () => { expect(Measure.ceil(Measure.of(3.4, mps))).toEqual(Measure.of(4, mps)); }); it("floor", () => { expect(Measure.floor(Measure.of(7.8, mps))).toEqual(Measure.of(7, mps)); }); it("hypot", () => { expect( Measure.hypot(Measure.of(3, meters), Measure.of(4, meters)), ).toEqual(Measure.of(5, meters)); }); it("max", () => { expect( Measure.max( Measure.of(10, mps), Measure.of(5, mps), Measure.of(15, mps), ), ).toEqual(Measure.of(15, mps)); }); it("min", () => { expect( Measure.min( Measure.of(10, mps), Measure.of(5, mps), Measure.of(15, mps), ), ).toEqual(Measure.of(5, mps)); }); it("pow", () => { expect(Measure.pow(Measure.of(3, meters), "4")).toEqual( Measure.of(81, meters.toThe("4")), ); }); it("round", () => { expect(Measure.round(Measure.of(7.8, mps))).toEqual(Measure.of(8, mps)); }); it("sqrt", () => { expect(Measure.sqrt(Measure.of(25, meters.squared()))).toEqual( Measure.of(5, meters), ); }); it("sum", () => { expect( Measure.sum( Measure.of(10, mps), Measure.of(5, mps), Measure.of(15, mps), ), ).toEqual(Measure.of(30, mps)); }); it("trunc", () => { expect(Measure.trunc(Measure.of(-7.8, mps))).toEqual(Measure.of(-7, mps)); }); }); describe("arithmetic", () => { it("should negate", () => { const value = Measure.of(10, mps); expect(value.negate()).toEqual(Measure.of(-10, mps)); }); it("should add", () => { const left = Measure.of(10, mps); const right = Measure.of(5, mps); expect(left.plus(right)).toEqual(Measure.of(15, mps)); }); it("should subtract", () => { const left = Measure.of(10, seconds); const right = Measure.of(5, seconds); expect(left.minus(right)).toEqual(Measure.of(5, seconds)); }); it("should multiply", () => { const left = Measure.of(10, mps); const right = Measure.of(5, seconds); expect(left.times(right)).toEqual(Measure.of(50, meters)); }); it("should divide", () => { const left = Measure.of(10, mps); const right = Measure.of(5, seconds); expect(left.over(right)).toEqual(Measure.of(2, mps2)); expect(left.per(right)).toEqual(Measure.of(2, mps2)); expect(left.div(right)).toEqual(Measure.of(2, mps2)); }); it("should scale", () => { const value = Measure.of(10, mps); expect(value.scale(2)).toEqual(Measure.of(20, mps)); }); it("should exponentiate", () => { const value = Measure.of(10, meters); expect(value.inverse()).toEqual(Measure.of(0.1, meters.inverse())); expect(value.reciprocal()).toEqual(Measure.of(0.1, meters.inverse())); expect(value.toThe("0")).toEqual(Measure.dimensionless(1)); expect(value.toThe("1")).toEqual(Measure.of(10, meters)); expect(value.squared()).toEqual(Measure.of(100, meters.squared())); expect(value.cubed()).toEqual(Measure.of(1000, meters.cubed())); }); }); describe("comparison", () => { const zero = Measure.of(0, meters); const five = Measure.of(5, meters); const ten = Measure.of(10, meters); it("should compare less than", () => { expect(five.lt(zero)).toBe(false); expect(five.lt(five)).toBe(false); expect(five.lt(ten)).toBe(true); }); it("should compare less than or equal to", () => { expect(five.lte(zero)).toBe(false); expect(five.lte(five)).toBe(true); expect(five.lte(ten)).toBe(true); }); it("should compare equal to", () => { expect(five.eq(zero)).toBe(false); expect(five.eq(five)).toBe(true); expect(five.eq(ten)).toBe(false); }); it("should compare not equal to", () => { expect(five.neq(zero)).toBe(true); expect(five.neq(five)).toBe(false); expect(five.neq(ten)).toBe(true); }); it("should compare greater than or equal to", () => { expect(five.gte(zero)).toBe(true); expect(five.gte(five)).toBe(true); expect(five.gte(ten)).toBe(false); }); it("should compare greater than", () => { expect(five.gt(zero)).toBe(true); expect(five.gt(five)).toBe(false); expect(five.gt(ten)).toBe(false); }); }); describe("symbols", () => { it("should assign a symbol via .of()", () => { expect(Measure.of(1000, meters, "km").symbol).toBe("km"); }); it("should copy assign a symbol via .withSymbol()", () => { const original = Measure.of(1000, meters); const result = original.withSymbol("km"); expect(result).not.toBe(original); expect(original.symbol).toBeUndefined(); expect(result.symbol).toBe("km"); }); it("should not pass along symbols through operations", () => { const km = Measure.of(1000, meters.squared()).withSymbol("km2"); const dm = Measure.of(10, meters.squared()).withSymbol("dm2"); expect(km.negate().symbol).toBeUndefined(); expect(km.squared().symbol).toBeUndefined(); expect(km.inverse().symbol).toBeUndefined(); expect(km.plus(dm).symbol).toBeUndefined(); expect(km.minus(dm).symbol).toBeUndefined(); expect(km.times(dm).symbol).toBeUndefined(); expect(km.over(dm).symbol).toBeUndefined(); }); }); describe("formatting", () => { function expectFormat( unit: Measure<any>, formatted: string, formatter?: MeasureFormatter<number>, ): void { expect(unit.toString(formatter)).toBe(formatted); } it("should format dimensionless units", () => { expectFormat(Measure.dimensionless(10), "10"); }); it("should format base units", () => { expectFormat(meters, "1 m"); }); it("should format units with only positive exponents", () => { expectFormat(meters.squared(), "1 m^2"); expectFormat(meters.times(seconds), "1 m * s"); expectFormat(meters.cubed().times(seconds.squared()), "1 m^3 * s^2"); }); it("should format units with only negative exponents", () => { expectFormat(seconds.inverse(), "1 s^-1"); expectFormat(seconds.toThe("-2"), "1 s^-2"); expectFormat( seconds.toThe("-2").times(meters.toThe("-3")), "1 m^-3 * s^-2", ); }); it("should format units with positive exponents and one negative exponent", () => { expectFormat(meters.per(seconds), "1 m / s"); expectFormat(meters.squared().per(seconds.squared()), "1 m^2 / s^2"); expectFormat( meters.times(kilograms).per(seconds.squared()), "1 kg * m / s^2", ); }); it("should format units with positive exponents and negative exponents", () => { expectFormat(meters.per(seconds.times(kilograms)), "1 m / (kg * s)"); expectFormat( meters.squared().per(seconds.squared().times(kilograms)), "1 m^2 / (kg * s^2)", ); expectFormat( meters .cubed() .times(kilograms) .per(kilograms.cubed()) .per(seconds.squared()), "1 m^3 / (kg^2 * s^2)", ); }); it("should have consistent formatting no matter how the unit is constructed", () => { const metersTimesSecond = "1 m * s"; expectFormat(meters.times(seconds), metersTimesSecond); expectFormat(seconds.times(meters), metersTimesSecond); const metersPerSecond = "1 m / s"; expectFormat(meters.per(seconds), metersPerSecond); expectFormat(seconds.inverse().times(meters), metersPerSecond); }); it("should not format using symbol even if present", () => { expect(Measure.of(5, meters.squared()).withSymbol("m2").toString()).toBe( "5 m^2", ); expect(Measure.dimensionless(0).withSymbol("rad").toString()).toBe("0"); }); it("should format measures as other measures with symbols", () => { const kilometers = Measure.of(1000, meters, "km"); expect(Measure.of(10000, meters).in(kilometers)).toBe("10 km"); }); it("should use normal formatting if the other measure has no symbol", () => { const kilometers = Measure.of(1000, meters); expect(Measure.of(1000, meters).in(kilometers)).toBe("1000 m"); }); it("should use base unit symbols to format when available", () => { const m = Measure.dimension("test-length", "meter"); const s = Measure.dimension("test-time", "second"); expect(m.toString()).toBe("1 meter"); expect(Measure.of(1, m.per(s)).toString()).toBe("1 meter / second"); expect(Measure.of(1, m.squared().per(s.squared())).toString()).toBe( "1 meter^2 / second^2", ); }); it("should use a custom formatter for values if provided", () => { expectFormat(Measure.of(3.14159, meters), "3.14 m", { formatValue: (value) => value.toPrecision(3), }); }); it("should use a custom formatter for units if provided", () => { expectFormat(Measure.of(3.14159, meters), "3.14159 meters", { formatUnit: () => "meters", }); }); it("should use both custom formatters if provided", () => { expectFormat(Measure.of(3.14159, meters), "3.142 meters", { formatValue: (value) => value.toPrecision(4), formatUnit: () => "meters", }); }); it("should not use a custom formatter for units when expressing in terms of another measure", () => { const kilometers = Measure.of(1000, meters, "km"); expect( Measure.of(20, kilometers).in(kilometers, { formatValue: (value) => value.toExponential(), formatUnit: () => "kilometers", }), ).toBe("2e+1 km"); }); it("should use a custom formatter for units when expressing in terms of another unit with no symbol", () => { const kilometers = Measure.of(1000, meters); expect( Measure.of(20, kilometers).in(kilometers, { formatUnit: () => "meters", }), ).toBe("20000 meters"); }); }); describe("conversion", () => { it("should calculate meters to kilometers correctly", () => { expect(Measure.of(10000, meters).valueIn(kilo(meters))).toBe(10); }); it("should calculate feet to meters correctly", () => { expect(Measure.of(10, feet).valueIn(meters)).toBeCloseTo(3.048); }); }); describe("utils", () => { it("should clone", () => { const original = Measure.of(100, meters); const copy = original.clone(); expect(original).not.toBe(copy); expect(original).toEqual(copy); }); }); });