Skip to content

Commit

Permalink
fix(kit): Number fails to parse small number on blur (exponential n…
Browse files Browse the repository at this point in the history
…otation problem) (#339)
  • Loading branch information
nsbarsukov authored Jun 20, 2023
1 parent d1452b5 commit 7f83a7f
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,28 @@ describe('Number | precision', () => {
});
});

it('keeps untouched decimal part if `precision: Infinity`', () => {
openNumberPage('decimalSeparator=,&precision=Infinity');
describe('keeps untouched decimal part if `precision: Infinity`', () => {
it('0,123456789', () => {
openNumberPage('decimalSeparator=,&precision=Infinity');

cy.get('@input')
.type('0,123456789')
.should('have.value', '0,123456789')
.should('have.prop', 'selectionStart', '0,123456789'.length)
.should('have.prop', 'selectionEnd', '0,123456789'.length);
cy.get('@input')
.type('0,123456789')
.should('have.value', '0,123456789')
.should('have.prop', 'selectionStart', '0,123456789'.length)
.should('have.prop', 'selectionEnd', '0,123456789'.length);
});

it('0,0000000001', () => {
openNumberPage('decimalSeparator=,&precision=Infinity');

cy.get('@input')
.type('0,0000000001') // 1e-10
.should('have.value', '0,0000000001')
.should('have.prop', 'selectionStart', '0,0000000001'.length)
.should('have.prop', 'selectionEnd', '0,0000000001'.length)
.blur()
.wait(100) // to be sure that value is not changed even in case of some async validation
.should('have.value', '0,0000000001');
});
});
});
15 changes: 9 additions & 6 deletions projects/kit/src/lib/masks/number/plugins/min-max.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {MaskitoPlugin} from '@maskito/core';
import {MaskitoPlugin, maskitoTransform} from '@maskito/core';

import {maskitoEventHandler} from '../../../plugins';
import {clamp} from '../../../utils';
import {maskitoParseNumber} from '../utils';
import {maskitoParseNumber, stringifyNumberWithoutExp} from '../utils';

/**
* This plugin is connected with {@link createMinMaxPostprocessor}:
Expand All @@ -17,12 +17,15 @@ export function createMinMaxPlugin({
max: number;
decimalSeparator: string;
}): MaskitoPlugin {
return maskitoEventHandler('blur', element => {
return maskitoEventHandler('blur', (element, options) => {
const parsedNumber = maskitoParseNumber(element.value, decimalSeparator);
const newValue = clamp(parsedNumber, min, max).toString();
const clampedNumber = clamp(parsedNumber, min, max);

if (newValue !== element.value) {
element.value = newValue;
if (parsedNumber !== clampedNumber) {
element.value = maskitoTransform(
stringifyNumberWithoutExp(clampedNumber),
options,
);
element.dispatchEvent(new Event('input'));
}
});
Expand Down
1 change: 1 addition & 0 deletions projects/kit/src/lib/masks/number/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './generate-mask-expression';
export * from './get-default-pseudo-separators';
export * from './parse-number';
export * from './stringify-number-without-exp';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Convert number to string with replacing exponent part on decimals
*
* @param value the number
* @return string representation of a number
*/
export function stringifyNumberWithoutExp(value: number): string {
const valueAsString = String(value);
const [numberPart, expPart] = valueAsString.split(`e-`);

let valueWithoutExp = valueAsString;

if (expPart) {
const [, fractionalPart] = numberPart.split(`.`);
const decimalDigits = Number(expPart) + (fractionalPart?.length || 0);

valueWithoutExp = value.toFixed(decimalDigits);
}

return valueWithoutExp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {stringifyNumberWithoutExp} from '../stringify-number-without-exp';

describe(`number converting to string without exponent`, () => {
it(`value with exponent and without fractional part and precision > 6`, () => {
expect(stringifyNumberWithoutExp(1e-10)).toBe(`0.0000000001`);
});

it(`value with exponent and fractional part and precision > 6`, () => {
expect(stringifyNumberWithoutExp(1.23e-8)).toBe(`0.0000000123`);
});

it(`negative value with exponent and fractional part and precision > 6`, () => {
expect(stringifyNumberWithoutExp(-1.23e-8)).toBe(`-0.0000000123`);
});

it(`integer value`, () => {
expect(stringifyNumberWithoutExp(1)).toBe(`1`);
});

it(`integer value with zeros`, () => {
expect(stringifyNumberWithoutExp(100)).toBe(`100`);
});

it(`fractional value without exponent`, () => {
expect(stringifyNumberWithoutExp(0.111)).toBe(`0.111`);
});

it(`negative integer value`, () => {
expect(stringifyNumberWithoutExp(-100)).toBe(`-100`);
});

it(`negative fractional value`, () => {
expect(stringifyNumberWithoutExp(-1e-2)).toBe(`-0.01`);
});

it(`fractional value with exponent and precision equals 4`, () => {
expect(stringifyNumberWithoutExp(2.23e-2)).toBe(`0.0223`);
});
});
2 changes: 1 addition & 1 deletion projects/kit/src/lib/plugins/caret-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {getFocused} from '../utils';
export function maskitoCaretGuard(
guard: (value: string) => [from: number, to: number],
): MaskitoPlugin {
return (element: HTMLInputElement | HTMLTextAreaElement): (() => void) => {
return element => {
const document = element.ownerDocument;
const listener = (): void => {
if (getFocused(document) !== element) {
Expand Down
11 changes: 7 additions & 4 deletions projects/kit/src/lib/plugins/event-handler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {MaskitoPlugin} from '@maskito/core';
import {MaskitoOptions, MaskitoPlugin} from '@maskito/core';

export function maskitoEventHandler(
name: string,
handler: (element: HTMLInputElement | HTMLTextAreaElement) => void,
handler: (
element: HTMLInputElement | HTMLTextAreaElement,
options: Required<MaskitoOptions>,
) => void,
): MaskitoPlugin {
return (element: HTMLInputElement | HTMLTextAreaElement): (() => void) => {
const listener = (): void => handler(element);
return (element, options) => {
const listener = (): void => handler(element, options);

element.addEventListener(name, listener);

Expand Down

0 comments on commit 7f83a7f

Please sign in to comment.