Skip to content

Commit

Permalink
[WNMGDS-62] Return original value in Mask component when unable to ma…
Browse files Browse the repository at this point in the history
…sk (#435)

* Return original value if no numerics found in numeric masks

* Change maskValue and unmask to return the same value back if masking is not possible

* Change unmask to unmaskValue

* Update tests
  • Loading branch information
bernardwang authored Jul 3, 2019
1 parent eacedb2 commit 0acf756
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 71 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/components/TextField/Mask.example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from 'react';
import ReactDOM from 'react-dom';

function handleBlur(evt, mask) {
console.log('Unmasked value:', unmaskValue(evt.target.value, mask));
console.log('Unmasked value: ', unmaskValue(evt.target.value, mask));
}

class ControlledCurrencyField extends React.PureComponent {
Expand Down
70 changes: 39 additions & 31 deletions packages/core/src/components/TextField/Mask.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const deliminatedMaskRegex = {
*/
function deliminateRegexGroups(value, rx) {
const matches = toDigitsAndAsterisks(value).match(rx);

if (matches && matches.length > 1) {
value = matches
.slice(1)
Expand Down Expand Up @@ -84,9 +83,6 @@ function toDigits(value) {
* @returns {Number}
*/
function toNumber(value) {
if (typeof value !== 'string') return value;
if (!value.match(/\d/)) return undefined;

const sign = value.charAt(0) === '-' ? -1 : 1;
const parts = value.split('.');
// This assumes if the user adds a "." it should be a float. If we want it to
Expand All @@ -103,24 +99,39 @@ function toNumber(value) {
}

/**
* Returns the value with additional masking characters
* Determines if a value is a valid string with numeric digits
* @param {String} value
* @returns {String}
* @param {String} mask
* @returns {Boolean}
*/
export function maskValue(value = '', mask) {
function isValueMaskable(value, mask) {
if (value && typeof value === 'string') {
value = value.trim();
const hasDigits = value.match(/\d/);
const hasDigitsAsterisks = value.match(/[\d*]/g);
if (hasDigits || (hasDigitsAsterisks && mask === 'ssn')) {
return true;
}
}
return false;
}

/**
* Returns the value with additional masking characters, or the same value back if invalid numeric string
* @param {String} value
* @returns {String}
*/
export function maskValue(value = '', mask) {
if (isValueMaskable(value, mask)) {
if (mask === 'currency') {
// Format number with commas. If the number includes a decimal,
// ensure it includes two decimal points
const number = toNumber(value);
if (number === undefined) {
value = '';
} else {
if (number !== undefined) {
value = stringWithFixedDigits(number.toLocaleString('en-US'));
}
} else if (Object.keys(deliminatedMaskRegex).includes(mask)) {
} else if (deliminatedMaskRegex[mask]) {
// Use deliminator regex to mask value and remove unwanted characters
// If the regex does not match, return the numeric digits.
value = deliminateRegexGroups(value, deliminatedMaskRegex[mask]);
}
}
Expand Down Expand Up @@ -177,7 +188,10 @@ export class Mask extends React.PureComponent {
// given and what we have locally don't match, that means the controlling
// component has made its own unrelated change, so we should update our
// state and mask this new value.
if (unmask(fieldProps.value, mask) !== unmask(this.state.value, mask)) {
if (
unmaskValue(fieldProps.value, mask) !==
unmaskValue(this.state.value, mask)
) {
const value = maskValue(fieldProps.value || '', mask);
this.setState({ value }); // eslint-disable-line react/no-did-update-set-state
}
Expand Down Expand Up @@ -260,29 +274,23 @@ Mask.propTypes = {
};

/**
* Remove mask characters from value
* Remove mask characters from value, or the same value back if invalid numeric string
* @param {String} value
* @param {String} mask
* @returns {String}
*/
export function unmask(value, mask) {
if (!value || typeof value !== 'string') return value;
const rawValue = value;
value = value.trim();

if (mask === 'currency') {
// Preserve only digits, decimal point, or negative symbol
const matches = value.match(/^-|[\d.]/g);
if (matches) {
value = matches.join('');
} else {
value = '';
export function unmaskValue(value, mask) {
if (isValueMaskable(value, mask)) {
if (mask === 'currency') {
// Preserve only digits, decimal point, or negative symbol
const matches = value.match(/^-|[\d.]/g);
if (matches) {
value = matches.join('');
}
} else if (deliminatedMaskRegex[mask]) {
// Remove the deliminators and revert to single ungrouped string
value = toDigitsAndAsterisks(value);
}
} else if (Object.keys(deliminatedMaskRegex).includes(mask)) {
// Remove the deliminators and revert to single ungrouped string
value = toDigitsAndAsterisks(value);
} else {
return rawValue;
}

return value;
Expand Down
74 changes: 37 additions & 37 deletions packages/core/src/components/TextField/Mask.test.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Mask, { unmask } from './Mask';
import Mask, { unmaskValue } from './Mask';
import { mount, shallow } from 'enzyme';
import React from 'react';

Expand Down Expand Up @@ -101,7 +101,7 @@ describe('Mask', function() {
<input
name="foo"
type="text"
value={unmask(event.target.value, 'currency')}
value={unmaskValue(event.target.value, 'currency')}
/>
)
});
Expand Down Expand Up @@ -332,72 +332,72 @@ describe('Mask', function() {
});
});

describe('unmask', () => {
describe('unmaskValue', () => {
it('returns value when mask is undefined', () => {
expect(unmask(' 1,234 Foo ')).toBe(' 1,234 Foo ');
expect(unmaskValue(' 1,234 Foo ')).toBe(' 1,234 Foo ');
});

it('returns value when mask is unknown', () => {
expect(unmask('1,234', 'foo')).toBe('1,234');
expect(unmaskValue('1,234', 'foo')).toBe('1,234');
});

it('exits when value is undefined or null', () => {
expect(unmask()).toBeUndefined();
expect(unmask(null)).toBeNull();
expect(unmaskValue()).toBeUndefined();
expect(unmaskValue(null)).toBeNull();
});

it('returns empty string when there are no numeric characters in the value', () => {
expect(unmask('banana', 'currency')).toBe('');
expect(unmask('banana', 'zip')).toBe('');
expect(unmask('banana', 'ssn')).toBe('');
expect(unmask('banana', 'phone')).toBe('');
it('returns same string back when there are no numeric characters in the value', () => {
expect(unmaskValue('banana', 'currency')).toBe('banana');
expect(unmaskValue('banana', 'zip')).toBe('banana');
expect(unmaskValue('banana', 'ssn')).toBe('banana');
expect(unmaskValue('banana', 'phone')).toBe('banana');
});

it('returns just the numbers when there is other garbage mixed in', () => {
expect(unmask('b4n4n4', 'currency')).toBe('444');
expect(unmask('b4n4n4', 'zip')).toBe('444');
expect(unmask('b4n4n4', 'ssn')).toBe('444');
expect(unmask('b4n4n4', 'phone')).toBe('444');

expect(unmask('a1.b2c3', 'currency')).toBe('1.23');
expect(unmask('1,,00.b', 'currency')).toBe('100.');
expect(unmask('1-1-1-2-3-4', 'zip')).toBe('111234');
expect(unmask('4---31', 'ssn')).toBe('431');
expect(unmask('--2-3444', 'phone')).toBe('23444');
expect(unmaskValue('b4n4n4', 'currency')).toBe('444');
expect(unmaskValue('b4n4n4', 'zip')).toBe('444');
expect(unmaskValue('b4n4n4', 'ssn')).toBe('444');
expect(unmaskValue('b4n4n4', 'phone')).toBe('444');

expect(unmaskValue('a1.b2c3', 'currency')).toBe('1.23');
expect(unmaskValue('1,,00.b', 'currency')).toBe('100.');
expect(unmaskValue('1-1-1-2-3-4', 'zip')).toBe('111234');
expect(unmaskValue('4---31', 'ssn')).toBe('431');
expect(unmaskValue('--2-3444', 'phone')).toBe('23444');
});

it('removes mask from currency value', () => {
const name = 'currency';

expect(unmask('', name)).toBe('');
expect(unmask(' 1,234 ', name)).toBe('1234'); // whitespace
expect(unmask('1,234', name)).toBe('1234');
expect(unmask('1,234.5', name)).toBe('1234.5');
expect(unmask('1,234,000.50', name)).toBe('1234000.50');
expect(unmask('-1,234,000.50', name)).toBe('-1234000.50');
expect(unmaskValue('', name)).toBe('');
expect(unmaskValue(' 1,234 ', name)).toBe('1234'); // whitespace
expect(unmaskValue('1,234', name)).toBe('1234');
expect(unmaskValue('1,234.5', name)).toBe('1234.5');
expect(unmaskValue('1,234,000.50', name)).toBe('1234000.50');
expect(unmaskValue('-1,234,000.50', name)).toBe('-1234000.50');
});

it('removes mask from zip code', () => {
const name = 'zip';

expect(unmask('', name)).toBe('');
expect(unmask(' 12345 ', name)).toBe('12345');
expect(unmask('12345-6789', name)).toBe('123456789');
expect(unmaskValue('', name)).toBe('');
expect(unmaskValue(' 12345 ', name)).toBe('12345');
expect(unmaskValue('12345-6789', name)).toBe('123456789');
});

it('removes mask from ssn value', () => {
const name = 'ssn';

expect(unmask('', name)).toBe('');
expect(unmask(' 123-45-6789 ', name)).toBe('123456789');
expect(unmask('123456789', name)).toBe('123456789');
expect(unmask('***-**-6789', name)).toBe('*****6789');
expect(unmaskValue('', name)).toBe('');
expect(unmaskValue(' 123-45-6789 ', name)).toBe('123456789');
expect(unmaskValue('123456789', name)).toBe('123456789');
expect(unmaskValue('***-**-6789', name)).toBe('*****6789');
});

it('removes mask from phone number', () => {
const name = 'phone';

expect(unmask('', name)).toBe('');
expect(unmask(' 123-456-7890 ', name)).toBe('1234567890');
expect(unmaskValue('', name)).toBe('');
expect(unmaskValue(' 123-456-7890 ', name)).toBe('1234567890');
});
});
4 changes: 2 additions & 2 deletions packages/core/src/components/TextField/TextField.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Mask, { unmask } from './Mask';
import FormLabel from '../FormLabel/FormLabel';
import Mask from './Mask';
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import uniqueId from 'lodash.uniqueid';

export { unmask as unmaskValue };
export { unmaskValue } from './Mask';

/**
* A `TextField` component renders an input field as well as supporting UI
Expand Down

0 comments on commit 0acf756

Please sign in to comment.