Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
kosmydel committed Nov 6, 2023
1 parent ac22a25 commit 685df2d
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 192 deletions.
4 changes: 2 additions & 2 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import removeInvisibleCharacters from '@libs/removeInvisibleCharacters';
import StringUtils from '@libs/StringUtils';
import Visibility from '@libs/Visibility';
import stylePropTypes from '@styles/stylePropTypes';
import styles from '@styles/styles';
Expand Down Expand Up @@ -131,7 +131,7 @@ function Form(props) {
const trimmedStringValues = {};
_.each(values, (inputValue, inputID) => {
if (_.isString(inputValue)) {
trimmedStringValues[inputID] = removeInvisibleCharacters(inputValue);
trimmedStringValues[inputID] = StringUtils.removeInvisibleCharacters(inputValue);
} else {
trimmedStringValues[inputID] = inputValue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Form/FormProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import _ from 'underscore';
import networkPropTypes from '@components/networkPropTypes';
import {withNetwork} from '@components/OnyxProvider';
import compose from '@libs/compose';
import removeInvisibleCharacters from '@libs/removeInvisibleCharacters';
import StringUtils from '@libs/StringUtils';
import Visibility from '@libs/Visibility';
import stylePropTypes from '@styles/stylePropTypes';
import * as FormActions from '@userActions/FormActions';
Expand Down Expand Up @@ -117,7 +117,7 @@ function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnC
const trimmedStringValues = {};
_.each(values, (inputValue, inputID) => {
if (_.isString(inputValue)) {
trimmedStringValues[inputID] = removeInvisibleCharacters(inputValue);
trimmedStringValues[inputID] = StringUtils.removeInvisibleCharacters(inputValue);
} else {
trimmedStringValues[inputID] = inputValue;
}
Expand Down
48 changes: 47 additions & 1 deletion src/libs/StringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,50 @@ function sanitizeString(str: string): string {
return _.deburr(str).toLowerCase().replaceAll(CONST.REGEX.NON_ALPHABETIC_AND_NON_LATIN_CHARS, '');
}

export default {sanitizeString};
/**
* Check if the string would be empty if all invisible characters were removed.
*/
function isEmptyString(value: string): boolean {
// \p{C} matches all 'Other' characters
// \p{Z} matches all separators (spaces etc.)
// Source: http://www.unicode.org/reports/tr18/#General_Category_Property
let transformed = value.replace(CONST.REGEX.INVISIBLE_CHARACTERS_GROUPS, '');

// Remove other invisible characters that are not in the above unicode categories
transformed = transformed.replace(CONST.REGEX.OTHER_INVISIBLE_CHARACTERS, '');

// Check if after removing invisible characters the string is empty
return transformed === '';
}

/**
* Remove invisible characters from a string except for spaces and format characters for emoji, and trim it.
*/
function removeInvisibleCharacters(value: string): string {
let result = value;

// Remove spaces:
// - \u200B: zero-width space
// - \u00A0: non-breaking space
// - \u2060: word joiner
result = result.replace(/[\u200B\u00A0\u2060]/g, '');

// Remove all characters from the 'Other' (C) category except for format characters (Cf)
// because some of them are used for emojis
result = result.replace(/[\p{Cc}\p{Cs}\p{Co}\p{Cn}]/gu, '');

// Remove characters from the (Cf) category that are not used for emojis
result = result.replace(/[\u200E-\u200F]/g, '');

// Remove all characters from the 'Separator' (Z) category except for Space Separator (Zs)
result = result.replace(/[\p{Zl}\p{Zp}]/gu, '');

// If the result consist of only invisible characters, return an empty string
if (isEmptyString(result)) {
return '';
}

return result.trim();
}

export default {sanitizeString, isEmptyString, removeInvisibleCharacters};
4 changes: 2 additions & 2 deletions src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import CONST from '@src/CONST';
import {Report} from '@src/types/onyx';
import * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import * as CardUtils from './CardUtils';
import isEmptyString from './isEmptyString';
import * as LoginUtils from './LoginUtils';
import StringUtils from './StringUtils';

/**
* Implements the Luhn Algorithm, a checksum formula used to validate credit card
Expand Down Expand Up @@ -74,7 +74,7 @@ function isValidPastDate(date: string | Date): boolean {
*/
function isRequiredFulfilled(value: string | Date | unknown[] | Record<string, unknown>): boolean {
if (typeof value === 'string') {
return !isEmptyString(value);
return !StringUtils.isEmptyString(value);
}

if (isDate(value)) {
Expand Down
19 changes: 0 additions & 19 deletions src/libs/isEmptyString.ts

This file was deleted.

33 changes: 0 additions & 33 deletions src/libs/removeInvisibleCharacters.ts

This file was deleted.

120 changes: 60 additions & 60 deletions tests/unit/isEmptyString.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,92 @@
import _ from 'underscore';
import enEmojis from '../../assets/emojis/en';
import isEmpty from '../../src/libs/isEmptyString';
import StringUtils from '../../src/libs/StringUtils';

describe('libs/isEmpty', () => {
describe('libs/StringUtils.isEmptyString', () => {
it('basic tests', () => {
expect(isEmpty('test')).toBe(false);
expect(isEmpty('test test')).toBe(false);
expect(isEmpty('test test test')).toBe(false);
expect(isEmpty(' ')).toBe(true);
expect(StringUtils.isEmptyString('test')).toBe(false);
expect(StringUtils.isEmptyString('test test')).toBe(false);
expect(StringUtils.isEmptyString('test test test')).toBe(false);
expect(StringUtils.isEmptyString(' ')).toBe(true);
});
it('trim spaces', () => {
expect(isEmpty(' test')).toBe(false);
expect(isEmpty('test ')).toBe(false);
expect(isEmpty(' test ')).toBe(false);
expect(StringUtils.isEmptyString(' test')).toBe(false);
expect(StringUtils.isEmptyString('test ')).toBe(false);
expect(StringUtils.isEmptyString(' test ')).toBe(false);
});
it('remove invisible characters', () => {
expect(isEmpty('\u200B')).toBe(true);
expect(isEmpty('\u200B')).toBe(true);
expect(isEmpty('\u200B ')).toBe(true);
expect(isEmpty('\u200B \u200B')).toBe(true);
expect(isEmpty('\u200B \u200B ')).toBe(true);
expect(StringUtils.isEmptyString('\u200B')).toBe(true);
expect(StringUtils.isEmptyString('\u200B')).toBe(true);
expect(StringUtils.isEmptyString('\u200B ')).toBe(true);
expect(StringUtils.isEmptyString('\u200B \u200B')).toBe(true);
expect(StringUtils.isEmptyString('\u200B \u200B ')).toBe(true);
});
it('remove invisible characters (Cc)', () => {
expect(isEmpty('\u0000')).toBe(true);
expect(isEmpty('\u0001')).toBe(true);
expect(isEmpty('\u0009')).toBe(true);
expect(StringUtils.isEmptyString('\u0000')).toBe(true);
expect(StringUtils.isEmptyString('\u0001')).toBe(true);
expect(StringUtils.isEmptyString('\u0009')).toBe(true);
});
it('remove invisible characters (Cf)', () => {
expect(isEmpty('\u200E')).toBe(true);
expect(isEmpty('\u200F')).toBe(true);
expect(isEmpty('\u2060')).toBe(true);
expect(StringUtils.isEmptyString('\u200E')).toBe(true);
expect(StringUtils.isEmptyString('\u200F')).toBe(true);
expect(StringUtils.isEmptyString('\u2060')).toBe(true);
});
it('remove invisible characters (Cs)', () => {
expect(isEmpty('\uD800')).toBe(true);
expect(isEmpty('\uD801')).toBe(true);
expect(isEmpty('\uD802')).toBe(true);
expect(StringUtils.isEmptyString('\uD800')).toBe(true);
expect(StringUtils.isEmptyString('\uD801')).toBe(true);
expect(StringUtils.isEmptyString('\uD802')).toBe(true);
});
it('remove invisible characters (Co)', () => {
expect(isEmpty('\uE000')).toBe(true);
expect(isEmpty('\uE001')).toBe(true);
expect(isEmpty('\uE002')).toBe(true);
expect(StringUtils.isEmptyString('\uE000')).toBe(true);
expect(StringUtils.isEmptyString('\uE001')).toBe(true);
expect(StringUtils.isEmptyString('\uE002')).toBe(true);
});
it('remove invisible characters (Zl)', () => {
expect(isEmpty('\u2028')).toBe(true);
expect(isEmpty('\u2029')).toBe(true);
expect(isEmpty('\u202A')).toBe(true);
expect(StringUtils.isEmptyString('\u2028')).toBe(true);
expect(StringUtils.isEmptyString('\u2029')).toBe(true);
expect(StringUtils.isEmptyString('\u202A')).toBe(true);
});
it('basic check emojis not removed', () => {
expect(isEmpty('😀')).toBe(false);
expect(StringUtils.isEmptyString('😀')).toBe(false);
});
it('all emojis not removed', () => {
_.keys(enEmojis).forEach((key) => {
expect(isEmpty(key)).toBe(false);
expect(StringUtils.isEmptyString(key)).toBe(false);
});
});
it('remove invisible characters (editpad)', () => {
expect(isEmpty('\u0020')).toBe(true);
expect(isEmpty('\u00A0')).toBe(true);
expect(isEmpty('\u2000')).toBe(true);
expect(isEmpty('\u2001')).toBe(true);
expect(isEmpty('\u2002')).toBe(true);
expect(isEmpty('\u2003')).toBe(true);
expect(isEmpty('\u2004')).toBe(true);
expect(isEmpty('\u2005')).toBe(true);
expect(isEmpty('\u2006')).toBe(true);
expect(isEmpty('\u2007')).toBe(true);
expect(isEmpty('\u2008')).toBe(true);
expect(isEmpty('\u2009')).toBe(true);
expect(isEmpty('\u200A')).toBe(true);
expect(isEmpty('\u2028')).toBe(true);
expect(isEmpty('\u205F')).toBe(true);
expect(isEmpty('\u3000')).toBe(true);
expect(isEmpty(' ')).toBe(true);
expect(StringUtils.isEmptyString('\u0020')).toBe(true);
expect(StringUtils.isEmptyString('\u00A0')).toBe(true);
expect(StringUtils.isEmptyString('\u2000')).toBe(true);
expect(StringUtils.isEmptyString('\u2001')).toBe(true);
expect(StringUtils.isEmptyString('\u2002')).toBe(true);
expect(StringUtils.isEmptyString('\u2003')).toBe(true);
expect(StringUtils.isEmptyString('\u2004')).toBe(true);
expect(StringUtils.isEmptyString('\u2005')).toBe(true);
expect(StringUtils.isEmptyString('\u2006')).toBe(true);
expect(StringUtils.isEmptyString('\u2007')).toBe(true);
expect(StringUtils.isEmptyString('\u2008')).toBe(true);
expect(StringUtils.isEmptyString('\u2009')).toBe(true);
expect(StringUtils.isEmptyString('\u200A')).toBe(true);
expect(StringUtils.isEmptyString('\u2028')).toBe(true);
expect(StringUtils.isEmptyString('\u205F')).toBe(true);
expect(StringUtils.isEmptyString('\u3000')).toBe(true);
expect(StringUtils.isEmptyString(' ')).toBe(true);
});
it('other tests', () => {
expect(isEmpty('\u200D')).toBe(true);
expect(isEmpty('\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F')).toBe(false);
expect(isEmpty('\uD83C')).toBe(true);
expect(isEmpty('\uDFF4')).toBe(true);
expect(isEmpty('\uDB40')).toBe(true);
expect(isEmpty('\uDC67')).toBe(true);
expect(isEmpty('\uDC62')).toBe(true);
expect(isEmpty('\uDC65')).toBe(true);
expect(isEmpty('\uDC6E')).toBe(true);
expect(isEmpty('\uDC67')).toBe(true);
expect(isEmpty('\uDC7F')).toBe(true);
expect(StringUtils.isEmptyString('\u200D')).toBe(true);
expect(StringUtils.isEmptyString('\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F')).toBe(false);
expect(StringUtils.isEmptyString('\uD83C')).toBe(true);
expect(StringUtils.isEmptyString('\uDFF4')).toBe(true);
expect(StringUtils.isEmptyString('\uDB40')).toBe(true);
expect(StringUtils.isEmptyString('\uDC67')).toBe(true);
expect(StringUtils.isEmptyString('\uDC62')).toBe(true);
expect(StringUtils.isEmptyString('\uDC65')).toBe(true);
expect(StringUtils.isEmptyString('\uDC6E')).toBe(true);
expect(StringUtils.isEmptyString('\uDC67')).toBe(true);
expect(StringUtils.isEmptyString('\uDC7F')).toBe(true);

// A special test, an invisible character from other Unicode categories than format and control
expect(isEmpty('\u3164')).toBe(true);
expect(StringUtils.isEmptyString('\u3164')).toBe(true);
});
});
Loading

0 comments on commit 685df2d

Please sign in to comment.