diff --git a/.github/actions/javascript/isDeployChecklistLocked/index.js b/.github/actions/javascript/isDeployChecklistLocked/index.js index 9321db05e6d7..cfe1ac778241 100644 --- a/.github/actions/javascript/isDeployChecklistLocked/index.js +++ b/.github/actions/javascript/isDeployChecklistLocked/index.js @@ -3432,6 +3432,7 @@ var require_CONST = __commonJS({ REPLACE_CARD: "replace_card", SHIP_CARD: "ship_card", REPORT_CARD_FRAUD: "report_card_fraud", + APPROVE_DIGITAL_WALLET: "approve_digital_wallet", ISSUE_CARD: "issue_card", UPDATE_CARD: "update_card", UPDATE_PERSONAL_DETAILS: "update_personal_details", @@ -22075,6 +22076,117 @@ var require_str = __commonJS({ } return parameter; } + function compare(strA, strB) { + if (strA < strB) { + return -1; + } + if (strA > strB) { + return 1; + } + return 0; + } + function caseInsensitiveCompare(strA, strB) { + const lowerCaseStrA = strA.toLocaleLowerCase(); + const lowerCaseStrB = strB.toLocaleLowerCase(); + return compare(lowerCaseStrA, lowerCaseStrB); + } + function contains(haystack, needle) { + return haystack.indexOf(needle) !== -1; + } + function cutAfter(str, substr) { + const index = str.indexOf(substr); + if (index !== -1) { + return str.substring(0, index); + } + return str; + } + function cutBefore(str, substr) { + const index = str.indexOf(substr); + if (index !== -1) { + return str.substring(index + substr.length); + } + return str; + } + function extractEmailDomain(email) { + return cutBefore(email, "@"); + } + function getExtension(url) { + var _a, _b; + if (typeof url !== "string") { + Log_1.default.warn("Str.getExtension: url is not a string", { url }); + return void 0; + } + return (_b = (_a = url.split(".").pop()) === null || _a === void 0 ? void 0 : _a.split("?")[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase(); + } + function getRawByteSize(inputChar) { + const onlyChar = String(inputChar); + const c = onlyChar.charCodeAt(0); + if (c < 1 << 7) { + return 1; + } + if (c < 1 << 11) { + return 2; + } + if (c < 1 << 16) { + return 3; + } + if (c < 1 << 21) { + return 4; + } + if (c < 1 << 26) { + return 5; + } + if (c < 1 << 31) { + return 6; + } + return Number.NaN; + } + function htmlDecode(s) { + return HtmlEntities.decode(s); + } + function htmlEncode(s) { + return HtmlEntities.encode(s); + } + function isTypeOf(obj, type) { + return Object.prototype.toString.call(obj) === `[object ${type}]`; + } + function isNumber(obj) { + return isTypeOf(obj, "Number"); + } + function isString(obj) { + return isTypeOf(obj, "String"); + } + function isUndefined(obj) { + return obj === void 0; + } + function isValidE164Phone(phone) { + return Constants.CONST.SMS.E164_REGEX.test(phone); + } + function isValidEmail(str) { + if (!str || typeof str !== "string") { + return false; + } + const unicodeVersion = punycode_1.default.toUnicode(str); + if (String(unicodeVersion).match(Constants.CONST.REG_EXP.EMOJI_RULE)) { + return false; + } + return !!String(str).match(Constants.CONST.REG_EXP.EMAIL); + } + function maskFirstNCharacters(str, num, mask) { + if (!isString(str) || !isString(mask) || str.length === 0 || !isNumber(num)) { + return str; + } + return str.substring(0, num).replaceAll(/./g, mask) + str.substring(num); + } + function removeSMSDomain(text) { + return text.replaceAll(REMOVE_SMS_DOMAIN_PATTERN, ""); + } + function removeTrailingComma(str) { + return str.trim().replaceAll(/(,$)/g, ""); + } + function startsWith(haystack, needle) { + return isString(haystack) && isString(needle) && haystack.substring(0, needle.length) === needle; + } var Str2 = { /** * Return true if the string is ending with the provided suffix @@ -22132,18 +22244,14 @@ var require_str = __commonJS({ * @param s The string to decode. * @returns The decoded string. */ - htmlDecode(s) { - return HtmlEntities.decode(s); - }, + htmlDecode, /** * HTML encodes the given string. * * @param s The string to encode. * @return string @p s HTML encoded. */ - htmlEncode(s) { - return HtmlEntities.encode(s); - }, + htmlEncode, /** * Decodes the given HTML encoded string. * @@ -22185,7 +22293,7 @@ var require_str = __commonJS({ * @returns True when first === second, ignoring HTML encoding */ htmlEncodingInsensitiveEquals(first, second) { - return first === second || this.htmlDecode(first) === second || this.htmlEncode(first) === second; + return first === second || htmlDecode(first) === second || htmlEncode(first) === second; }, /** * Creates an ID that can be used as an HTML attribute from @p str. @@ -22268,36 +22376,14 @@ var require_str = __commonJS({ * one. * @returns Byte size of the character */ - getRawByteSize(inputChar) { - const onlyChar = String(inputChar); - const c = onlyChar.charCodeAt(0); - if (c < 1 << 7) { - return 1; - } - if (c < 1 << 11) { - return 2; - } - if (c < 1 << 16) { - return 3; - } - if (c < 1 << 21) { - return 4; - } - if (c < 1 << 26) { - return 5; - } - if (c < 1 << 31) { - return 6; - } - return Number.NaN; - }, + getRawByteSize, /** * Gets the length of a string in bytes, including non-ASCII characters * @returns The number of bytes used by string */ getByteLength(input) { const stringInput = String(input); - const byteLength = Array.from(stringInput).reduce((acc, char) => acc + this.getRawByteSize(char), 0); + const byteLength = Array.from(stringInput).reduce((acc, char) => acc + getRawByteSize(char), 0); return byteLength; }, /** @@ -22309,7 +22395,7 @@ var require_str = __commonJS({ const stringInput = String(input); let totalByteLength = 0; for (let i = 0; i < stringInput.length; i++) { - const charByteSize = this.getRawByteSize(stringInput[i]); + const charByteSize = getRawByteSize(stringInput[i]); if (charByteSize + totalByteLength > maxSize) { return `${stringInput.substr(0, i - 3)}...`; } @@ -22324,9 +22410,7 @@ var require_str = __commonJS({ * @param needle The case-sensitive string to search for * @returns True if the haystack starts with the needle. */ - startsWith(haystack, needle) { - return this.isString(haystack) && this.isString(needle) && haystack.substring(0, needle.length) === needle; - }, + startsWith, /** * Gets the textual value of the given string. * @@ -22334,7 +22418,7 @@ var require_str = __commonJS({ * @returns The text from within the HTML string. */ stripHTML(str) { - if (!this.isString(str)) { + if (!isString(str)) { return ""; } return str.replaceAll(/<[^>]*>?/gm, ""); @@ -22357,13 +22441,7 @@ var require_str = __commonJS({ * @param substr The substring to search for. * @returns The cut/trimmed string. */ - cutAfter(str, substr) { - const index = str.indexOf(substr); - if (index !== -1) { - return str.substring(0, index); - } - return str; - }, + cutAfter, /** * Returns a string containing all the characters str from after the first * occurrence of substr to the end of the string. @@ -22373,13 +22451,7 @@ var require_str = __commonJS({ * @param substr The substring to search for. * @returns The cut/trimmed string. */ - cutBefore(str, substr) { - const index = str.indexOf(substr); - if (index !== -1) { - return str.substring(index + substr.length); - } - return str; - }, + cutBefore, /** * Checks that the string is a domain name (e.g. example.com) * @@ -22406,16 +22478,7 @@ var require_str = __commonJS({ * * @returns True if the string is an email */ - isValidEmail(str) { - if (!str || typeof str !== "string") { - return false; - } - const unicodeVersion = punycode_1.default.toUnicode(str); - if (String(unicodeVersion).match(Constants.CONST.REG_EXP.EMOJI_RULE)) { - return false; - } - return !!String(str).match(Constants.CONST.REG_EXP.EMAIL); - }, + isValidEmail, /** * Checks if the string is an valid email address formed during comment markdown formation. * @@ -22433,9 +22496,7 @@ var require_str = __commonJS({ * * @returns string with the trailing comma removed */ - removeTrailingComma(str) { - return str.trim().replaceAll(/(,$)/g, ""); - }, + removeTrailingComma, /** * Checks that the string is a list of coma separated email addresss. * @@ -22444,12 +22505,12 @@ var require_str = __commonJS({ * @returns True if all emails are valid or if input is empty */ areValidEmails(str) { - const string = this.removeTrailingComma(str); + const string = removeTrailingComma(str); if (string === "") { return true; } const emails = string.split(","); - const result = emails.every((email) => this.isValidEmail(email.trim())); + const result = emails.every((email) => isValidEmail(email.trim())); return result; }, /** @@ -22466,9 +22527,7 @@ var require_str = __commonJS({ * * @returns The domain name in the email address. */ - extractEmailDomain(email) { - return this.cutBefore(email, "@"); - }, + extractEmailDomain, /** * Tries to extract the company name from the given email address * (e.g. "yelp" for "joe@yelp.co.uk"). @@ -22478,7 +22537,7 @@ var require_str = __commonJS({ * @returns The company name in the email address or null. */ extractCompanyNameFromEmailDomain(email) { - const domain = this.extractEmailDomain(email); + const domain = extractEmailDomain(email); if (!domain) { return null; } @@ -22497,7 +22556,7 @@ var require_str = __commonJS({ * @returns The local part in the email address. */ extractEmailLocalPart(email) { - return this.cutAfter(email, "@"); + return cutAfter(email, "@"); }, /** * Sanitize phone number to return only numbers. Return null if non valid phone number. @@ -22555,13 +22614,13 @@ var require_str = __commonJS({ * @returns true if the length is in the range, false otherwise */ isOfLength(str, minimumLength, maximumLength) { - if (!this.isString(str)) { + if (!isString(str)) { return false; } if (str.length < minimumLength) { return false; } - if (!this.isUndefined(maximumLength) && str.length > maximumLength) { + if (!isUndefined(maximumLength) && str.length > maximumLength) { return false; } return true; @@ -22614,9 +22673,7 @@ var require_str = __commonJS({ * * @returns Returns true if the haystack contains the needle */ - contains(haystack, needle) { - return haystack.indexOf(needle) !== -1; - }, + contains, /** * Returns true if the haystack contains the needle, ignoring case * @@ -22626,7 +22683,7 @@ var require_str = __commonJS({ * @returns Returns true if the haystack contains the needle, ignoring case */ caseInsensitiveContains(haystack, needle) { - return this.contains(haystack.toLowerCase(), needle.toLowerCase()); + return contains(haystack.toLowerCase(), needle.toLowerCase()); }, /** * Case insensitive compare function @@ -22638,11 +22695,7 @@ var require_str = __commonJS({ * 1 if first string > second string * 0 if first string = second string */ - caseInsensitiveCompare(strA, strB) { - const lowerCaseStrA = strA.toLocaleLowerCase(); - const lowerCaseStrB = strB.toLocaleLowerCase(); - return this.compare(lowerCaseStrA, lowerCaseStrB); - }, + caseInsensitiveCompare, /** * Case insensitive equals * @@ -22651,7 +22704,7 @@ var require_str = __commonJS({ * @returns true when first == second except for case */ caseInsensitiveEquals(strA, strB) { - return this.caseInsensitiveCompare(strA, strB) === 0; + return caseInsensitiveCompare(strA, strB) === 0; }, /** * Compare function @@ -22663,15 +22716,7 @@ var require_str = __commonJS({ * 1 if first string > second string * 0 if first string = second string */ - compare(strA, strB) { - if (strA < strB) { - return -1; - } - if (strA > strB) { - return 1; - } - return 0; - }, + compare, /** * Check if a file extension is supported by SmartReports */ @@ -22690,45 +22735,37 @@ var require_str = __commonJS({ const accountNumber = String(num); const len = accountNumber.length; if (len < 6 || len > 20) { - return this.maskFirstNCharacters(accountNumber, len, "X"); + return maskFirstNCharacters(accountNumber, len, "X"); } if (len < 14) { - return this.maskFirstNCharacters(accountNumber, len - 4, "X"); + return maskFirstNCharacters(accountNumber, len - 4, "X"); } const first = accountNumber.substr(0, 6); const last = accountNumber.substr(7); - const masked = this.maskFirstNCharacters(last, len - 11, "X"); + const masked = maskFirstNCharacters(last, len - 11, "X"); return `${first}${masked}`; }, /** * Checks if something is a string * Stolen from underscore */ - isString(obj) { - return this.isTypeOf(obj, "String"); - }, + isString, /** * Checks if something is a number * Stolen from underscore * @param obj */ - isNumber(obj) { - return this.isTypeOf(obj, "Number"); - }, + isNumber, /** * Checks if something is a certain type * Stolen from underscore */ - isTypeOf(obj, type) { - return Object.prototype.toString.call(obj) === `[object ${type}]`; - }, + isTypeOf, /** * Checks to see if something is undefined * Stolen from underscore */ - isUndefined(obj) { - return obj === void 0; - }, + isUndefined, /** * Replace first N characters of the string with maskChar * eg: maskFirstNCharacters( '1234567890', 6, 'X' ) yields XXXXXX7890 @@ -22737,12 +22774,7 @@ var require_str = __commonJS({ * @param mask String we want replace the first N chars with * @returns Masked string */ - maskFirstNCharacters(str, num, mask) { - if (!this.isString(str) || !this.isString(mask) || str.length === 0 || !this.isNumber(num)) { - return str; - } - return str.substring(0, num).replaceAll(/./g, mask) + str.substring(num); - }, + maskFirstNCharacters, /** * Trim a string */ @@ -22754,7 +22786,7 @@ var require_str = __commonJS({ * @param percentageString The percentage as a string */ percentageStringToNumber(percentageString) { - return Number(this.cutAfter(percentageString, "%")); + return Number(cutAfter(percentageString, "%")); }, /** * Remove all the spaces from a string @@ -22798,7 +22830,7 @@ var require_str = __commonJS({ * Converts a value to boolean, case-insensitive. */ toBool(value) { - if (this.isString(value)) { + if (isString(value)) { return value.toLowerCase() === "true"; } return !!value; @@ -22848,9 +22880,7 @@ var require_str = __commonJS({ /** * Check for whether a phone number is valid according to E.164 standard. */ - isValidE164Phone(phone) { - return Constants.CONST.SMS.E164_REGEX.test(phone); - }, + isValidE164Phone, /** * Check for whether a phone number is valid in different formats/standards. For example: * significant: 4404589784 @@ -22876,14 +22906,12 @@ var require_str = __commonJS({ /** * Returns text without our SMS domain */ - removeSMSDomain(text) { - return text.replaceAll(REMOVE_SMS_DOMAIN_PATTERN, ""); - }, + removeSMSDomain, /** * Returns true if the text is a valid E.164 phone number with our SMS domain removed */ isSMSLogin(text) { - return this.isValidE164Phone(this.removeSMSDomain(text)); + return isValidE164Phone(removeSMSDomain(text)); }, /** * This method will return all matches of a single regex like preg_match_all() in PHP. This is not a common part of @@ -22936,7 +22964,7 @@ var require_str = __commonJS({ return url; } const website = match[3] ? match[2] : `${defaultScheme}://${match[2]}`; - return website.toLowerCase() + this.cutBefore(match[1], match[2]); + return website.toLowerCase() + cutBefore(match[1], match[2]); }, /** * Checks if parameter is a string or function @@ -22948,14 +22976,7 @@ var require_str = __commonJS({ * Get file extension for a given url with or * without query parameters */ - getExtension(url) { - var _a, _b; - if (typeof url !== "string") { - Log_1.default.warn("Str.getExtension: url is not a string", { url }); - return void 0; - } - return (_b = (_a = url.split(".").pop()) === null || _a === void 0 ? void 0 : _a.split("?")[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase(); - }, + getExtension, /** * Takes in a URL and checks if the file extension is PDF * @@ -22963,7 +22984,7 @@ var require_str = __commonJS({ * @returns Whether file path is PDF or not */ isPDF(url) { - return this.getExtension(url) === "pdf"; + return getExtension(url) === "pdf"; }, /** * Takes in a URL and checks if the file extension is an image @@ -22974,7 +22995,7 @@ var require_str = __commonJS({ * https://reactnative.dev/docs/image#source */ isImage(url) { - const extension = this.getExtension(url); + const extension = getExtension(url); if (!extension) { return false; } @@ -22990,7 +23011,7 @@ var require_str = __commonJS({ * https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs */ isVideo(url) { - const extension = this.getExtension(url); + const extension = getExtension(url); if (!extension) { return false; } @@ -23003,7 +23024,7 @@ var require_str = __commonJS({ * @returns True if is a domain account email, otherwise false. */ isDomainEmail(email) { - return this.startsWith(email, "+@"); + return startsWith(email, "+@"); }, /** * Find the minimum indentation of any line in the string, @@ -24141,13 +24162,17 @@ ${g2} }, { name: "anchor", - regex: /<(a)[^><]*href\s*=\s*(['"])(.*?)\2(?:".*?"|'.*?'|[^'"><])*>([\s\S]*?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi, - replacement: (_extras, _match, _g1, _g2, g3, g4) => { - const email = g3.startsWith("mailto:") ? g3.slice(7) : ""; - if (email === g4) { - return email; + regex: /(\]\()?<(a)[^><]*href\s*=\s*(['"])(.*?)\3(?:".*?"|'.*?'|[^'"><])*>([\s\S]*?)<\/\2>(?![^<]*(<\/pre>|<\/code>))/gi, + replacement: (_extras, _match, g1, _g2, _g3, g4, g5) => { + const incompleteLinkStart = g1 !== null && g1 !== void 0 ? g1 : ""; + const email = g4.startsWith("mailto:") ? g4.slice(7) : ""; + if (email === g5) { + return `${incompleteLinkStart}${email}`; + } + if (incompleteLinkStart && (g4 === g5 || g4 === `https://${g5}`)) { + return `${incompleteLinkStart}${g5}`; } - return `[${g4}](${email || g3})`; + return `${incompleteLinkStart}[${g5}](${email || g4})`; } }, { diff --git a/.github/actions/javascript/proposalPoliceComment/index.js b/.github/actions/javascript/proposalPoliceComment/index.js index beb367dd3ebd..58a12c5fb27a 100644 --- a/.github/actions/javascript/proposalPoliceComment/index.js +++ b/.github/actions/javascript/proposalPoliceComment/index.js @@ -24691,6 +24691,7 @@ var require_CONST = __commonJS({ REPLACE_CARD: "replace_card", SHIP_CARD: "ship_card", REPORT_CARD_FRAUD: "report_card_fraud", + APPROVE_DIGITAL_WALLET: "approve_digital_wallet", ISSUE_CARD: "issue_card", UPDATE_CARD: "update_card", UPDATE_PERSONAL_DETAILS: "update_personal_details", @@ -43334,6 +43335,117 @@ var require_str = __commonJS({ } return parameter; } + function compare(strA, strB) { + if (strA < strB) { + return -1; + } + if (strA > strB) { + return 1; + } + return 0; + } + function caseInsensitiveCompare(strA, strB) { + const lowerCaseStrA = strA.toLocaleLowerCase(); + const lowerCaseStrB = strB.toLocaleLowerCase(); + return compare(lowerCaseStrA, lowerCaseStrB); + } + function contains(haystack, needle) { + return haystack.indexOf(needle) !== -1; + } + function cutAfter(str2, substr) { + const index = str2.indexOf(substr); + if (index !== -1) { + return str2.substring(0, index); + } + return str2; + } + function cutBefore(str2, substr) { + const index = str2.indexOf(substr); + if (index !== -1) { + return str2.substring(index + substr.length); + } + return str2; + } + function extractEmailDomain(email) { + return cutBefore(email, "@"); + } + function getExtension(url) { + var _a3, _b; + if (typeof url !== "string") { + Log_1.default.warn("Str.getExtension: url is not a string", { url }); + return void 0; + } + return (_b = (_a3 = url.split(".").pop()) === null || _a3 === void 0 ? void 0 : _a3.split("?")[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase(); + } + function getRawByteSize(inputChar) { + const onlyChar = String(inputChar); + const c = onlyChar.charCodeAt(0); + if (c < 1 << 7) { + return 1; + } + if (c < 1 << 11) { + return 2; + } + if (c < 1 << 16) { + return 3; + } + if (c < 1 << 21) { + return 4; + } + if (c < 1 << 26) { + return 5; + } + if (c < 1 << 31) { + return 6; + } + return Number.NaN; + } + function htmlDecode(s) { + return HtmlEntities.decode(s); + } + function htmlEncode(s) { + return HtmlEntities.encode(s); + } + function isTypeOf(obj, type) { + return Object.prototype.toString.call(obj) === `[object ${type}]`; + } + function isNumber(obj) { + return isTypeOf(obj, "Number"); + } + function isString(obj) { + return isTypeOf(obj, "String"); + } + function isUndefined(obj) { + return obj === void 0; + } + function isValidE164Phone(phone) { + return Constants.CONST.SMS.E164_REGEX.test(phone); + } + function isValidEmail(str2) { + if (!str2 || typeof str2 !== "string") { + return false; + } + const unicodeVersion = punycode_1.default.toUnicode(str2); + if (String(unicodeVersion).match(Constants.CONST.REG_EXP.EMOJI_RULE)) { + return false; + } + return !!String(str2).match(Constants.CONST.REG_EXP.EMAIL); + } + function maskFirstNCharacters(str2, num, mask) { + if (!isString(str2) || !isString(mask) || str2.length === 0 || !isNumber(num)) { + return str2; + } + return str2.substring(0, num).replaceAll(/./g, mask) + str2.substring(num); + } + function removeSMSDomain(text) { + return text.replaceAll(REMOVE_SMS_DOMAIN_PATTERN, ""); + } + function removeTrailingComma(str2) { + return str2.trim().replaceAll(/(,$)/g, ""); + } + function startsWith(haystack, needle) { + return isString(haystack) && isString(needle) && haystack.substring(0, needle.length) === needle; + } var Str6 = { /** * Return true if the string is ending with the provided suffix @@ -43391,18 +43503,14 @@ var require_str = __commonJS({ * @param s The string to decode. * @returns The decoded string. */ - htmlDecode(s) { - return HtmlEntities.decode(s); - }, + htmlDecode, /** * HTML encodes the given string. * * @param s The string to encode. * @return string @p s HTML encoded. */ - htmlEncode(s) { - return HtmlEntities.encode(s); - }, + htmlEncode, /** * Decodes the given HTML encoded string. * @@ -43444,7 +43552,7 @@ var require_str = __commonJS({ * @returns True when first === second, ignoring HTML encoding */ htmlEncodingInsensitiveEquals(first, second) { - return first === second || this.htmlDecode(first) === second || this.htmlEncode(first) === second; + return first === second || htmlDecode(first) === second || htmlEncode(first) === second; }, /** * Creates an ID that can be used as an HTML attribute from @p str. @@ -43527,36 +43635,14 @@ var require_str = __commonJS({ * one. * @returns Byte size of the character */ - getRawByteSize(inputChar) { - const onlyChar = String(inputChar); - const c = onlyChar.charCodeAt(0); - if (c < 1 << 7) { - return 1; - } - if (c < 1 << 11) { - return 2; - } - if (c < 1 << 16) { - return 3; - } - if (c < 1 << 21) { - return 4; - } - if (c < 1 << 26) { - return 5; - } - if (c < 1 << 31) { - return 6; - } - return Number.NaN; - }, + getRawByteSize, /** * Gets the length of a string in bytes, including non-ASCII characters * @returns The number of bytes used by string */ getByteLength(input) { const stringInput = String(input); - const byteLength = Array.from(stringInput).reduce((acc, char) => acc + this.getRawByteSize(char), 0); + const byteLength = Array.from(stringInput).reduce((acc, char) => acc + getRawByteSize(char), 0); return byteLength; }, /** @@ -43568,7 +43654,7 @@ var require_str = __commonJS({ const stringInput = String(input); let totalByteLength = 0; for (let i = 0; i < stringInput.length; i++) { - const charByteSize = this.getRawByteSize(stringInput[i]); + const charByteSize = getRawByteSize(stringInput[i]); if (charByteSize + totalByteLength > maxSize) { return `${stringInput.substr(0, i - 3)}...`; } @@ -43583,9 +43669,7 @@ var require_str = __commonJS({ * @param needle The case-sensitive string to search for * @returns True if the haystack starts with the needle. */ - startsWith(haystack, needle) { - return this.isString(haystack) && this.isString(needle) && haystack.substring(0, needle.length) === needle; - }, + startsWith, /** * Gets the textual value of the given string. * @@ -43593,7 +43677,7 @@ var require_str = __commonJS({ * @returns The text from within the HTML string. */ stripHTML(str2) { - if (!this.isString(str2)) { + if (!isString(str2)) { return ""; } return str2.replaceAll(/<[^>]*>?/gm, ""); @@ -43616,13 +43700,7 @@ var require_str = __commonJS({ * @param substr The substring to search for. * @returns The cut/trimmed string. */ - cutAfter(str2, substr) { - const index = str2.indexOf(substr); - if (index !== -1) { - return str2.substring(0, index); - } - return str2; - }, + cutAfter, /** * Returns a string containing all the characters str from after the first * occurrence of substr to the end of the string. @@ -43632,13 +43710,7 @@ var require_str = __commonJS({ * @param substr The substring to search for. * @returns The cut/trimmed string. */ - cutBefore(str2, substr) { - const index = str2.indexOf(substr); - if (index !== -1) { - return str2.substring(index + substr.length); - } - return str2; - }, + cutBefore, /** * Checks that the string is a domain name (e.g. example.com) * @@ -43665,16 +43737,7 @@ var require_str = __commonJS({ * * @returns True if the string is an email */ - isValidEmail(str2) { - if (!str2 || typeof str2 !== "string") { - return false; - } - const unicodeVersion = punycode_1.default.toUnicode(str2); - if (String(unicodeVersion).match(Constants.CONST.REG_EXP.EMOJI_RULE)) { - return false; - } - return !!String(str2).match(Constants.CONST.REG_EXP.EMAIL); - }, + isValidEmail, /** * Checks if the string is an valid email address formed during comment markdown formation. * @@ -43692,9 +43755,7 @@ var require_str = __commonJS({ * * @returns string with the trailing comma removed */ - removeTrailingComma(str2) { - return str2.trim().replaceAll(/(,$)/g, ""); - }, + removeTrailingComma, /** * Checks that the string is a list of coma separated email addresss. * @@ -43703,12 +43764,12 @@ var require_str = __commonJS({ * @returns True if all emails are valid or if input is empty */ areValidEmails(str2) { - const string = this.removeTrailingComma(str2); + const string = removeTrailingComma(str2); if (string === "") { return true; } const emails = string.split(","); - const result = emails.every((email) => this.isValidEmail(email.trim())); + const result = emails.every((email) => isValidEmail(email.trim())); return result; }, /** @@ -43725,9 +43786,7 @@ var require_str = __commonJS({ * * @returns The domain name in the email address. */ - extractEmailDomain(email) { - return this.cutBefore(email, "@"); - }, + extractEmailDomain, /** * Tries to extract the company name from the given email address * (e.g. "yelp" for "joe@yelp.co.uk"). @@ -43737,7 +43796,7 @@ var require_str = __commonJS({ * @returns The company name in the email address or null. */ extractCompanyNameFromEmailDomain(email) { - const domain = this.extractEmailDomain(email); + const domain = extractEmailDomain(email); if (!domain) { return null; } @@ -43756,7 +43815,7 @@ var require_str = __commonJS({ * @returns The local part in the email address. */ extractEmailLocalPart(email) { - return this.cutAfter(email, "@"); + return cutAfter(email, "@"); }, /** * Sanitize phone number to return only numbers. Return null if non valid phone number. @@ -43814,13 +43873,13 @@ var require_str = __commonJS({ * @returns true if the length is in the range, false otherwise */ isOfLength(str2, minimumLength, maximumLength) { - if (!this.isString(str2)) { + if (!isString(str2)) { return false; } if (str2.length < minimumLength) { return false; } - if (!this.isUndefined(maximumLength) && str2.length > maximumLength) { + if (!isUndefined(maximumLength) && str2.length > maximumLength) { return false; } return true; @@ -43873,9 +43932,7 @@ var require_str = __commonJS({ * * @returns Returns true if the haystack contains the needle */ - contains(haystack, needle) { - return haystack.indexOf(needle) !== -1; - }, + contains, /** * Returns true if the haystack contains the needle, ignoring case * @@ -43885,7 +43942,7 @@ var require_str = __commonJS({ * @returns Returns true if the haystack contains the needle, ignoring case */ caseInsensitiveContains(haystack, needle) { - return this.contains(haystack.toLowerCase(), needle.toLowerCase()); + return contains(haystack.toLowerCase(), needle.toLowerCase()); }, /** * Case insensitive compare function @@ -43897,11 +43954,7 @@ var require_str = __commonJS({ * 1 if first string > second string * 0 if first string = second string */ - caseInsensitiveCompare(strA, strB) { - const lowerCaseStrA = strA.toLocaleLowerCase(); - const lowerCaseStrB = strB.toLocaleLowerCase(); - return this.compare(lowerCaseStrA, lowerCaseStrB); - }, + caseInsensitiveCompare, /** * Case insensitive equals * @@ -43910,7 +43963,7 @@ var require_str = __commonJS({ * @returns true when first == second except for case */ caseInsensitiveEquals(strA, strB) { - return this.caseInsensitiveCompare(strA, strB) === 0; + return caseInsensitiveCompare(strA, strB) === 0; }, /** * Compare function @@ -43922,15 +43975,7 @@ var require_str = __commonJS({ * 1 if first string > second string * 0 if first string = second string */ - compare(strA, strB) { - if (strA < strB) { - return -1; - } - if (strA > strB) { - return 1; - } - return 0; - }, + compare, /** * Check if a file extension is supported by SmartReports */ @@ -43949,45 +43994,37 @@ var require_str = __commonJS({ const accountNumber = String(num); const len = accountNumber.length; if (len < 6 || len > 20) { - return this.maskFirstNCharacters(accountNumber, len, "X"); + return maskFirstNCharacters(accountNumber, len, "X"); } if (len < 14) { - return this.maskFirstNCharacters(accountNumber, len - 4, "X"); + return maskFirstNCharacters(accountNumber, len - 4, "X"); } const first = accountNumber.substr(0, 6); const last = accountNumber.substr(7); - const masked = this.maskFirstNCharacters(last, len - 11, "X"); + const masked = maskFirstNCharacters(last, len - 11, "X"); return `${first}${masked}`; }, /** * Checks if something is a string * Stolen from underscore */ - isString(obj) { - return this.isTypeOf(obj, "String"); - }, + isString, /** * Checks if something is a number * Stolen from underscore * @param obj */ - isNumber(obj) { - return this.isTypeOf(obj, "Number"); - }, + isNumber, /** * Checks if something is a certain type * Stolen from underscore */ - isTypeOf(obj, type) { - return Object.prototype.toString.call(obj) === `[object ${type}]`; - }, + isTypeOf, /** * Checks to see if something is undefined * Stolen from underscore */ - isUndefined(obj) { - return obj === void 0; - }, + isUndefined, /** * Replace first N characters of the string with maskChar * eg: maskFirstNCharacters( '1234567890', 6, 'X' ) yields XXXXXX7890 @@ -43996,12 +44033,7 @@ var require_str = __commonJS({ * @param mask String we want replace the first N chars with * @returns Masked string */ - maskFirstNCharacters(str2, num, mask) { - if (!this.isString(str2) || !this.isString(mask) || str2.length === 0 || !this.isNumber(num)) { - return str2; - } - return str2.substring(0, num).replaceAll(/./g, mask) + str2.substring(num); - }, + maskFirstNCharacters, /** * Trim a string */ @@ -44013,7 +44045,7 @@ var require_str = __commonJS({ * @param percentageString The percentage as a string */ percentageStringToNumber(percentageString) { - return Number(this.cutAfter(percentageString, "%")); + return Number(cutAfter(percentageString, "%")); }, /** * Remove all the spaces from a string @@ -44057,7 +44089,7 @@ var require_str = __commonJS({ * Converts a value to boolean, case-insensitive. */ toBool(value) { - if (this.isString(value)) { + if (isString(value)) { return value.toLowerCase() === "true"; } return !!value; @@ -44107,9 +44139,7 @@ var require_str = __commonJS({ /** * Check for whether a phone number is valid according to E.164 standard. */ - isValidE164Phone(phone) { - return Constants.CONST.SMS.E164_REGEX.test(phone); - }, + isValidE164Phone, /** * Check for whether a phone number is valid in different formats/standards. For example: * significant: 4404589784 @@ -44135,14 +44165,12 @@ var require_str = __commonJS({ /** * Returns text without our SMS domain */ - removeSMSDomain(text) { - return text.replaceAll(REMOVE_SMS_DOMAIN_PATTERN, ""); - }, + removeSMSDomain, /** * Returns true if the text is a valid E.164 phone number with our SMS domain removed */ isSMSLogin(text) { - return this.isValidE164Phone(this.removeSMSDomain(text)); + return isValidE164Phone(removeSMSDomain(text)); }, /** * This method will return all matches of a single regex like preg_match_all() in PHP. This is not a common part of @@ -44195,7 +44223,7 @@ var require_str = __commonJS({ return url; } const website = match2[3] ? match2[2] : `${defaultScheme}://${match2[2]}`; - return website.toLowerCase() + this.cutBefore(match2[1], match2[2]); + return website.toLowerCase() + cutBefore(match2[1], match2[2]); }, /** * Checks if parameter is a string or function @@ -44207,14 +44235,7 @@ var require_str = __commonJS({ * Get file extension for a given url with or * without query parameters */ - getExtension(url) { - var _a3, _b; - if (typeof url !== "string") { - Log_1.default.warn("Str.getExtension: url is not a string", { url }); - return void 0; - } - return (_b = (_a3 = url.split(".").pop()) === null || _a3 === void 0 ? void 0 : _a3.split("?")[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase(); - }, + getExtension, /** * Takes in a URL and checks if the file extension is PDF * @@ -44222,7 +44243,7 @@ var require_str = __commonJS({ * @returns Whether file path is PDF or not */ isPDF(url) { - return this.getExtension(url) === "pdf"; + return getExtension(url) === "pdf"; }, /** * Takes in a URL and checks if the file extension is an image @@ -44233,7 +44254,7 @@ var require_str = __commonJS({ * https://reactnative.dev/docs/image#source */ isImage(url) { - const extension = this.getExtension(url); + const extension = getExtension(url); if (!extension) { return false; } @@ -44249,7 +44270,7 @@ var require_str = __commonJS({ * https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs */ isVideo(url) { - const extension = this.getExtension(url); + const extension = getExtension(url); if (!extension) { return false; } @@ -44262,7 +44283,7 @@ var require_str = __commonJS({ * @returns True if is a domain account email, otherwise false. */ isDomainEmail(email) { - return this.startsWith(email, "+@"); + return startsWith(email, "+@"); }, /** * Find the minimum indentation of any line in the string, @@ -45400,13 +45421,17 @@ ${g2} }, { name: "anchor", - regex: /<(a)[^><]*href\s*=\s*(['"])(.*?)\2(?:".*?"|'.*?'|[^'"><])*>([\s\S]*?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi, - replacement: (_extras, _match, _g1, _g2, g3, g4) => { - const email = g3.startsWith("mailto:") ? g3.slice(7) : ""; - if (email === g4) { - return email; + regex: /(\]\()?<(a)[^><]*href\s*=\s*(['"])(.*?)\3(?:".*?"|'.*?'|[^'"><])*>([\s\S]*?)<\/\2>(?![^<]*(<\/pre>|<\/code>))/gi, + replacement: (_extras, _match, g1, _g2, _g3, g4, g5) => { + const incompleteLinkStart = g1 !== null && g1 !== void 0 ? g1 : ""; + const email = g4.startsWith("mailto:") ? g4.slice(7) : ""; + if (email === g5) { + return `${incompleteLinkStart}${email}`; + } + if (incompleteLinkStart && (g4 === g5 || g4 === `https://${g5}`)) { + return `${incompleteLinkStart}${g5}`; } - return `[${g4}](${email || g3})`; + return `${incompleteLinkStart}[${g5}](${email || g4})`; } }, { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 63acb97ed248..b003bee00d33 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -146,7 +146,7 @@ PODS: - ExpoModulesCore - ExpoLogBox (57.0.1): - React-Core - - ExpoModulesCore (57.0.7): + - ExpoModulesCore (57.0.15): - ExpoModulesJSI - hermes-engine - RCTRequired @@ -170,10 +170,10 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoModulesJSI (57.0.4): + - ExpoModulesJSI (57.0.8): - React-Core - ReactCommon - - ExpoModulesWorklets (57.0.7): + - ExpoModulesWorklets (57.0.15): - ExpoModulesCore - ExpoModulesJSI - ExpoStoreReview (57.0.1): @@ -2955,7 +2955,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.342): + - RNLiveMarkdown (0.1.343): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3101,7 +3101,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNReanimated (4.5.5): + - RNReanimated (4.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3123,12 +3123,12 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNReanimated/apple (= 4.5.5) - - RNReanimated/common (= 4.5.5) - - RNReanimated/view (= 4.5.5) + - RNReanimated/apple (= 4.7.0) + - RNReanimated/common (= 4.7.0) + - RNReanimated/view (= 4.7.0) - RNWorklets - Yoga - - RNReanimated/apple (4.5.5): + - RNReanimated/apple (4.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3152,7 +3152,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNReanimated/common (4.5.5): + - RNReanimated/common (4.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3176,7 +3176,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNReanimated/view (4.5.5): + - RNReanimated/view (4.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3337,7 +3337,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNWorklets (0.11.1): + - RNWorklets (0.13.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3359,10 +3359,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNWorklets/apple (= 0.11.1) - - RNWorklets/common (= 0.11.1) + - RNWorklets/apple (= 0.13.0) + - RNWorklets/common (= 0.13.0) - Yoga - - RNWorklets/apple (0.11.1): + - RNWorklets/apple (0.13.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3385,7 +3385,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNWorklets/common (0.11.1): + - RNWorklets/common (0.13.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3961,9 +3961,9 @@ SPEC CHECKSUMS: ExpoImageManipulator: d3c962dabd86b331980f8ec6794c6073b3cb541a ExpoLocation: 187949911b868f72e34a8a6f8f487207ba86388a ExpoLogBox: 8a3cfa2897e088083cd49fc1ff7fdb2de4385547 - ExpoModulesCore: 5b2895ff83c42d320aaaa713c44051d84b0de698 - ExpoModulesJSI: 3accd404d715d63e7b04015d20c857e903e3bc73 - ExpoModulesWorklets: b21b8f233b5fd71ca74327217601fa4ead9ddaaf + ExpoModulesCore: 9c02efc618119023146b9b8db9c0930e43ac977d + ExpoModulesJSI: 4f849c58e99b57c2438b21ed6bc2fcb6f984a0d1 + ExpoModulesWorklets: 5e70b46d570c5b1b702c18bc8ac8d1af0171450e ExpoStoreReview: 2a0f6b8e04112aea2f6e3e1cb84109d78f4d041f ExpoTaskManager: f68326227852a30148700965c37c058c1473c6a2 ExpoVideo: b1d9a325ddcd4f3ed8ccb374c01b688e98bc6a04 @@ -4109,18 +4109,18 @@ SPEC CHECKSUMS: RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 RNGestureHandler: 85e32defcf23dd3b023356355c5c1dd1d9d51bde RNGoogleSignin: 89877c73f0fbf6af2038fbdb7b73b5a25b8330cc - RNLiveMarkdown: f4c383d846d5d756f42bdf6006e85d5baf0ebfc8 + RNLiveMarkdown: b6894bb864d517f51ea80c98facd26571f2cd0fe RNLocalize: 82e7aa2857ce5dcd9e2a0c04f8c34c2ed50821df rnmapbox-maps: 54eb40428a4fd89f484a492fc7186bc1c1eea9ce RNNitroSQLite: c986393b7cb0e5e4587d0c9f7108e56ee8db9627 RNPermissions: 32bd3b977f28442af0900b102f4458f0f9f9b77c RNReactNativeHapticFeedback: 6c8677db48f62a635e6a2065f5d2bd02498d7a73 - RNReanimated: f093c30aa11e45a8044dcc19417f44cffa1c4e1d + RNReanimated: e75c988a6527ec651ea2957b93891ddb64d530d4 RNScreens: 8708989f700bdc3dc5abc3c77aa3b5f3e73ce2b2 RNSentry: c878a3c462c1097f63a81615c784d44641ae92e4 RNShare: eb378c57de30a83b0253eed74762ee99249f47d7 RNSVG: 394cfd0518613b144c65ffe69045f827b78a7a7c - RNWorklets: f0734080219ea54d479c179da498c9b24611faed + RNWorklets: 3da8f468c848547646f4e6b12fb46ad93a2cdee2 SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c diff --git a/jest.config.js b/jest.config.js index 58b96c5f43b7..61fe030d49f2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,6 +16,7 @@ const emitsUntestedFiles = !shardMatch || shardMatch[1] === '1'; module.exports = { preset: 'jest-expo', + resolver: '/jest/resolver.ts', collectCoverageFrom: emitsUntestedFiles ? ['/src/**/*.{ts,tsx,js,jsx}', '!/src/**/__mocks__/**', '!/src/**/tests/**', '!**/*.d.ts'] : undefined, // Keeps the instrumented set identical to `collectCoverageFrom` above on the shards that skip it. coveragePathIgnorePatterns: ['/node_modules/', '/(?!src/)', '/__mocks__/', '/src/.*/tests/', '\\.d\\.ts$'], diff --git a/jest/resolver.ts b/jest/resolver.ts new file mode 100644 index 000000000000..b0093552708c --- /dev/null +++ b/jest/resolver.ts @@ -0,0 +1,28 @@ +// Since Reanimated 4.6 its shared files are split into `.native`/`.web` variants and Jest resolves the `.native` ones, +// which call native-only APIs at import time (e.g. `setCSSEventHandler`) and crash every suite that imports Reanimated. +// Reanimated's resolver maps those files to their web variants (and chains the Worklets resolver), but replacing the +// jest-expo preset's React Native resolver would break mocking `react-native/*` subpaths, so this chains both. +// +// Jest `require`s this file untransformed, so Node strips the types and loads it as an ES module: Jest reads the `sync` +// export, and both resolvers are untyped CommonJS loaded through `createRequire`. +import {createRequire} from 'node:module'; + +type ResolverOptions = { + defaultResolver: SyncResolver; +}; + +/** The subset of Jest's `SyncResolver` (from `jest-resolve`) that this file relies on. */ +type SyncResolver = (path: string, options: ResolverOptions) => string; + +const requireResolver: (id: string) => SyncResolver = createRequire(import.meta.url); +const reactNativeResolver = requireResolver('@react-native/jest-preset/jest/resolver'); +const reanimatedResolver = requireResolver('react-native-reanimated/jest/resolver'); + +const resolver: SyncResolver = (request, options) => + reanimatedResolver(request, { + ...options, + defaultResolver: (path, nextOptions) => reactNativeResolver(path, {...nextOptions, defaultResolver: options.defaultResolver}), + }); + +export default resolver; +export {resolver as sync}; diff --git a/jest/setup.ts b/jest/setup.ts index 9afaa8f219a1..d7e3eec8321e 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -152,11 +152,6 @@ jest.mock('react-native-reanimated', () => ({ // react-native-reanimated/mock leaves dispatchCommand out (see its own "ADD ME IF NEEDED" comment). forceClearInput // (src/libs/ComponentUtils) dispatches it from a UI-thread worklet, so any test exercising that path needs it mocked. dispatchCommand: jest.fn(), - // react-native-reanimated/mock also leaves out useComposedEventHandler, useHandler and isSharedValue, which Gesture Handler 3 - // calls from its detectors (including the ones behind its ScrollView / FlatList wrappers). - useComposedEventHandler: jest.fn(() => () => {}), - useHandler: jest.fn(() => ({context: {}, doDependenciesDiffer: false})), - isSharedValue: jest.fn((value: unknown) => typeof value === 'object' && value !== null && 'value' in value && 'get' in value && typeof value.get === 'function'), useReducedMotion: jest.fn, useScrollViewOffset: jest.fn(() => 0), useAnimatedRef: jest.fn(() => jest.fn()), diff --git a/package-lock.json b/package-lock.json index 3eb37d7e7423..4578225e08d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@expensify/nitro-utils": "file:./modules/ExpensifyNitroUtils", "@expensify/react-native-background-task": "file:./modules/background-task", "@expensify/react-native-hybrid-app": "file:./modules/hybrid-app", - "@expensify/react-native-live-markdown": "0.1.342", + "@expensify/react-native-live-markdown": "0.1.343", "@expensify/react-native-wallet": "0.1.22", "@expo/metro-config": "57.0.7", "@expo/metro-runtime": "57.0.7", @@ -68,7 +68,7 @@ "date-fns-tz": "^3.2.0", "dom-serializer": "^0.2.2", "domhandler": "^5.0.3", - "expensify-common": "2.0.207", + "expensify-common": "2.0.211", "expo": "57.0.8", "expo-asset": "57.0.7", "expo-audio": "57.0.3", @@ -76,7 +76,7 @@ "expo-image": "57.0.1", "expo-image-manipulator": "57.0.6", "expo-location": "57.0.6", - "expo-modules-core": "57.0.7", + "expo-modules-core": "57.0.15", "expo-store-review": "57.0.1", "expo-task-manager": "57.0.6", "expo-video": "57.0.2", @@ -136,7 +136,7 @@ "react-native-qrcode-svg": "6.3.21", "react-native-quick-base64": "^3.0.1", "react-native-quick-crypto": "^1.1.6", - "react-native-reanimated": "4.5.5", + "react-native-reanimated": "4.7.0", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "5.9.1", "react-native-screens": "4.28.0", @@ -148,7 +148,7 @@ "react-native-vision-camera": "^4.7.2", "react-native-web": "0.21.2", "react-native-webview": "16.0.0", - "react-native-worklets": "0.11.1", + "react-native-worklets": "0.13.0", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", @@ -5936,9 +5936,9 @@ "link": true }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.342", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.342.tgz", - "integrity": "sha512-xt8t0pUTHHPfY9G25wZ0rQUP44eFdVQp44Mo2Ji2H66zr/qjUnDqDOQPJxi5azucUwI/uORmmMJOvXMdKsEDNg==", + "version": "0.1.343", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.343.tgz", + "integrity": "sha512-3M1FQ6YgMy9dv0LiMORI1rwKwpJ0z3Ce94NkuxqGvObHPuee1vHX2Ozi7th97c+MzBXkydDJgnq20yH20jic9Q==", "license": "MIT", "workspaces": [ "./example", @@ -26874,9 +26874,9 @@ } }, "node_modules/expensify-common": { - "version": "2.0.207", - "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.207.tgz", - "integrity": "sha512-AHrgp9LZkS9z/37G5ZbKbIwa9GqSCPVwfwilS1yhSQmFyL6Jb/eDxlopMfthaTbXO0NHTPRH2UmImlUtdlNYvw==", + "version": "2.0.211", + "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.211.tgz", + "integrity": "sha512-BMFDPN4UJrvfESDViMMt5p/JA8JXWfBpCYshqy+v6hhxgZ5D5oGcko3mLPi4eoNVD1M+etkIY4pQJIKb68D3hw==", "license": "MIT", "dependencies": { "awesome-phonenumber": "^5.4.0", @@ -27217,13 +27217,13 @@ } }, "node_modules/expo-modules-core": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.7.tgz", - "integrity": "sha512-5HrbCfgYmLs0a5dzfM4GmRGelVTPIg+eYp0vmSbWnKRTOPj5DyVOfg1rHGEsuNKp07QwDxfLJfQVp8CNbuvxMQ==", + "version": "57.0.15", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.15.tgz", + "integrity": "sha512-HBxPXsx3eVLLRjAY57/8ghKDWIxjaEbluKKYFIq1OHPJC85ZfHsawwc0SOU4Z/qu+mdLz7Pwt5IkkWimeNSH5A==", "license": "MIT", "dependencies": { "@expo/expo-modules-macros-plugin": "0.6.1", - "expo-modules-jsi": "~57.0.4", + "expo-modules-jsi": "~57.0.7", "invariant": "^2.2.4" }, "peerDependencies": { @@ -27238,9 +27238,9 @@ } }, "node_modules/expo-modules-jsi": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.4.tgz", - "integrity": "sha512-vt7FyqUqqFXiRVnBqYD7y+GSPTgeua5Ocoy0+SYt+RSHkZEA2Fyop7If3g1TYDzQObYybPRo7TG2Rle1XLaWFw==", + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.8.tgz", + "integrity": "sha512-MFwvKP0FXLkEET27KsKvOh/L7Dpu2d00UgzAlmKozJgtI23WTKSyKPxu+EOSs9peSgkHzKI0vQOhWjOseGBMkQ==", "license": "MIT", "peerDependencies": { "react-native": "*" @@ -38001,9 +38001,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.5.5.tgz", - "integrity": "sha512-xtJXZRZ1vkec1AIUVG02St2sbyZS5jc7227TlDu/HonqUYPeZAfiC4RI2jqL1dVMNBu1+7oBx/c9QkjF2kg6Lg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.7.0.tgz", + "integrity": "sha512-8TyTrPisqz7WzmbhCz8F60Uw7NI2QeYWrY5Hk58/kurCA5L5KuHmhyQjxeq5k2YalpXUAKbdkn/mAMZWm/fU0g==", "license": "MIT", "dependencies": { "react-native-is-edge-to-edge": "^1.3.1", @@ -38011,8 +38011,8 @@ }, "peerDependencies": { "react": "*", - "react-native": "0.83 - 0.86", - "react-native-worklets": "0.10.x - 0.11.x" + "react-native": "0.86 - 0.88", + "react-native-worklets": "0.13.x" } }, "node_modules/react-native-reanimated/node_modules/semver": { @@ -38297,9 +38297,9 @@ } }, "node_modules/react-native-worklets": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.11.1.tgz", - "integrity": "sha512-e+deeFdC45RrQcRh0bYr0fbaaXSB6+mGTX4xlEzT76PCvNJEOmu/5aDbT7gpd9AMFAGhkO2b0eoDBErWuEvZ8Q==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.13.0.tgz", + "integrity": "sha512-1OrhAeAKaZF4XrBa4YbPQbKzNOpjh52HDlId8e6ete1OCHNdAlrQHzG4gznJ225EAj3dgQXHViJvK51M9B52jQ==", "license": "MIT", "dependencies": { "@babel/generator": "^7.27.1", @@ -38314,14 +38314,16 @@ "@babel/preset-typescript": "^7.28.5", "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", - "semver": "^7.7.4" + "semver": "^7.7.4", + "whatwg-fetch": "^3.6.20" }, "peerDependencies": { "@babel/core": "*", "@react-native/metro-config": "*", "react": "*", - "react-native": "0.83 - 0.86" + "react-native": "0.86 - 0.88" } }, "node_modules/react-native-worklets/node_modules/semver": { @@ -42976,7 +42978,9 @@ } }, "node_modules/whatwg-fetch": { - "version": "3.6.2", + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, "node_modules/whatwg-url": { diff --git a/package.json b/package.json index 6b353b310e9e..203b3b0abe4b 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "@expensify/nitro-utils": "file:./modules/ExpensifyNitroUtils", "@expensify/react-native-background-task": "file:./modules/background-task", "@expensify/react-native-hybrid-app": "file:./modules/hybrid-app", - "@expensify/react-native-live-markdown": "0.1.342", + "@expensify/react-native-live-markdown": "0.1.343", "@expensify/react-native-wallet": "0.1.22", "@expo/metro-config": "57.0.7", "@expo/metro-runtime": "57.0.7", @@ -144,7 +144,7 @@ "date-fns-tz": "^3.2.0", "dom-serializer": "^0.2.2", "domhandler": "^5.0.3", - "expensify-common": "2.0.207", + "expensify-common": "2.0.211", "expo": "57.0.8", "expo-asset": "57.0.7", "expo-audio": "57.0.3", @@ -152,7 +152,7 @@ "expo-image": "57.0.1", "expo-image-manipulator": "57.0.6", "expo-location": "57.0.6", - "expo-modules-core": "57.0.7", + "expo-modules-core": "57.0.15", "expo-store-review": "57.0.1", "expo-task-manager": "57.0.6", "expo-video": "57.0.2", @@ -212,7 +212,7 @@ "react-native-qrcode-svg": "6.3.21", "react-native-quick-base64": "^3.0.1", "react-native-quick-crypto": "^1.1.6", - "react-native-reanimated": "4.5.5", + "react-native-reanimated": "4.7.0", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "5.9.1", "react-native-screens": "4.28.0", @@ -224,7 +224,7 @@ "react-native-vision-camera": "^4.7.2", "react-native-web": "0.21.2", "react-native-webview": "16.0.0", - "react-native-worklets": "0.11.1", + "react-native-worklets": "0.13.0", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", @@ -434,7 +434,7 @@ "expo": "57.0.8" }, "expo-modules-core": { - "react-native-worklets": "0.11.1" + "react-native-worklets": "0.13.0" } }, "expo": { diff --git a/patches/expo-modules-core/details.md b/patches/expo-modules-core/details.md index 50bed85a837c..0ad1cb38f52e 100644 --- a/patches/expo-modules-core/details.md +++ b/patches/expo-modules-core/details.md @@ -1,6 +1,6 @@ # `expo-modules-core` patches -### [expo-modules-core+57.0.7+001+disableViewRecycling.patch](expo-modules-core+57.0.7+001+disableViewRecycling.patch) +### [expo-modules-core+57.0.15+001+disableViewRecycling.patch](expo-modules-core+57.0.15+001+disableViewRecycling.patch) - Reason: Opt-out from RN Fabric view recycling - Upstream PR/issue: https://expo.canny.io/feature-requests/p/opt-out-of-react-native-fabric-view-recycling-for-expofabricviewobjc diff --git a/patches/expo-modules-core/expo-modules-core+57.0.7+001+disableViewRecycling.patch b/patches/expo-modules-core/expo-modules-core+57.0.15+001+disableViewRecycling.patch similarity index 83% rename from patches/expo-modules-core/expo-modules-core+57.0.7+001+disableViewRecycling.patch rename to patches/expo-modules-core/expo-modules-core+57.0.15+001+disableViewRecycling.patch index 88bc73dcde81..2a6f018796d9 100644 --- a/patches/expo-modules-core/expo-modules-core+57.0.7+001+disableViewRecycling.patch +++ b/patches/expo-modules-core/expo-modules-core+57.0.15+001+disableViewRecycling.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm b/node_modules/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm -index 0645540..9243248 100644 +index 370015f..1eb91cb 100644 --- a/node_modules/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm +++ b/node_modules/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm -@@ -137,6 +137,11 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask +@@ -218,6 +218,11 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask } } diff --git a/patches/react-native-reanimated/details.md b/patches/react-native-reanimated/details.md deleted file mode 100644 index 638154d65bb8..000000000000 --- a/patches/react-native-reanimated/details.md +++ /dev/null @@ -1,9 +0,0 @@ - -# `react-native-reanimated` patches - -### [react-native-reanimated+4.5.5+001+catch-all-exceptions-on-stoi.patch](react-native-reanimated+4.5.5+001+catch-all-exceptions-on-stoi.patch) - -- Reason: Reanimated wasn't able to catch an exception here, so the catch clause was broadened. -- Upstream PR/issue: 🛑 -- E/App issue: 🛑 -- PR Introducing Patch: [Upgrade to React Native 0.76](https://github.com/Expensify/App/pull/51475) diff --git a/patches/react-native-reanimated/react-native-reanimated+4.5.5+001+catch-all-exceptions-on-stoi.patch b/patches/react-native-reanimated/react-native-reanimated+4.5.5+001+catch-all-exceptions-on-stoi.patch deleted file mode 100644 index 73ced70d9f6e..000000000000 --- a/patches/react-native-reanimated/react-native-reanimated+4.5.5+001+catch-all-exceptions-on-stoi.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp b/node_modules/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp -index 9402e3e..ddc29cd 100644 ---- a/node_modules/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp -+++ b/node_modules/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp -@@ -986,7 +986,7 @@ void LayoutAnimationsProxy_Legacy::transferConfigFromNativeID(const std::string - auto nativeId = stoi(nativeIdString); - layoutAnimationsManager_->transferConfigFromNativeID(nativeId, tag); - } catch (std::invalid_argument) { -- } catch (std::out_of_range) {} -+ } catch (...) {} - } - - // When entering animations start, we temporarily set opacity to 0 diff --git a/patches/whatwg-fetch/details.md b/patches/whatwg-fetch/details.md deleted file mode 100644 index 26f043b4e76a..000000000000 --- a/patches/whatwg-fetch/details.md +++ /dev/null @@ -1,13 +0,0 @@ -# `whatwg-patches` patches - -### [whatwg-fetch+3.6.2+001+use-microtask-queue-for-xhr-events.patch](whatwg-fetch+3.6.2+001+use-microtask-queue-for-xhr-events.patch) - -- Reason: - - ``` - This patch replaces setTimeout(0) with queueMicrotask for XHR event handlers within the whatwg-fetch polyfill. - ``` - -- Upstream PR/issue: https://github.com/JakeChampion/fetch/issues/1484 -- E/App issue: https://github.com/Expensify/App/issues/90309 -- PR introducing patch: https://github.com/Expensify/App/pull/94399 diff --git a/patches/whatwg-fetch/whatwg-fetch+3.6.2+001+use-microtask-queue-for-xhr-events.patch b/patches/whatwg-fetch/whatwg-fetch+3.6.2+001+use-microtask-queue-for-xhr-events.patch deleted file mode 100644 index 3785dcfe07e0..000000000000 --- a/patches/whatwg-fetch/whatwg-fetch+3.6.2+001+use-microtask-queue-for-xhr-events.patch +++ /dev/null @@ -1,80 +0,0 @@ -diff --git a/node_modules/whatwg-fetch/dist/fetch.umd.js b/node_modules/whatwg-fetch/dist/fetch.umd.js -index 8c3269e..b3bad8f 100644 ---- a/node_modules/whatwg-fetch/dist/fetch.umd.js -+++ b/node_modules/whatwg-fetch/dist/fetch.umd.js -@@ -525,27 +525,27 @@ - }; - options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL'); - var body = 'response' in xhr ? xhr.response : xhr.responseText; -- setTimeout(function() { -+ queueMicrotask(function() { - resolve(new Response(body, options)); -- }, 0); -+ }); - }; - - xhr.onerror = function() { -- setTimeout(function() { -+ queueMicrotask(function() { - reject(new TypeError('Network request failed')); -- }, 0); -+ }); - }; - - xhr.ontimeout = function() { -- setTimeout(function() { -+ queueMicrotask(function() { - reject(new TypeError('Network request failed')); -- }, 0); -+ }); - }; - - xhr.onabort = function() { -- setTimeout(function() { -+ queueMicrotask(function() { - reject(new exports.DOMException('Aborted', 'AbortError')); -- }, 0); -+ }); - }; - - function fixUrl(url) { -diff --git a/node_modules/whatwg-fetch/fetch.js b/node_modules/whatwg-fetch/fetch.js -index 5050a24..f70dbe6 100644 ---- a/node_modules/whatwg-fetch/fetch.js -+++ b/node_modules/whatwg-fetch/fetch.js -@@ -519,27 +519,27 @@ export function fetch(input, init) { - } - options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') - var body = 'response' in xhr ? xhr.response : xhr.responseText -- setTimeout(function() { -+ queueMicrotask(function() { - resolve(new Response(body, options)) -- }, 0) -+ }) - } - - xhr.onerror = function() { -- setTimeout(function() { -+ queueMicrotask(function() { - reject(new TypeError('Network request failed')) -- }, 0) -+ }) - } - - xhr.ontimeout = function() { -- setTimeout(function() { -+ queueMicrotask(function() { - reject(new TypeError('Network request failed')) -- }, 0) -+ }) - } - - xhr.onabort = function() { -- setTimeout(function() { -+ queueMicrotask(function() { - reject(new DOMException('Aborted', 'AbortError')) -- }, 0) -+ }) - } - - function fixUrl(url) { diff --git a/src/components/DatePicker/CalendarPicker/index.tsx b/src/components/DatePicker/CalendarPicker/index.tsx index a93e1724cc1a..89aaa22f5c15 100644 --- a/src/components/DatePicker/CalendarPicker/index.tsx +++ b/src/components/DatePicker/CalendarPicker/index.tsx @@ -239,16 +239,67 @@ function CalendarPicker({ }); const webOnlyMarginStyle = isSmallScreenWidth ? {} : styles.mh1; - const calendarContainerStyle = isSmallScreenWidth ? [webOnlyMarginStyle, themeStyles.calendarBodyContainer] : [webOnlyMarginStyle, animatedStyle]; const headerPaddingStyle = headerContainerStyle ?? themeStyles.ph3; - // On mobile (isSmallScreenWidth is always true on native), the height animation is skipped - // so using Animated.View is unnecessary. Using a plain View with collapsable={false} avoids - // activating Reanimated's Fabric commit hook, which on Android can interfere with React's - // reconciliation of child view styles and prevent day-selection background changes from painting. - const CalendarBody = isSmallScreenWidth ? View : Animated.View; const getAccessibilityState = useCallback((isSelected: boolean) => ({selected: isSelected}), []); + const calendarWeeks = calendarDaysMatrix?.map((week) => ( + + {week.map((day, index) => { + const currentDate = new Date(currentYearView, currentMonthView, day); + const isBeforeMinDate = currentDate < startOfDay(new Date(minDate)); + const isAfterMaxDate = currentDate > startOfDay(new Date(maxDate)); + const isSelectable = selectableDates ? selectableDates?.some((date) => isSameDay(parseISO(date), currentDate)) : true; + const isDisabled = !day || isBeforeMinDate || isAfterMaxDate || !isSelectable; + const isSelected = !!day && isSameDay(parseISO(value.toString()), new Date(currentYearView, currentMonthView, day)); + const handleOnPress = () => { + if (!day || isDisabled) { + return; + } + + onDayPressed(day); + }; + const key = `${index}_day-${day}`; + const fullDate = day ? new Date(currentYearView, currentMonthView, day) : null; + const accessibilityDateLabel = fullDate ? DateUtils.formatToLongDateWithWeekday(fullDate, dateFnsLocale) : ''; + return ( + + {({hovered, pressed}) => ( + + {day} + + )} + + ); + })} + + )); + return ( ))} - - {calendarDaysMatrix?.map((week) => ( - - {week.map((day, index) => { - const currentDate = new Date(currentYearView, currentMonthView, day); - const isBeforeMinDate = currentDate < startOfDay(new Date(minDate)); - const isAfterMaxDate = currentDate > startOfDay(new Date(maxDate)); - const isSelectable = selectableDates ? selectableDates?.some((date) => isSameDay(parseISO(date), currentDate)) : true; - const isDisabled = !day || isBeforeMinDate || isAfterMaxDate || !isSelectable; - const isSelected = !!day && isSameDay(parseISO(value.toString()), new Date(currentYearView, currentMonthView, day)); - const handleOnPress = () => { - if (!day || isDisabled) { - return; - } - - onDayPressed(day); - }; - const key = `${index}_day-${day}`; - const fullDate = day ? new Date(currentYearView, currentMonthView, day) : null; - const accessibilityDateLabel = fullDate ? DateUtils.formatToLongDateWithWeekday(fullDate, dateFnsLocale) : ''; - return ( - - {({hovered, pressed}) => ( - - {day} - - )} - - ); - })} - - ))} - + {/* On mobile (isSmallScreenWidth is always true on native), the height animation is skipped + so using Animated.View is unnecessary. Using a plain View with collapsable={false} avoids + activating Reanimated's Fabric commit hook, which on Android can interfere with React's + reconciliation of child view styles and prevent day-selection background changes from painting. */} + {isSmallScreenWidth ? ( + + {calendarWeeks} + + ) : ( + + {calendarWeeks} + + )}