diff --git a/src/common/utils.spec.ts b/src/common/utils.spec.ts new file mode 100644 index 000000000..90605c228 --- /dev/null +++ b/src/common/utils.spec.ts @@ -0,0 +1,47 @@ +// Currently only covers converToSI +import { convertToSI } from "./utils"; + +describe("convertToSI", () => { + it("should convert a known unit to SI successfully", () => { + const result = convertToSI(1, "cm"); + expect(result.valueSI).toBeCloseTo(0.01); + expect(result.unitSI).toEqual("m"); + }); + + it("should convert angstrom to SI successfully", () => { + const result = convertToSI(1, "Å"); + expect(result.valueSI).toBeCloseTo(1e-10); + expect(result.unitSI).toEqual("m"); + }); + + it("should handle different versions of Å in unicode", () => { + const inputUnit = "\u212B"; // Old unicode representation of "Å", is not boolean equal to the one we added. + const result = convertToSI(1, inputUnit); + expect(result.valueSI).toBeCloseTo(1e-10); + expect(result.unitSI).toEqual("m"); + }); + + it("should return the input value and unit if conversion fails", () => { + const result = convertToSI(1, "invalidUnit"); + expect(result.valueSI).toEqual(1); + expect(result.unitSI).toEqual("invalidUnit"); + }); + + it("should convert SI units correctly", () => { + const result = convertToSI(1000, "g"); + expect(result.valueSI).toBeCloseTo(1); + expect(result.unitSI).toEqual("kg"); + }); + + it("should handle already normalized units", () => { + const result = convertToSI(1, "m"); + expect(result.valueSI).toEqual(1); + expect(result.unitSI).toEqual("m"); + }); + + it("should handle negative values properly", () => { + const result = convertToSI(-5, "cm"); + expect(result.valueSI).toBeCloseTo(-0.05); + expect(result.unitSI).toEqual("m"); + }); +}); diff --git a/src/common/utils.ts b/src/common/utils.ts index f30ec2734..b771e2420 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -2,7 +2,7 @@ import { Logger } from "@nestjs/common"; import { inspect } from "util"; import { DateTime } from "luxon"; -import { format, unit } from "mathjs"; +import { format, unit, Unit, createUnit } from "mathjs"; import { Expression, FilterQuery, Model, PipelineStage } from "mongoose"; import { DatasetType } from "src/datasets/dataset-type.enum"; import { @@ -13,14 +13,22 @@ import { } from "./interfaces/common.interface"; import { ScientificRelation } from "./scientific-relation.enum"; +// add Å to mathjs accepted units as equivalent to angstrom +const isAlphaOriginal = Unit.isValidAlpha; +Unit.isValidAlpha = function (c) { + return isAlphaOriginal(c) || c == "Å"; +}; +createUnit("Å", "1 angstrom"); + export const convertToSI = ( inputValue: number, inputUnit: string, ): { valueSI: number; unitSI: string } => { try { + const normalizedUnit = inputUnit.normalize("NFC"); // catch and normalize the different versions of Å in unicode // Workaround related to a bug reported at https://github.com/josdejong/mathjs/issues/3097 and https://github.com/josdejong/mathjs/issues/2499 - const quantity = unit(inputValue, inputUnit) - .to(unit(inputUnit).toSI().toJSON().unit) + const quantity = unit(inputValue, normalizedUnit) + .to(unit(normalizedUnit).toSI().toJSON().unit) .toJSON(); return { valueSI: Number(quantity.value), unitSI: quantity.unit }; } catch (error) {