Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6612,7 +6612,6 @@ const CONST = {
REGEX: {
SPECIAL_CHARS_WITHOUT_NEWLINE: /((?!\n)[()-\s\t])/g,
DIGITS_AND_PLUS: /^\+?[0-9]*$/,
ALPHABETIC_AND_LATIN_CHARS: /^[\p{Script=Latin} ]*$/u,
// Matches complete tags like <script> or <123a>; lone < or > are allowed.
NAME_ON_CARD_INVALID_CHARS: /<[^>]*>/,
NON_ALPHABETIC_AND_NON_LATIN_CHARS: /[^\p{Script=Latin}]/gu,
Expand Down
10 changes: 5 additions & 5 deletions src/components/SubStepForms/FullNameStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {SubPageProps} from '@hooks/useSubPage/types';
import useThemeStyles from '@hooks/useThemeStyles';

import type {ForwardedFSClassProps} from '@libs/Fullstory/types';
import {doesContainReservedWord, getFieldRequiredErrors, isRequiredFulfilled, isValidLegalName} from '@libs/ValidationUtils';
import {doesContainReservedWord, getFieldRequiredErrors, isRequiredFulfilled, isValidDisplayName} from '@libs/ValidationUtils';

import HelpLinks from '@pages/ReimbursementAccount/USD/Requestor/PersonalInfo/HelpLinks';

Expand Down Expand Up @@ -89,9 +89,9 @@ function FullNameStepImpl({
if (!isRequiredFulfilled(firstName)) {
// @ts-expect-error type mismatch to be fixed
errors[firstNameInputID] = translate('common.error.fieldRequired');
} else if (!isValidLegalName(firstName)) {
} else if (!isValidDisplayName(firstName)) {
// @ts-expect-error type mismatch to be fixed
errors[firstNameInputID] = translate('privatePersonalDetails.error.hasInvalidCharacter');
errors[firstNameInputID] = translate('privatePersonalDetails.error.cannotIncludeCommaOrSemicolon');
} else if (firstName.length > CONST.LEGAL_NAME.MAX_LENGTH) {
// @ts-expect-error type mismatch to be fixed
errors[firstNameInputID] = translate('common.error.characterLimitExceedCounter', firstName.length, CONST.LEGAL_NAME.MAX_LENGTH);
Expand All @@ -106,9 +106,9 @@ function FullNameStepImpl({
if (!isRequiredFulfilled(lastName)) {
// @ts-expect-error type mismatch to be fixed
errors[lastNameInputID] = translate('common.error.fieldRequired');
} else if (!isValidLegalName(lastName)) {
} else if (!isValidDisplayName(lastName)) {
// @ts-expect-error type mismatch to be fixed
errors[lastNameInputID] = translate('privatePersonalDetails.error.hasInvalidCharacter');
errors[lastNameInputID] = translate('privatePersonalDetails.error.cannotIncludeCommaOrSemicolon');
} else if (lastName.length > CONST.LEGAL_NAME.MAX_LENGTH) {
// @ts-expect-error type mismatch to be fixed
errors[lastNameInputID] = translate('common.error.characterLimitExceedCounter', lastName.length, CONST.LEGAL_NAME.MAX_LENGTH);
Expand Down
8 changes: 0 additions & 8 deletions src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,6 @@ function isValidDisplayName(name: string): boolean {
return !name.includes(',') && !name.includes(';');
}

/**
* Checks that the provided legal name doesn't contain special characters
*/
function isValidLegalName(name: string): boolean {
return CONST.REGEX.ALPHABETIC_AND_LATIN_CHARS.test(name);
}

/**
* Checks that the provided name on card does not contain HTML-like tags (e.g. `<script>`).
* Lone `<` or `>` characters are allowed; the backend sanitizes the value before embossing.
Expand Down Expand Up @@ -946,7 +939,6 @@ export {
isValidCompanyName,
isValidNameOnCard,
isValidDisplayName,
isValidLegalName,
doesContainReservedWord,
isNumeric,
isValidAccountRoute,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import type {SubPageProps} from '@hooks/useSubPage/types';

import {getFieldRequiredErrors, isValidLegalName} from '@libs/ValidationUtils';
import {getFieldRequiredErrors, isValidDisplayName} from '@libs/ValidationUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -24,8 +24,8 @@ function Name({onNext, onMove, isEditing}: SubPageProps) {
const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.ENTER_SINGER_INFO_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.ENTER_SINGER_INFO_FORM> => {
const errors = getFieldRequiredErrors(values, [inputID], translate);

if (values[inputID] && !isValidLegalName(String(values[inputID]))) {
errors[inputID] = translate('bankAccount.error.fullName');
if (values[inputID] && !isValidDisplayName(String(values[inputID]))) {
errors[inputID] = translate('privatePersonalDetails.error.cannotIncludeCommaOrSemicolon');
}

return errors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useOnyx from '@hooks/useOnyx';
import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit';
import type {SubPageProps} from '@hooks/useSubPage/types';

import {getFieldRequiredErrors, isValidLegalName} from '@libs/ValidationUtils';
import {getFieldRequiredErrors, isValidDisplayName} from '@libs/ValidationUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -27,8 +27,8 @@ function Name({onNext, onMove, isEditing}: NameProps) {
const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM> => {
const errors = getFieldRequiredErrors(values, [SIGNER_FULL_NAME], translate);

if (values[SIGNER_FULL_NAME] && !isValidLegalName(String(values[SIGNER_FULL_NAME]))) {
errors[SIGNER_FULL_NAME] = translate('bankAccount.error.fullName');
if (values[SIGNER_FULL_NAME] && !isValidDisplayName(String(values[SIGNER_FULL_NAME]))) {
errors[SIGNER_FULL_NAME] = translate('privatePersonalDetails.error.cannotIncludeCommaOrSemicolon');
}

return errors;
Expand Down
35 changes: 27 additions & 8 deletions tests/unit/ValidationUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
isValidEmailWithTLD,
isValidExpirationDate,
isValidInputLength,
isValidLegalName,
isValidDisplayName,
isValidPastDate,
isValidPaymentZipCode,
isValidPersonName,
Expand Down Expand Up @@ -473,15 +473,34 @@ describe('ValidationUtils', () => {
});
});

describe('ValidateLegalName', () => {
test('Valid legal name', () => {
expect(isValidLegalName('test name')).toBe(true);
expect(isValidLegalName(`X Æ A test`)).toBe(true);
describe('isValidDisplayName', () => {
test.each(['test name', 'X Æ A test', 'a hyphenated-name', "O'Connor", 'άλφα', '李明', 'X Æ A-12'])('accepts names without commas or semicolons: %s', (name) => {
// Given a name containing characters supported by legal-name forms
// When checking the shared name-character rule
const isValid = isValidDisplayName(name);

// Then punctuation and non-Latin scripts do not block the name
expect(isValid).toBe(true);
});

test('Invalid legal name', () => {
expect(isValidLegalName(`a hyphenated-name`)).toBe(false);
expect(isValidLegalName('άλφα')).toBe(false);
test.each(['Smith, Jones', 'Smith; Jones'])('rejects names containing a comma or semicolon: %s', (name) => {
// Given a name containing a prohibited separator
// When checking the shared name-character rule
const isValid = isValidDisplayName(name);

// Then the form can show the comma-or-semicolon error
expect(isValid).toBe(false);
});

test.each(['', ' '])('leaves empty-name rejection to required-field validation: %s', (name) => {
// Given an empty or whitespace-only name
// When applying character and required-field validation separately
const hasValidCharacters = isValidDisplayName(name);
const isRequiredValuePresent = isRequiredFulfilled(name);

// Then the required-field rule still rejects the name
expect(hasValidCharacters).toBe(true);
expect(isRequiredValuePresent).toBe(false);
});
});

Expand Down
Loading