We have code in our class that formats a number by transforming for example 1_000_000
to 1 M
and 1_500_000
to 1.5 M
respectively.
To achieve this we are using the following code:
export const shortNumberString = (number: number) => {
if (!number) {
return number === 0 ? '0' : '';
}
const i = Math.floor(Math.log(number) / Math.log(1000));
return (
Number((number / Math.pow(1000, i)).toFixed(1)).toLocaleString() +
' ' +
['', 'K', 'M', 'B'][i]
).trim();
};
The code works as expected and does what we want them to do, but we have developers from all over the world so based on the locale of the user this test fails. For example for some users it gets formatted to 1.5 M
and for others to 1,5 M
.
Normally we just ignore these failed tests, but they are super annoying because you always see these tests failing but have to activiely check to ignore them.
We have attempted the following solutions:
Add
NODE_ICU
Some users have recommended to use
NODE_ICU
package, which I added usingyarn add -D NODE_ICU
Afterwards I have added this to our test script
"test": "NODE_ICU_DATA=node_modules/full-icu craco test",
However, running
yarn test
does still result in errors. I have also tried to run this via command line but the same error appears.Mock
Number().toLocaleString()
I have tried to mock
Number.toLocaleString
but without success. I have tried the following methodsIgnore the return values as I just wanted it to fail with the given input so I'm sure it works with mocking
First:
(global as any).Number.toLocaleString = jest.fn(() => ({ ...(global as any).Number, toLocaleString: jest.fn(() => '12.3.2019 13.47.47'), }));
However, this does not work. It is not mocking anything.
Same applies for this code
(global as any).Number.toLocaleString = jest.fn(() => new Date('2019-04-07T10:20:30Z'));
This:
(global as any).Number = jest.fn(() => ({ toLocaleString: jest.fn(() => '12.3.2019 13.47.47'), }));
also fails, because of the following error
Cannot read properties of undefined (reading 'toLocaleString')
TypeError: Cannot read properties of undefined (reading 'toLocaleString')This:
jest.mock('Number', () => ({ ...jest.requireActual('Number'), // eslint-disable-next-line @typescript-eslint/no-unused-vars toLocaleString: () => { return Promise.resolve('yolo'); }, }));
does not work as there is no module named
Number
jest can mock. I have not found a way to mockThis:
jest .spyOn(global.Number, 'toLocaleString') .mockImplementation((number: string) => 10);
also does not work as it tells me I cannot call the method
spyOn
like.spyOn(global.Number, 'toLocaleString')
Set
env.LL
etc.I have also tried to set the locales of env manually
const env: any = process.env; env.LANG = 'en-GB'; env.LANGUAGE = 'en-GB'; env.LC_ALL = 'en-GB'; env.LC_MESSAGES = 'en-GB'; const language = env.LANG || env.LANGUAGE || env.LC_ALL || env.LC_MESSAGES; console.log(language);
in the console.log it returns
en-GB
but the tests are still failing even thoughen-GB
should return the correct format
I am out of ideas but cannot believe that it is impossible to mock this. However, everything I find on Google is mostly for Date mocking but not for Number mocking and I cannot apply these suggestions to Number formatting to get them working.
The test can fail because the output of the function depends on the locale but the output of the test does not. Instead make the test depend on the locale too by:
object
(or whatever you prefer) from your formatterthe
invariantNumberFormat
is locale independent. then in your test:The
invariantNumberFormat
is similar to your function, just a bit shorter. If you need to handleBigInt
than do not use parseFloat. But the idea is the same. Configure theIntl
formatter according to your needs, they provide a lot of options.