From 746e2a7e55d8ab79ef6b0d2fd96192ad1f363c6b Mon Sep 17 00:00:00 2001
From: Kevin Brian Bader <kevin.bader96@gmail.com>
Date: Tue, 10 Jun 2025 13:26:56 -0700
Subject: [PATCH 1/6] fix(validation): update merchant validation length check
 to bytes

---
 src/CONST.ts                                              | 2 +-
 .../SearchFiltersMerchantPage.tsx                         | 6 ++++--
 src/pages/iou/request/step/IOURequestStepMerchant.tsx     | 8 +++++---
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/CONST.ts b/src/CONST.ts
index f6aefe3cf254..f600a8e0a50f 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -526,7 +526,7 @@ const CONST = {
     // Prevents consecutive special characters or spaces like '--', '..', '((', '))', or '  '.
     REPEATED_SPECIAL_CHAR_PATTERN: /([-\s().])\1+/,
 
-    MERCHANT_NAME_MAX_LENGTH: 255,
+    MERCHANT_NAME_MAX_BYTES: 255,
 
     MASKED_PAN_PREFIX: 'XXXXXXXXXXXX',
 
diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
index 4a519d94aefc..22add2ba54af 100644
--- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
@@ -16,6 +16,7 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm';
+import {Buffer} from 'buffer/';
 
 function SearchFiltersMerchantPage() {
     const styles = useThemeStyles();
@@ -33,9 +34,10 @@ function SearchFiltersMerchantPage() {
     const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM>) => {
         const errors: FormInputErrors<typeof ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM> = {};
         const merchantValue = values.merchant.trim();
+        const merchantByteLength = Buffer.from(merchantValue).length;
 
-        if (merchantValue.length > CONST.MERCHANT_NAME_MAX_LENGTH) {
-            errors.merchant = translate('common.error.characterLimitExceedCounter', {length: merchantValue.length, limit: CONST.MERCHANT_NAME_MAX_LENGTH});
+        if (merchantByteLength > CONST.MERCHANT_NAME_MAX_BYTES) {
+            errors.merchant = translate('common.error.characterLimitExceedCounter', {length: merchantByteLength, limit: CONST.MERCHANT_NAME_MAX_BYTES});
         }
 
         return errors;
diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx
index 0d2980ac5fdc..e39a8d7fce3c 100644
--- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx
+++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx
@@ -17,6 +17,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type SCREENS from '@src/SCREENS';
 import INPUT_IDS from '@src/types/form/MoneyRequestMerchantForm';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import {Buffer} from 'buffer/';
 import DiscardChangesConfirmation from './DiscardChangesConfirmation';
 import StepScreenWrapper from './StepScreenWrapper';
 import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound';
@@ -60,15 +61,16 @@ function IOURequestStepMerchant({
     const validate = useCallback(
         (value: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM>) => {
             const errors: FormInputErrors<typeof ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM> = {};
+            const merchantByteLength = Buffer.from(value.moneyRequestMerchant).length;
 
             if (isMerchantRequired && !value.moneyRequestMerchant) {
                 errors.moneyRequestMerchant = translate('common.error.fieldRequired');
             } else if (isMerchantRequired && value.moneyRequestMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT) {
                 errors.moneyRequestMerchant = translate('iou.error.invalidMerchant');
-            } else if (value.moneyRequestMerchant.length > CONST.MERCHANT_NAME_MAX_LENGTH) {
+            } else if (merchantByteLength > CONST.MERCHANT_NAME_MAX_BYTES) {
                 errors.moneyRequestMerchant = translate('common.error.characterLimitExceedCounter', {
-                    length: value.moneyRequestMerchant.length,
-                    limit: CONST.MERCHANT_NAME_MAX_LENGTH,
+                    length: merchantByteLength,
+                    limit: CONST.MERCHANT_NAME_MAX_BYTES,
                 });
             }
 

From e59ce566d1658c0e37c10255433456b5e1640bd6 Mon Sep 17 00:00:00 2001
From: Kevin Brian Bader <kevin.bader96@gmail.com>
Date: Thu, 12 Jun 2025 14:05:53 -0700
Subject: [PATCH 2/6] update(refactor): added validation function and unit
 tests

---
 src/libs/ValidationUtils.ts                   | 23 +++++
 .../SearchFiltersMerchantPage.tsx             |  8 +-
 .../request/step/IOURequestStepMerchant.tsx   |  8 +-
 tests/unit/ValidationUtilsTest.ts             | 96 +++++++++++++++++++
 4 files changed, 127 insertions(+), 8 deletions(-)

diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index a550a8f2f049..83adba0e310d 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -1,3 +1,4 @@
+import {Buffer} from 'buffer';
 import {addYears, endOfMonth, format, isAfter, isBefore, isSameDay, isValid, isWithinInterval, parse, parseISO, startOfDay, subYears} from 'date-fns';
 import {PUBLIC_DOMAINS_SET, Str, Url} from 'expensify-common';
 import isEmpty from 'lodash/isEmpty';
@@ -657,6 +658,27 @@ function isValidRegistrationNumber(registrationNumber: string, country: Country
     }
 }
 
+/**
+ * Checks if the character length of an input string exceeds the specified length,
+ * returning `isValid` (boolean) and `byteLength` (number) to be used in dynamic error copy.
+ *
+ * @remarks
+ * This function uses `Buffer.from(inputValue).length` to calculate the byte size of the input string in UTF-8 encoding.
+ * Unlike JavaScript's `string.length` which counts UTF-16 code units (where some characters, like emojis or certain non-Latin characters, may use two code units),
+ * `Buffer` measures the actual byte size in UTF-8. This is important for non-Latin alphabets (e.g., Sanskrit, Chinese, or emojis) where characters often require more than one byte.
+ *
+ * For example:
+ * - Latin characters (e.g., 'a', 'b') use 1 byte each in UTF-8.
+ * - Sanskrit characters (e.g., 'क', 'ष') typically use 3 bytes each in UTF-8.
+ * - Emojis (e.g., '😊') may use 4 bytes in UTF-8.
+ *
+ * This distinction is critical when validating input for systems with byte-based size constraints, such as database fields or network protocols.
+ */
+function isValidInputLength(inputValue: string, length: number) {
+    const inputValueLength = Buffer.from(inputValue).length;
+    return {isValid: inputValueLength > length, byteLength: inputValueLength};
+}
+
 export {
     meetsMinimumAgeRequirement,
     meetsMaximumAgeRequirement,
@@ -708,4 +730,5 @@ export {
     isValidZipCodeInternational,
     isValidOwnershipPercentage,
     isValidRegistrationNumber,
+    isValidInputLength,
 };
diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
index 22add2ba54af..97817e755c6a 100644
--- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
@@ -12,11 +12,11 @@ import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {updateAdvancedFilters} from '@libs/actions/Search';
 import Navigation from '@libs/Navigation/Navigation';
+import {isValidInputLength} from '@libs/ValidationUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm';
-import {Buffer} from 'buffer/';
 
 function SearchFiltersMerchantPage() {
     const styles = useThemeStyles();
@@ -34,10 +34,10 @@ function SearchFiltersMerchantPage() {
     const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM>) => {
         const errors: FormInputErrors<typeof ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM> = {};
         const merchantValue = values.merchant.trim();
-        const merchantByteLength = Buffer.from(merchantValue).length;
+        const {isValid, byteLength} = isValidInputLength(merchantValue, CONST.MERCHANT_NAME_MAX_BYTES);
 
-        if (merchantByteLength > CONST.MERCHANT_NAME_MAX_BYTES) {
-            errors.merchant = translate('common.error.characterLimitExceedCounter', {length: merchantByteLength, limit: CONST.MERCHANT_NAME_MAX_BYTES});
+        if (isValid) {
+            errors.merchant = translate('common.error.characterLimitExceedCounter', {length: byteLength, limit: CONST.MERCHANT_NAME_MAX_BYTES});
         }
 
         return errors;
diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx
index e39a8d7fce3c..abfb5d77e52d 100644
--- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx
+++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx
@@ -11,13 +11,13 @@ import usePolicy from '@hooks/usePolicy';
 import useThemeStyles from '@hooks/useThemeStyles';
 import Navigation from '@libs/Navigation/Navigation';
 import {getTransactionDetails, isExpenseRequest, isPolicyExpenseChat} from '@libs/ReportUtils';
+import {isValidInputLength} from '@libs/ValidationUtils';
 import {setDraftSplitTransaction, setMoneyRequestMerchant, updateMoneyRequestMerchant} from '@userActions/IOU';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type SCREENS from '@src/SCREENS';
 import INPUT_IDS from '@src/types/form/MoneyRequestMerchantForm';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import {Buffer} from 'buffer/';
 import DiscardChangesConfirmation from './DiscardChangesConfirmation';
 import StepScreenWrapper from './StepScreenWrapper';
 import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound';
@@ -61,15 +61,15 @@ function IOURequestStepMerchant({
     const validate = useCallback(
         (value: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM>) => {
             const errors: FormInputErrors<typeof ONYXKEYS.FORMS.MONEY_REQUEST_MERCHANT_FORM> = {};
-            const merchantByteLength = Buffer.from(value.moneyRequestMerchant).length;
+            const {isValid, byteLength} = isValidInputLength(value.moneyRequestMerchant, CONST.MERCHANT_NAME_MAX_BYTES);
 
             if (isMerchantRequired && !value.moneyRequestMerchant) {
                 errors.moneyRequestMerchant = translate('common.error.fieldRequired');
             } else if (isMerchantRequired && value.moneyRequestMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT) {
                 errors.moneyRequestMerchant = translate('iou.error.invalidMerchant');
-            } else if (merchantByteLength > CONST.MERCHANT_NAME_MAX_BYTES) {
+            } else if (isValid) {
                 errors.moneyRequestMerchant = translate('common.error.characterLimitExceedCounter', {
-                    length: merchantByteLength,
+                    length: byteLength,
                     limit: CONST.MERCHANT_NAME_MAX_BYTES,
                 });
             }
diff --git a/tests/unit/ValidationUtilsTest.ts b/tests/unit/ValidationUtilsTest.ts
index 343e05ef2e52..3cf8897cef43 100644
--- a/tests/unit/ValidationUtilsTest.ts
+++ b/tests/unit/ValidationUtilsTest.ts
@@ -7,6 +7,7 @@ import {
     isValidAccountRoute,
     isValidDate,
     isValidExpirationDate,
+    isValidInputLength,
     isValidLegalName,
     isValidPastDate,
     isValidPaymentZipCode,
@@ -396,4 +397,99 @@ describe('ValidationUtils', () => {
             expect(isValid).toBe(false);
         });
     });
+
+    describe('isValidInputLength', () => {
+        // Test Latin alphabet characters (1 byte each in UTF-8)
+        describe('Latin alphabet characters', () => {
+            test('returns true and correct byte length when Latin string byte length exceeds limit', () => {
+                expect(isValidInputLength('abc', 2)).toEqual({isValid: true, byteLength: 3}); // 3 bytes > 2
+            });
+
+            test('returns false and correct byte length when Latin string byte length equals limit', () => {
+                expect(isValidInputLength('abc', 3)).toEqual({isValid: false, byteLength: 3}); // 3 bytes ≤ 3
+            });
+
+            test('returns false and correct byte length when Latin string byte length is less than limit', () => {
+                expect(isValidInputLength('ab', 3)).toEqual({isValid: false, byteLength: 2}); // 2 bytes ≤ 3
+            });
+        });
+
+        // Test Sanskrit characters (typically 3 bytes each in UTF-8)
+        describe('Sanskrit characters', () => {
+            test('returns true and correct byte length when Sanskrit string byte length exceeds limit', () => {
+                expect(isValidInputLength('कष', 5)).toEqual({isValid: true, byteLength: 6}); // 6 bytes > 5
+            });
+
+            test('returns false and correct byte length when Sanskrit string byte length equals limit', () => {
+                expect(isValidInputLength('कष', 6)).toEqual({isValid: false, byteLength: 6}); // 6 bytes ≤ 6
+            });
+
+            test('returns false and correct byte length when Sanskrit string byte length is less than limit', () => {
+                expect(isValidInputLength('क', 4)).toEqual({isValid: false, byteLength: 3}); // 3 bytes ≤ 4
+            });
+        });
+
+        // Test emojis (typically 4 bytes each in UTF-8)
+        describe('Emojis', () => {
+            test('returns true and correct byte length when emoji byte length exceeds limit', () => {
+                expect(isValidInputLength('😊', 3)).toEqual({isValid: true, byteLength: 4}); // 4 bytes > 3
+            });
+
+            test('returns false and correct byte length when emoji byte length equals limit', () => {
+                expect(isValidInputLength('😊', 4)).toEqual({isValid: false, byteLength: 4}); // 4 bytes ≤ 4
+            });
+
+            test('returns false and correct byte length when emoji byte length is less than limit', () => {
+                expect(isValidInputLength('😊', 5)).toEqual({isValid: false, byteLength: 4}); // 4 bytes ≤ 5
+            });
+        });
+
+        // Test empty strings and spaces
+        describe('Empty strings and spaces', () => {
+            test('returns false and correct byte length for empty string regardless of limit', () => {
+                expect(isValidInputLength('', 0)).toEqual({isValid: false, byteLength: 0}); // 0 bytes ≤ 0
+                expect(isValidInputLength('', 1)).toEqual({isValid: false, byteLength: 0}); // 0 bytes ≤ 1
+            });
+
+            test('returns true and correct byte length when space string byte length exceeds limit', () => {
+                expect(isValidInputLength('   ', 2)).toEqual({isValid: true, byteLength: 3}); // 3 bytes > 2
+            });
+
+            test('returns false and correct byte length when space string byte length equals limit', () => {
+                expect(isValidInputLength('  ', 2)).toEqual({isValid: false, byteLength: 2}); // 2 bytes ≤ 2
+            });
+        });
+
+        // Test mixed characters
+        describe('Mixed characters', () => {
+            test('returns true and correct byte length when mixed string byte length exceeds limit', () => {
+                expect(isValidInputLength('aक😊', 6)).toEqual({isValid: true, byteLength: 8}); // 1 + 3 + 4 = 8 bytes > 6
+            });
+
+            test('returns false and correct byte length when mixed string byte length equals limit', () => {
+                expect(isValidInputLength('aक😊', 8)).toEqual({isValid: false, byteLength: 8}); // 1 + 3 + 4 = 8 bytes ≤ 8
+            });
+
+            test('returns false and correct byte length when mixed string byte length is less than limit', () => {
+                expect(isValidInputLength('aक', 5)).toEqual({isValid: false, byteLength: 4}); // 1 + 3 = 4 bytes ≤ 5
+            });
+        });
+
+        // Test edge cases
+        describe('Edge cases', () => {
+            test('handles negative length parameter', () => {
+                expect(isValidInputLength('abc', -1)).toEqual({isValid: true, byteLength: 3}); // 3 bytes > -1
+            });
+
+            test('handles zero length parameter', () => {
+                expect(isValidInputLength('a', 0)).toEqual({isValid: true, byteLength: 1}); // 1 byte > 0
+                expect(isValidInputLength('', 0)).toEqual({isValid: false, byteLength: 0}); // 0 bytes ≤ 0
+            });
+
+            test('handles special characters (e.g., newlines, tabs)', () => {
+                expect(isValidInputLength('\n\t', 1)).toEqual({isValid: true, byteLength: 2}); // 2 bytes > 1
+                expect(isValidInputLength('\n\t', 2)).toEqual({isValid: false, byteLength: 2}); // 2 bytes ≤ 2
+            });
+        });
+    });
 });

From 624a2b3366eb2a9087ca82a4cb5070dda3913fb0 Mon Sep 17 00:00:00 2001
From: Kevin Brian Bader <kevin.bader96@gmail.com>
Date: Thu, 12 Jun 2025 14:51:12 -0700
Subject: [PATCH 3/6] fix(eslint): added canBeMissing and code review
 adjustments

---
 src/libs/ValidationUtils.ts                   | 22 +++++--------------
 .../SearchFiltersMerchantPage.tsx             |  2 +-
 2 files changed, 6 insertions(+), 18 deletions(-)

diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 83adba0e310d..8c3cccf52422 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -659,24 +659,12 @@ function isValidRegistrationNumber(registrationNumber: string, country: Country
 }
 
 /**
- * Checks if the character length of an input string exceeds the specified length,
+ * Checks if the `inputValue` byte length exceeds the specified byte length,
  * returning `isValid` (boolean) and `byteLength` (number) to be used in dynamic error copy.
- *
- * @remarks
- * This function uses `Buffer.from(inputValue).length` to calculate the byte size of the input string in UTF-8 encoding.
- * Unlike JavaScript's `string.length` which counts UTF-16 code units (where some characters, like emojis or certain non-Latin characters, may use two code units),
- * `Buffer` measures the actual byte size in UTF-8. This is important for non-Latin alphabets (e.g., Sanskrit, Chinese, or emojis) where characters often require more than one byte.
- *
- * For example:
- * - Latin characters (e.g., 'a', 'b') use 1 byte each in UTF-8.
- * - Sanskrit characters (e.g., 'क', 'ष') typically use 3 bytes each in UTF-8.
- * - Emojis (e.g., '😊') may use 4 bytes in UTF-8.
- *
- * This distinction is critical when validating input for systems with byte-based size constraints, such as database fields or network protocols.
- */
-function isValidInputLength(inputValue: string, length: number) {
-    const inputValueLength = Buffer.from(inputValue).length;
-    return {isValid: inputValueLength > length, byteLength: inputValueLength};
+ */
+function isValidInputLength(inputValue: string, byteLength: number) {
+    const valueByteLength = Buffer.from(inputValue).length;
+    return {isValid: valueByteLength > byteLength, byteLength: valueByteLength};
 }
 
 export {
diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
index 97817e755c6a..6368b6854a03 100644
--- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
@@ -22,7 +22,7 @@ function SearchFiltersMerchantPage() {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
 
-    const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
+    const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {canBeMissing: true});
     const merchant = searchAdvancedFiltersForm?.[FILTER_KEYS.MERCHANT];
     const {inputCallbackRef} = useAutoFocusInput();
 

From 995758a756e92133923f866f3d9a37de27377d77 Mon Sep 17 00:00:00 2001
From: Kevin Brian Bader <kevin.bader96@gmail.com>
Date: Sat, 14 Jun 2025 16:54:01 -0700
Subject: [PATCH 4/6] update(api): switched to TextEncoder API

---
 src/libs/StringUtils/index.ts | 10 ++++++++++
 src/libs/ValidationUtils.ts   |  3 +--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/libs/StringUtils/index.ts b/src/libs/StringUtils/index.ts
index 6d4f1c0ba1c6..f69163752402 100644
--- a/src/libs/StringUtils/index.ts
+++ b/src/libs/StringUtils/index.ts
@@ -139,6 +139,15 @@ function removePreCodeBlock(text = '') {
     return text.replace(/<pre[^>]*>|<\/pre>/g, '');
 }
 
+/**
+ * Returns the number of bytes required to encode a string in UTF-8.
+ */
+function getUTF8ByteLength(str: string) {
+    const encoder = new TextEncoder();
+    const bytes = encoder.encode(str);
+    return bytes.length;
+}
+
 export default {
     sanitizeString,
     isEmptyString,
@@ -153,4 +162,5 @@ export default {
     sortStringArrayByLength,
     dedent,
     hash,
+    getUTF8ByteLength,
 };
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 8c3cccf52422..8cbbb22cd536 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -1,4 +1,3 @@
-import {Buffer} from 'buffer';
 import {addYears, endOfMonth, format, isAfter, isBefore, isSameDay, isValid, isWithinInterval, parse, parseISO, startOfDay, subYears} from 'date-fns';
 import {PUBLIC_DOMAINS_SET, Str, Url} from 'expensify-common';
 import isEmpty from 'lodash/isEmpty';
@@ -663,7 +662,7 @@ function isValidRegistrationNumber(registrationNumber: string, country: Country
  * returning `isValid` (boolean) and `byteLength` (number) to be used in dynamic error copy.
  */
 function isValidInputLength(inputValue: string, byteLength: number) {
-    const valueByteLength = Buffer.from(inputValue).length;
+    const valueByteLength = StringUtils.getUTF8ByteLength(inputValue);
     return {isValid: valueByteLength > byteLength, byteLength: valueByteLength};
 }
 

From 88ddfa9c92e222ee69022495ac911a48e55a7021 Mon Sep 17 00:00:00 2001
From: Kevin Brian Bader <kevin.bader96@gmail.com>
Date: Sat, 14 Jun 2025 17:42:20 -0700
Subject: [PATCH 5/6] fix(tests): added TextEncoder global mock

---
 tests/unit/ValidationUtilsTest.ts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/unit/ValidationUtilsTest.ts b/tests/unit/ValidationUtilsTest.ts
index 3cf8897cef43..09fe2bca58f2 100644
--- a/tests/unit/ValidationUtilsTest.ts
+++ b/tests/unit/ValidationUtilsTest.ts
@@ -1,4 +1,5 @@
 import {addDays, format, startOfDay, subYears} from 'date-fns';
+import {TextEncoder} from 'util';
 import {translateLocal} from '@libs/Localize';
 import CONST from '@src/CONST';
 import {
@@ -19,6 +20,8 @@ import {
     meetsMinimumAgeRequirement,
 } from '@src/libs/ValidationUtils';
 
+global.TextEncoder = TextEncoder;
+
 describe('ValidationUtils', () => {
     describe('isValidDate', () => {
         test('Should return true for a valid date within the range', () => {

From 5bab1071a9cfc8c11dc81042b46142f3f291daa1 Mon Sep 17 00:00:00 2001
From: Kevin Brian Bader <kevin.bader96@gmail.com>
Date: Mon, 16 Jun 2025 11:03:09 -0700
Subject: [PATCH 6/6] update(review): adjusted isValidInputLength return
 condition and tests

---
 src/libs/ValidationUtils.ts                   |  2 +-
 .../SearchFiltersMerchantPage.tsx             |  2 +-
 .../request/step/IOURequestStepMerchant.tsx   |  2 +-
 tests/unit/ValidationUtilsTest.ts             | 42 +++++++++----------
 4 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 8cbbb22cd536..69570619da2e 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -663,7 +663,7 @@ function isValidRegistrationNumber(registrationNumber: string, country: Country
  */
 function isValidInputLength(inputValue: string, byteLength: number) {
     const valueByteLength = StringUtils.getUTF8ByteLength(inputValue);
-    return {isValid: valueByteLength > byteLength, byteLength: valueByteLength};
+    return {isValid: valueByteLength <= byteLength, byteLength: valueByteLength};
 }
 
 export {
diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
index 6368b6854a03..a1ebf1f45020 100644
--- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage.tsx
@@ -36,7 +36,7 @@ function SearchFiltersMerchantPage() {
         const merchantValue = values.merchant.trim();
         const {isValid, byteLength} = isValidInputLength(merchantValue, CONST.MERCHANT_NAME_MAX_BYTES);
 
-        if (isValid) {
+        if (!isValid) {
             errors.merchant = translate('common.error.characterLimitExceedCounter', {length: byteLength, limit: CONST.MERCHANT_NAME_MAX_BYTES});
         }
 
diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx
index abfb5d77e52d..af14bc01e941 100644
--- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx
+++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx
@@ -67,7 +67,7 @@ function IOURequestStepMerchant({
                 errors.moneyRequestMerchant = translate('common.error.fieldRequired');
             } else if (isMerchantRequired && value.moneyRequestMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT) {
                 errors.moneyRequestMerchant = translate('iou.error.invalidMerchant');
-            } else if (isValid) {
+            } else if (!isValid) {
                 errors.moneyRequestMerchant = translate('common.error.characterLimitExceedCounter', {
                     length: byteLength,
                     limit: CONST.MERCHANT_NAME_MAX_BYTES,
diff --git a/tests/unit/ValidationUtilsTest.ts b/tests/unit/ValidationUtilsTest.ts
index 09fe2bca58f2..e9e10dc4f138 100644
--- a/tests/unit/ValidationUtilsTest.ts
+++ b/tests/unit/ValidationUtilsTest.ts
@@ -405,93 +405,93 @@ describe('ValidationUtils', () => {
         // Test Latin alphabet characters (1 byte each in UTF-8)
         describe('Latin alphabet characters', () => {
             test('returns true and correct byte length when Latin string byte length exceeds limit', () => {
-                expect(isValidInputLength('abc', 2)).toEqual({isValid: true, byteLength: 3}); // 3 bytes > 2
+                expect(isValidInputLength('abc', 2)).toEqual({isValid: false, byteLength: 3}); // 3 bytes > 2
             });
 
             test('returns false and correct byte length when Latin string byte length equals limit', () => {
-                expect(isValidInputLength('abc', 3)).toEqual({isValid: false, byteLength: 3}); // 3 bytes ≤ 3
+                expect(isValidInputLength('abc', 3)).toEqual({isValid: true, byteLength: 3}); // 3 bytes ≤ 3
             });
 
             test('returns false and correct byte length when Latin string byte length is less than limit', () => {
-                expect(isValidInputLength('ab', 3)).toEqual({isValid: false, byteLength: 2}); // 2 bytes ≤ 3
+                expect(isValidInputLength('ab', 3)).toEqual({isValid: true, byteLength: 2}); // 2 bytes ≤ 3
             });
         });
 
         // Test Sanskrit characters (typically 3 bytes each in UTF-8)
         describe('Sanskrit characters', () => {
             test('returns true and correct byte length when Sanskrit string byte length exceeds limit', () => {
-                expect(isValidInputLength('कष', 5)).toEqual({isValid: true, byteLength: 6}); // 6 bytes > 5
+                expect(isValidInputLength('कष', 5)).toEqual({isValid: false, byteLength: 6}); // 6 bytes > 5
             });
 
             test('returns false and correct byte length when Sanskrit string byte length equals limit', () => {
-                expect(isValidInputLength('कष', 6)).toEqual({isValid: false, byteLength: 6}); // 6 bytes ≤ 6
+                expect(isValidInputLength('कष', 6)).toEqual({isValid: true, byteLength: 6}); // 6 bytes ≤ 6
             });
 
             test('returns false and correct byte length when Sanskrit string byte length is less than limit', () => {
-                expect(isValidInputLength('क', 4)).toEqual({isValid: false, byteLength: 3}); // 3 bytes ≤ 4
+                expect(isValidInputLength('क', 4)).toEqual({isValid: true, byteLength: 3}); // 3 bytes ≤ 4
             });
         });
 
         // Test emojis (typically 4 bytes each in UTF-8)
         describe('Emojis', () => {
             test('returns true and correct byte length when emoji byte length exceeds limit', () => {
-                expect(isValidInputLength('😊', 3)).toEqual({isValid: true, byteLength: 4}); // 4 bytes > 3
+                expect(isValidInputLength('😊', 3)).toEqual({isValid: false, byteLength: 4}); // 4 bytes > 3
             });
 
             test('returns false and correct byte length when emoji byte length equals limit', () => {
-                expect(isValidInputLength('😊', 4)).toEqual({isValid: false, byteLength: 4}); // 4 bytes ≤ 4
+                expect(isValidInputLength('😊', 4)).toEqual({isValid: true, byteLength: 4}); // 4 bytes ≤ 4
             });
 
             test('returns false and correct byte length when emoji byte length is less than limit', () => {
-                expect(isValidInputLength('😊', 5)).toEqual({isValid: false, byteLength: 4}); // 4 bytes ≤ 5
+                expect(isValidInputLength('😊', 5)).toEqual({isValid: true, byteLength: 4}); // 4 bytes ≤ 5
             });
         });
 
         // Test empty strings and spaces
         describe('Empty strings and spaces', () => {
             test('returns false and correct byte length for empty string regardless of limit', () => {
-                expect(isValidInputLength('', 0)).toEqual({isValid: false, byteLength: 0}); // 0 bytes ≤ 0
-                expect(isValidInputLength('', 1)).toEqual({isValid: false, byteLength: 0}); // 0 bytes ≤ 1
+                expect(isValidInputLength('', 0)).toEqual({isValid: true, byteLength: 0}); // 0 bytes ≤ 0
+                expect(isValidInputLength('', 1)).toEqual({isValid: true, byteLength: 0}); // 0 bytes ≤ 1
             });
 
             test('returns true and correct byte length when space string byte length exceeds limit', () => {
-                expect(isValidInputLength('   ', 2)).toEqual({isValid: true, byteLength: 3}); // 3 bytes > 2
+                expect(isValidInputLength('   ', 2)).toEqual({isValid: false, byteLength: 3}); // 3 bytes > 2
             });
 
             test('returns false and correct byte length when space string byte length equals limit', () => {
-                expect(isValidInputLength('  ', 2)).toEqual({isValid: false, byteLength: 2}); // 2 bytes ≤ 2
+                expect(isValidInputLength('  ', 2)).toEqual({isValid: true, byteLength: 2}); // 2 bytes ≤ 2
             });
         });
 
         // Test mixed characters
         describe('Mixed characters', () => {
             test('returns true and correct byte length when mixed string byte length exceeds limit', () => {
-                expect(isValidInputLength('aक😊', 6)).toEqual({isValid: true, byteLength: 8}); // 1 + 3 + 4 = 8 bytes > 6
+                expect(isValidInputLength('aक😊', 6)).toEqual({isValid: false, byteLength: 8}); // 1 + 3 + 4 = 8 bytes > 6
             });
 
             test('returns false and correct byte length when mixed string byte length equals limit', () => {
-                expect(isValidInputLength('aक😊', 8)).toEqual({isValid: false, byteLength: 8}); // 1 + 3 + 4 = 8 bytes ≤ 8
+                expect(isValidInputLength('aक😊', 8)).toEqual({isValid: true, byteLength: 8}); // 1 + 3 + 4 = 8 bytes ≤ 8
             });
 
             test('returns false and correct byte length when mixed string byte length is less than limit', () => {
-                expect(isValidInputLength('aक', 5)).toEqual({isValid: false, byteLength: 4}); // 1 + 3 = 4 bytes ≤ 5
+                expect(isValidInputLength('aक', 5)).toEqual({isValid: true, byteLength: 4}); // 1 + 3 = 4 bytes ≤ 5
             });
         });
 
         // Test edge cases
         describe('Edge cases', () => {
             test('handles negative length parameter', () => {
-                expect(isValidInputLength('abc', -1)).toEqual({isValid: true, byteLength: 3}); // 3 bytes > -1
+                expect(isValidInputLength('abc', -1)).toEqual({isValid: false, byteLength: 3}); // 3 bytes > -1
             });
 
             test('handles zero length parameter', () => {
-                expect(isValidInputLength('a', 0)).toEqual({isValid: true, byteLength: 1}); // 1 byte > 0
-                expect(isValidInputLength('', 0)).toEqual({isValid: false, byteLength: 0}); // 0 bytes ≤ 0
+                expect(isValidInputLength('a', 0)).toEqual({isValid: false, byteLength: 1}); // 1 byte > 0
+                expect(isValidInputLength('', 0)).toEqual({isValid: true, byteLength: 0}); // 0 bytes ≤ 0
             });
 
             test('handles special characters (e.g., newlines, tabs)', () => {
-                expect(isValidInputLength('\n\t', 1)).toEqual({isValid: true, byteLength: 2}); // 2 bytes > 1
-                expect(isValidInputLength('\n\t', 2)).toEqual({isValid: false, byteLength: 2}); // 2 bytes ≤ 2
+                expect(isValidInputLength('\n\t', 1)).toEqual({isValid: false, byteLength: 2}); // 2 bytes > 1
+                expect(isValidInputLength('\n\t', 2)).toEqual({isValid: true, byteLength: 2}); // 2 bytes ≤ 2
             });
         });
     });
