Search code examples
javascripttypescripttestingjestjsvitest

Number.toLocaleString in pt-BR fails in vitest


I've defined a function toCurrency to convert string or number to locale strings:

function toCurrency(
    value: string | number,
    locale: string = "pt-BR",
    currency: string = "BRL",
    options?: Intl.NumberFormatOptions
) {
    return Number(value).toLocaleString(locale, {
        maximumFractionDigits: 2,
        style: "currency",
        currency: currency,
        ...options,
    });
}

and a test to validate it:

describe("toCurrency", () => {
    test("toCurrency defined", async () => {
        expect(toCurrency).toBeDefined();
    });

    test("toCurrency", () => {
        const result = toCurrency(1, "pt-BR", "BRL");
        expect(result).toEqual("R$ 1,00");
    });
    
    test("toCurrency USD", () => {
        const result = toCurrency(1, "en-US", "USD");
        expect(result).toEqual("$1.00");
    });
});

But the tests fails only for pt-BR:

enter image description here


Solution

  • The Node.js version you're running may have a different version of the CLDR data than your test expects, and so you get a different result for toLocaleString.

    Alternately, if you haven't written your test specifically against CLDR data, your test expects e.g. a normal 0x20 space between R$ and the number, while chances are the character as formatted is actually e.g. a non-breaking space (\xA0):

    > function toCurrency(
    ...     value,
    ...     locale = "pt-BR",
    ...     currency = "BRL"
    ... ) {
    ...     return Number(value).toLocaleString(locale, {
    ...         maximumFractionDigits: 2,
    ...         style: "currency",
    ...         currency,
    ...     });
    ... }
    > toCurrency(1)
    'R$ 1,00'
    > [...toCurrency(1)].map(c => c.codePointAt(0))
    [
      82, 36, 160, 49,
      44, 48,  48
    ]
    

    Note how the third codepoint is 160 – that's 0xA0.