Skip to content

Commit

Permalink
fix(input-amount): returns Unparseable as a modelValue if a wrong val…
Browse files Browse the repository at this point in the history
…ue has been entered (#2242)

* fix(input-amount): returns Unparseable as a modelValue if a wrong value has been entered

* fix(input-amount): do not break when a large amount has been entered

* Update docs/components/input-amount/overview.md

Co-authored-by: Thijs Louisse <[email protected]>

* Update packages/ui/components/input-amount/test/parsers.test.js

Co-authored-by: Thijs Louisse <[email protected]>

---------

Co-authored-by: Thijs Louisse <[email protected]>
  • Loading branch information
gerjanvangeest and tlouisse authored Apr 10, 2024
1 parent efcd4bf commit e8e9c07
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/ninety-rats-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lion/ui': patch
---

[input-amount] returns Unparseable as a modelValue if a wrong value has been entered
2 changes: 1 addition & 1 deletion docs/components/input-amount/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A web component based on the generic text input field. Its purpose is to provide

For formatting, we use [Intl NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat) with some overrides.

For parsing user input, we provide our own parser that takes into account a number of heuristics, locale and ignores invalid characters.
For parsing user input, we provide our own parser that takes into account locale, a number of heuristics and allows for pasting amount strings like 'EUR 100,00'.
Valid characters are digits and separators. Formatting happens on-blur.

If there are no valid characters in the input whatsoever, it will provide an error feedback.
Expand Down
14 changes: 11 additions & 3 deletions packages/ui/components/input-amount/src/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { parseNumber, getFractionDigits } from '@lion/ui/localize-no-side-effect
* @return {number} new value with rounded up decimals
*/
function round(value, decimals) {
if (typeof decimals === 'undefined') {
const numberContainsExponent = value?.toString().includes('e');
if (typeof decimals === 'undefined' || numberContainsExponent) {
return Number(value);
}
return Number(`${Math.round(Number(`${value}e${decimals}`))}e-${decimals}`);
Expand All @@ -30,10 +31,17 @@ function round(value, decimals) {
* @param {FormatOptions} [givenOptions] Locale Options
*/
export function parseAmount(value, givenOptions) {
const unmatchedInput = value.match(/[^0-9,.\- ]/g);
// for the full paste behavior documentation:
// ./docs/components/input-amount/use-cases.md#paste-behavior
if (unmatchedInput && givenOptions?.mode !== 'pasted') {
return undefined;
}

const number = parseNumber(value, givenOptions);

if (typeof number !== 'number') {
return number;
if (typeof number !== 'number' || Number.isNaN(number)) {
return undefined;
}

/** @type {FormatOptions} */
Expand Down
11 changes: 11 additions & 0 deletions packages/ui/components/input-amount/test/formatters.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,15 @@ describe('formatAmount()', () => {
localizeManager.locale = 'nl-NL';
expect(formatAmount(12345678)).to.equal('12.345.678,00');
});

// TODO: make it work with big numbers, e.g. make use of BigInt
it.skip('rounds up big numbers', async () => {
expect(formatAmount(1e21, { locale: 'en-GB', currency: 'EUR' })).to.equal(
'1,000,000,000,000,000,000,000.00',
);
// eslint-disable-next-line no-loss-of-precision
expect(formatAmount(12345678987654321.42, { locale: 'en-GB', currency: 'EUR' })).to.equal(
'12,345,678,987,654,321.42',
);
});
});
36 changes: 32 additions & 4 deletions packages/ui/components/input-amount/test/parsers.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { expect } from '@open-wc/testing';
import { localize } from '@lion/ui/localize.js';

import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js';
import { localizeTearDown } from '@lion/ui/localize-test-helpers.js';
import { parseAmount } from '@lion/ui/input-amount.js';

describe('parseAmount()', async () => {
const localizeManager = getLocalizeManager();

beforeEach(() => {
localizeManager.locale = 'en-GB';
});

afterEach(() => {
localizeTearDown();
});

it('with currency set to correct amount of decimals', async () => {
localize.locale = 'en-GB';
expect(
parseAmount('1.015', {
currency: 'EUR',
Expand All @@ -29,7 +38,26 @@ describe('parseAmount()', async () => {
});

it('with no currency keeps all decimals', async () => {
localize.locale = 'en-GB';
expect(parseAmount('1.015')).to.equal(1.015);
});

// TODO: make it work with big numbers, e.g. make use of BigInt
it.skip('rounds up big numbers', async () => {
// eslint-disable-next-line no-loss-of-precision
expect(parseAmount('999999999999999999999,42')).to.equal(999999999999999999999.42);
// eslint-disable-next-line no-loss-of-precision
expect(parseAmount('12,345,678,987,654,321.42')).to.equal(12345678987654321.42);
});

it('returns undefined if an invalid value is entered', async () => {
expect(parseAmount('foo')).to.equal(undefined);
expect(parseAmount('foo1')).to.equal(undefined);
expect(parseAmount('EUR 1,50')).to.equal(undefined);
expect(parseAmount('--1')).to.equal(undefined);
});

it('ignores letters when "pasted" mode used', async () => {
expect(parseAmount('foo1', { mode: 'pasted' })).to.equal(1);
expect(parseAmount('EUR 1,50', { mode: 'pasted' })).to.equal(1.5);
});
});
2 changes: 1 addition & 1 deletion packages/ui/components/localize/src/number/formatNumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function formatNumber(number, options = /** @type {FormatOptions} */ ({})
}
let printNumberOfParts = '';
// update numberOfParts because there may be some parts added
const numberOfParts = formattedToParts && formattedToParts.length;
const numberOfParts = formattedToParts ? formattedToParts.length : 0;
for (let i = 0; i < numberOfParts; i += 1) {
const part = /** @type {FormatNumberPart} */ (formattedToParts[i]);
printNumberOfParts += part.value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,15 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t
});
});

// TODO: make it work with big numbers, e.g. make use of BigInt
it.skip('can handle big numbers', async () => {
expect(formatNumber(1e21)).to.equal('1,000,000,000,000,000,000,000');
// eslint-disable-next-line no-loss-of-precision
expect(formatNumber(999999999999999999999.42)).to.equal('999,999,999,999,999,999,999.42');
// eslint-disable-next-line no-loss-of-precision
expect(formatNumber(12345678987654321.42)).to.equal('12,345,678,987,654,321.42');
});

describe('normalization', () => {
describe('en-GB', () => {
it('supports basics', () => {
Expand Down

0 comments on commit e8e9c07

Please sign in to comment.