diff --git a/.github/actions/javascript/authorChecklist/index.js b/.github/actions/javascript/authorChecklist/index.js
index 528a0a11498a..b20cc83498ba 100644
--- a/.github/actions/javascript/authorChecklist/index.js
+++ b/.github/actions/javascript/authorChecklist/index.js
@@ -255,7 +255,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -268,7 +268,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -281,85 +281,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -393,6 +397,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js
index f042dbb38a91..6b8401a08d6d 100644
--- a/.github/actions/javascript/awaitStagingDeploys/index.js
+++ b/.github/actions/javascript/awaitStagingDeploys/index.js
@@ -367,7 +367,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -380,7 +380,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -393,85 +393,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -505,6 +509,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/checkDeployBlockers/index.js b/.github/actions/javascript/checkDeployBlockers/index.js
index 8e10f8b1d8b6..dffc089ea5c5 100644
--- a/.github/actions/javascript/checkDeployBlockers/index.js
+++ b/.github/actions/javascript/checkDeployBlockers/index.js
@@ -334,7 +334,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -347,7 +347,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -360,85 +360,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -472,6 +476,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js
index 4441348a3c36..63398614fd11 100644
--- a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js
+++ b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js
@@ -40,8 +40,11 @@ async function run() {
// Next, we generate the checklist body
let checklistBody = '';
+ let checklistAssignees = [];
if (shouldCreateNewDeployChecklist) {
- checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+ const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+ checklistBody = issueBody;
+ checklistAssignees = issueAssignees;
} else {
// Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs
const PRList = _.reduce(
@@ -94,7 +97,7 @@ async function run() {
}
const didVersionChange = newVersionTag !== currentChecklistData.tag;
- checklistBody = await GithubUtils.generateStagingDeployCashBody(
+ const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees(
newVersionTag,
_.pluck(PRList, 'url'),
_.pluck(_.where(PRList, {isVerified: true}), 'url'),
@@ -105,6 +108,8 @@ async function run() {
didVersionChange ? false : currentChecklistData.isFirebaseChecked,
didVersionChange ? false : currentChecklistData.isGHStatusChecked,
);
+ checklistBody = issueBody;
+ checklistAssignees = issueAssignees;
}
// Finally, create or update the checklist
@@ -119,7 +124,7 @@ async function run() {
...defaultPayload,
title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`,
labels: [CONST.LABELS.STAGING_DEPLOY],
- assignees: [CONST.APPLAUSE_BOT],
+ assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees),
});
console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`);
return newChecklist;
diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
index 154dacbdc3c3..60ec0b9f0ae3 100644
--- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
+++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
@@ -49,8 +49,11 @@ async function run() {
// Next, we generate the checklist body
let checklistBody = '';
+ let checklistAssignees = [];
if (shouldCreateNewDeployChecklist) {
- checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+ const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+ checklistBody = issueBody;
+ checklistAssignees = issueAssignees;
} else {
// Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs
const PRList = _.reduce(
@@ -103,7 +106,7 @@ async function run() {
}
const didVersionChange = newVersionTag !== currentChecklistData.tag;
- checklistBody = await GithubUtils.generateStagingDeployCashBody(
+ const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBodyAndAssignees(
newVersionTag,
_.pluck(PRList, 'url'),
_.pluck(_.where(PRList, {isVerified: true}), 'url'),
@@ -114,6 +117,8 @@ async function run() {
didVersionChange ? false : currentChecklistData.isFirebaseChecked,
didVersionChange ? false : currentChecklistData.isGHStatusChecked,
);
+ checklistBody = issueBody;
+ checklistAssignees = issueAssignees;
}
// Finally, create or update the checklist
@@ -128,7 +133,7 @@ async function run() {
...defaultPayload,
title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`,
labels: [CONST.LABELS.STAGING_DEPLOY],
- assignees: [CONST.APPLAUSE_BOT],
+ assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees),
});
console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`);
return newChecklist;
@@ -406,7 +411,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -419,7 +424,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -432,85 +437,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -544,6 +553,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/getArtifactInfo/index.js b/.github/actions/javascript/getArtifactInfo/index.js
index ea56ff5f4ebd..b8cb062feba5 100644
--- a/.github/actions/javascript/getArtifactInfo/index.js
+++ b/.github/actions/javascript/getArtifactInfo/index.js
@@ -293,7 +293,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -306,7 +306,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -319,85 +319,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -431,6 +435,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js
index f272929d536a..c57ebf0fefe4 100644
--- a/.github/actions/javascript/getDeployPullRequestList/index.js
+++ b/.github/actions/javascript/getDeployPullRequestList/index.js
@@ -349,7 +349,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -362,7 +362,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -375,85 +375,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -487,6 +491,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js
index b8d7d821d64e..9eb608a8cc05 100644
--- a/.github/actions/javascript/getPullRequestDetails/index.js
+++ b/.github/actions/javascript/getPullRequestDetails/index.js
@@ -301,7 +301,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -314,7 +314,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -327,85 +327,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -439,6 +443,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/getReleaseBody/index.js b/.github/actions/javascript/getReleaseBody/index.js
index cc1321ce5cd5..ea12ef5f0df0 100644
--- a/.github/actions/javascript/getReleaseBody/index.js
+++ b/.github/actions/javascript/getReleaseBody/index.js
@@ -301,7 +301,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -314,7 +314,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -327,85 +327,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -439,6 +443,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/isStagingDeployLocked/index.js b/.github/actions/javascript/isStagingDeployLocked/index.js
index 8124c5795a5a..32b8b64d7b82 100644
--- a/.github/actions/javascript/isStagingDeployLocked/index.js
+++ b/.github/actions/javascript/isStagingDeployLocked/index.js
@@ -285,7 +285,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -298,7 +298,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -311,85 +311,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -423,6 +427,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js
index 36cd0aaefe4a..d275edf9d789 100644
--- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js
+++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js
@@ -450,7 +450,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -463,7 +463,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -476,85 +476,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -588,6 +592,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js
index 329e0d3aad5d..3bd3e6121be8 100644
--- a/.github/actions/javascript/postTestBuildComment/index.js
+++ b/.github/actions/javascript/postTestBuildComment/index.js
@@ -360,7 +360,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -373,7 +373,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -386,85 +386,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -498,6 +502,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/reopenIssueWithComment/index.js b/.github/actions/javascript/reopenIssueWithComment/index.js
index 6a5f89badb5e..9c740914dc1b 100644
--- a/.github/actions/javascript/reopenIssueWithComment/index.js
+++ b/.github/actions/javascript/reopenIssueWithComment/index.js
@@ -255,7 +255,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -268,7 +268,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -281,85 +281,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -393,6 +397,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/reviewerChecklist/index.js b/.github/actions/javascript/reviewerChecklist/index.js
index 322b529b89bf..7b162f06840d 100644
--- a/.github/actions/javascript/reviewerChecklist/index.js
+++ b/.github/actions/javascript/reviewerChecklist/index.js
@@ -255,7 +255,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -268,7 +268,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -281,85 +281,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -393,6 +397,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/actions/javascript/verifySignedCommits/index.js b/.github/actions/javascript/verifySignedCommits/index.js
index ba188d3a2b86..07173cb19bc5 100644
--- a/.github/actions/javascript/verifySignedCommits/index.js
+++ b/.github/actions/javascript/verifySignedCommits/index.js
@@ -255,7 +255,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -268,7 +268,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -281,85 +281,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -393,6 +397,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js
index 0cd407c78153..47ad2440c165 100644
--- a/.github/libs/GithubUtils.js
+++ b/.github/libs/GithubUtils.js
@@ -222,7 +222,7 @@ class GithubUtils {
}
/**
- * Generate the issue body for a StagingDeployCash.
+ * Generate the issue body and assignees for a StagingDeployCash.
*
* @param {String} tag
* @param {Array} PRList - The list of PR URLs which are included in this StagingDeployCash
@@ -235,7 +235,7 @@ class GithubUtils {
* @param {Boolean} [isGHStatusChecked]
* @returns {Promise}
*/
- static generateStagingDeployCashBody(
+ static generateStagingDeployCashBodyAndAssignees(
tag,
PRList,
verifiedPRList = [],
@@ -248,85 +248,89 @@ class GithubUtils {
) {
return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL))
.then((data) => {
- // The format of this map is following:
- // {
- // 'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
- // 'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
- // }
- const internalQAPRMap = _.reduce(
- _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
- (map, pr) => {
- // eslint-disable-next-line no-param-reassign
- map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
- return map;
- },
- {},
- );
- console.log('Found the following Internal QA PRs:', internalQAPRMap);
-
- const noQAPRs = _.pluck(
- _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
- 'html_url',
- );
- console.log('Found the following NO QA PRs:', noQAPRs);
- const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
-
- const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
- const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
-
- // Tag version and comparison URL
- // eslint-disable-next-line max-len
- let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
-
- // PR list
- if (!_.isEmpty(sortedPRList)) {
- issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
- _.each(sortedPRList, (URL) => {
- issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
- issueBody += ` ${URL}\r\n`;
- });
- issueBody += '\r\n\r\n';
- }
-
- // Internal QA PR list
- if (!_.isEmpty(internalQAPRMap)) {
- console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
- issueBody += '**Internal QA:**\r\n';
- _.each(internalQAPRMap, (assignees, URL) => {
- const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
- issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
- issueBody += `${URL}`;
- issueBody += ` -${assigneeMentions}`;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
-
- // Deploy blockers
- if (!_.isEmpty(deployBlockers)) {
- issueBody += '**Deploy Blockers:**\r\n';
- _.each(sortedDeployBlockers, (URL) => {
- issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
- issueBody += URL;
- issueBody += '\r\n';
- });
- issueBody += '\r\n\r\n';
- }
-
- issueBody += '**Deployer verifications:**';
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isTimingDashboardChecked ? 'x' : ' '
- }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${
- isFirebaseChecked ? 'x' : ' '
- }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
- // eslint-disable-next-line max-len
- issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
-
- issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
- return issueBody;
+ const internalQAPRs = _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA})));
+ return Promise.all(_.map(internalQAPRs, (pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({url: pr.html_url, mergerLogin})))).then((results) => {
+ // The format of this map is following:
+ // {
+ // 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+ // 'https://github.com/Expensify/App/pull/9642': 'mountiny'
+ // }
+ const internalQAPRMap = _.reduce(
+ results,
+ (acc, {url, mergerLogin}) => {
+ acc[url] = mergerLogin;
+ return acc;
+ },
+ {},
+ );
+ console.log('Found the following Internal QA PRs:', internalQAPRMap);
+
+ const noQAPRs = _.pluck(
+ _.filter(data, (PR) => /\[No\s?QA]/i.test(PR.title)),
+ 'html_url',
+ );
+ console.log('Found the following NO QA PRs:', noQAPRs);
+ const verifiedOrNoQAPRs = _.union(verifiedPRList, noQAPRs);
+
+ const sortedPRList = _.chain(PRList).difference(_.keys(internalQAPRMap)).unique().sortBy(GithubUtils.getPullRequestNumberFromURL).value();
+ const sortedDeployBlockers = _.sortBy(_.unique(deployBlockers), GithubUtils.getIssueOrPullRequestNumberFromURL);
+
+ // Tag version and comparison URL
+ // eslint-disable-next-line max-len
+ let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/App/compare/production...staging\r\n`;
+
+ // PR list
+ if (!_.isEmpty(sortedPRList)) {
+ issueBody += '\r\n**This release contains changes from the following pull requests:**\r\n';
+ _.each(sortedPRList, (URL) => {
+ issueBody += _.contains(verifiedOrNoQAPRs, URL) ? '- [x]' : '- [ ]';
+ issueBody += ` ${URL}\r\n`;
+ });
+ issueBody += '\r\n\r\n';
+ }
+
+ // Internal QA PR list
+ if (!_.isEmpty(internalQAPRMap)) {
+ console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
+ issueBody += '**Internal QA:**\r\n';
+ _.each(internalQAPRMap, (merger, URL) => {
+ const mergerMention = `@${merger}`;
+ issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
+ issueBody += `${URL}`;
+ issueBody += ` - ${mergerMention}`;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
+
+ // Deploy blockers
+ if (!_.isEmpty(deployBlockers)) {
+ issueBody += '**Deploy Blockers:**\r\n';
+ _.each(sortedDeployBlockers, (URL) => {
+ issueBody += _.contains(resolvedDeployBlockers, URL) ? '- [x] ' : '- [ ] ';
+ issueBody += URL;
+ issueBody += '\r\n';
+ });
+ issueBody += '\r\n\r\n';
+ }
+
+ issueBody += '**Deployer verifications:**';
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isTimingDashboardChecked ? 'x' : ' '
+ }] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${
+ isFirebaseChecked ? 'x' : ' '
+ }] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
+ // eslint-disable-next-line max-len
+ issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
+
+ issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
+ const issueAssignees = _.uniq(_.values(internalQAPRMap));
+ const issue = {issueBody, issueAssignees};
+ return issue;
+ });
})
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
}
@@ -360,6 +364,20 @@ class GithubUtils {
.catch((err) => console.error('Failed to get PR list', err));
}
+ /**
+ * @param {Number} pullRequestNumber
+ * @returns {Promise}
+ */
+ static getPullRequestMergerLogin(pullRequestNumber) {
+ return this.octokit.pulls
+ .get({
+ owner: CONST.GITHUB_OWNER,
+ repo: CONST.APP_REPO,
+ pull_number: pullRequestNumber,
+ })
+ .then(({data: pullRequest}) => pullRequest.merged_by.login);
+ }
+
/**
* @param {Number} pullRequestNumber
* @returns {Promise}
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 9c5db608a846..62e30858e73c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001045503
- versionName "1.4.55-3"
+ versionCode 1001045602
+ versionName "1.4.56-2"
}
flavorDimensions "default"
diff --git a/docs/articles/expensify-classic/expenses/Add-expenses-to-a-report.md b/docs/articles/expensify-classic/expenses/Add-expenses-to-a-report.md
new file mode 100644
index 000000000000..96427a60d87f
--- /dev/null
+++ b/docs/articles/expensify-classic/expenses/Add-expenses-to-a-report.md
@@ -0,0 +1,18 @@
+---
+title: Add expenses to a report
+description: Add expenses to a report to submit them for approval
+---
+
+
+To submit expenses for approval, they must be added to a report.
+
+1. Click the **Expenses** tab.
+2. Find the expenses you want to add to the report by searching through the table of expenses and/or using the sort filters.
+3. Select the expenses by checking the box to the left of each expense or selecting them all.
+4. Click **Add to Report** in the right corner and select either:
+ - **Auto-Report**: Automatically adds the expenses to an open report, or creates a new report if there are no open reports
+ - **New Report**: Creates a new report for the expenses
+ - **None**: Ensures none of the selected expenses are attached to a report (as long as the report has not already been submitted)
+ - **Existing Report**: Adds the expenses to the selected report
+
+
diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md b/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md
index ecdea4699ee0..47e7a5b5382a 100644
--- a/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md
+++ b/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md
@@ -34,7 +34,7 @@ In order to complete the steps below, you'll need a Workday System Administrator
4. Search and select "security group membership and access".
5. Search for the security group you just created.
6. Click the ellipsis, then **Security Group > Maintain Domain Permissions for Security Group**.
-7. Under **Integration Permissions**, add "External Account Provisioning" to **Domain Security Workspaces permitting Put access** and "Worker Data: Workers" to **Domain Security Workspaces permitting Get access**.
+7. Head to Integration Permissions and add **Get access** for “External Account Provisioning” and “Worker Data: Workers” under Domain Security Workspaces.
8. Click **OK** and **Done**.
9. Search **Activate Pending Security Workspace Changes** and complete the task for activating the security workspace change, adding a comment if required and checking the **Confirmed** check-box.
diff --git a/docs/articles/expensify-classic/workspaces/Set-time-and-distance-rates.md b/docs/articles/expensify-classic/workspaces/Set-time-and-distance-rates.md
new file mode 100644
index 000000000000..e81e05446379
--- /dev/null
+++ b/docs/articles/expensify-classic/workspaces/Set-time-and-distance-rates.md
@@ -0,0 +1,24 @@
+---
+title: Set time and distance rates
+description: Set rates for hourly and mileage expenses
+---
+
+
+You can set rates for your workspace’s hourly billable and mileage expenses.
+
+1. Hover over Settings, then click **Workspaces**.
+2. Click the **Group** tab on the left.
+3. Click the desired workspace name.
+4. Click the **Expenses** tab on the left.
+5. Scroll down to the time or distance section and set your rates.
+ - For distance:
+ - If desired, adjust your unit (miles or kilometers) and your default category. These options will apply to all of your distance rates.
+ - To add a new rate, click **Add a Mileage Rate**.
+ - To edit an existing rate,
+ - Click the toggle to enable or disable the rate.
+ - Click the name or rate field to edit them.
+ - For time,
+ - Click the Enable toggle to enable hourly rate expenses.
+ - Enter the default hourly rate.
+
+
diff --git a/docs/redirects.csv b/docs/redirects.csv
index df4e2a45dce3..7539a2777d92 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -66,3 +66,4 @@ https://help.expensify.com/articles/expensify-classic/expensify-billing/Individu
https://help.expensify.com/articles/expensify-classic/settings/Merge-Accounts,https://help.expensify.com/articles/expensify-classic/settings/account-settings/Merge-accounts
https://help.expensify.com/articles/expensify-classic/settings/Preferences,https://help.expensify.com/expensify-classic/hubs/settings/account-settings
https://help.expensify.com/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager,https://use.expensify.com/support
+https://help.expensify.com/articles/expensify-classic/settings/Copilot,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 5e2ba1fcd614..a962c69f0bc6 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageTypeAPPLCFBundleShortVersionString
- 1.4.55
+ 1.4.56CFBundleSignature????CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.55.3
+ 1.4.56.2ITSAppUsesNonExemptEncryptionLSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 69472200e46d..9f20eb574abc 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageTypeBNDLCFBundleShortVersionString
- 1.4.55
+ 1.4.56CFBundleSignature????CFBundleVersion
- 1.4.55.3
+ 1.4.56.2
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 008ca16909b0..2319ff879a03 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName$(PRODUCT_NAME)CFBundleShortVersionString
- 1.4.55
+ 1.4.56CFBundleVersion
- 1.4.55.3
+ 1.4.56.2NSExtensionNSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 4bff5eaf6eb8..fcebc3cd46dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.55-3",
+ "version": "1.4.56-2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.55-3",
+ "version": "1.4.56-2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 53eb229d7b85..ab1a3ecc7d64 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.55-3",
+ "version": "1.4.56-2",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/CONST.ts b/src/CONST.ts
index af4864c22a85..d13c3b374f5a 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1679,7 +1679,7 @@ const CONST = {
POLICY_ID_FROM_PATH: /\/w\/([a-zA-Z0-9]+)(\/|$)/,
- SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#]+(?:\\.[\\w\\-\\'\\+]+)*`, 'gim'),
+ SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#@]+(?:\\.[\\w\\-\\'\\+]+)*`, 'gim'),
},
PRONOUNS: {
@@ -4133,6 +4133,22 @@ const CONST = {
SESSION_STORAGE_KEYS: {
INITIAL_URL: 'INITIAL_URL',
},
+ DEFAULT_TAX: {
+ defaultExternalID: 'id_TAX_EXEMPT',
+ defaultValue: '0%',
+ foreignTaxDefault: 'id_TAX_EXEMPT',
+ name: 'Tax',
+ taxes: {
+ id_TAX_EXEMPT: {
+ name: 'Tax exempt',
+ value: '0%',
+ },
+ id_TAX_RATE_1: {
+ name: 'Tax Rate 1',
+ value: '5%',
+ },
+ },
+ },
} as const;
type Country = keyof typeof CONST.ALL_COUNTRIES;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index a49df16b570a..c216d5ac288c 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -304,13 +304,10 @@ const ROUTES = {
route: ':iouType/new/receipt/:reportID?',
getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` as const,
},
- MONEY_REQUEST_DISTANCE: {
- route: ':iouType/new/address/:reportID?',
- getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` as const,
- },
MONEY_REQUEST_CREATE: {
- route: 'create/:iouType/start/:transactionID/:reportID',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const,
+ route: ':action/:iouType/start/:transactionID/:reportID',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) =>
+ `create/${iouType}/start/${transactionID}/${reportID}` as const,
},
MONEY_REQUEST_STEP_CONFIRMATION: {
route: 'create/:iouType/confirmation/:transactionID/:reportID',
@@ -352,9 +349,9 @@ const ROUTES = {
getUrlWithBackToParam(`${action}/${iouType}/description/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo),
},
MONEY_REQUEST_STEP_DISTANCE: {
- route: 'create/:iouType/distance/:transactionID/:reportID',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
- getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}`, backTo),
+ route: ':action/:iouType/distance/:transactionID/:reportID',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
+ getUrlWithBackToParam(`${action}/${iouType}/distance/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_MERCHANT: {
route: ':action/:iouType/merchant/:transactionID/:reportID',
@@ -395,16 +392,19 @@ const ROUTES = {
getRoute: (iouType: ValueOf, iouRequestType: ValueOf) => `start/${iouType}/${iouRequestType}` as const,
},
MONEY_REQUEST_CREATE_TAB_DISTANCE: {
- route: 'create/:iouType/start/:transactionID/:reportID/distance',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/distance` as const,
+ route: ':action/:iouType/start/:transactionID/:reportID/distance',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) =>
+ `create/${iouType}/start/${transactionID}/${reportID}/distance` as const,
},
MONEY_REQUEST_CREATE_TAB_MANUAL: {
- route: 'create/:iouType/start/:transactionID/:reportID/manual',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/manual` as const,
+ route: ':action/:iouType/start/:transactionID/:reportID/manual',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) =>
+ `create/${iouType}/start/${transactionID}/${reportID}/manual` as const,
},
MONEY_REQUEST_CREATE_TAB_SCAN: {
- route: 'create/:iouType/start/:transactionID/:reportID/scan',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/scan` as const,
+ route: ':action/:iouType/start/:transactionID/:reportID/scan',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) =>
+ `create/${iouType}/start/${transactionID}/${reportID}/scan` as const,
},
IOU_REQUEST: 'request/new',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 9c33c7e63d7d..82fef0383918 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -155,7 +155,6 @@ const SCREENS = {
CURRENCY: 'Money_Request_Currency',
WAYPOINT: 'Money_Request_Waypoint',
EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint',
- DISTANCE: 'Money_Request_Distance',
RECEIPT: 'Money_Request_Receipt',
},
diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx
index 9901ff9243f9..d0aa2e206eb2 100644
--- a/src/components/AddressSearch/index.tsx
+++ b/src/components/AddressSearch/index.tsx
@@ -455,3 +455,5 @@ function AddressSearch(
AddressSearch.displayName = 'AddressSearchWithRef';
export default forwardRef(AddressSearch);
+
+export type {AddressSearchProps};
diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts
index 27e068cd1777..efbcc6374341 100644
--- a/src/components/AddressSearch/types.ts
+++ b/src/components/AddressSearch/types.ts
@@ -96,4 +96,4 @@ type AddressSearchProps = {
type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent, containerRef: RefObject) => boolean;
-export type {CurrentLocationButtonProps, AddressSearchProps, RenamedInputKeysProps, IsCurrentTargetInsideContainerType};
+export type {CurrentLocationButtonProps, AddressSearchProps, RenamedInputKeysProps, IsCurrentTargetInsideContainerType, StreetValue};
diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index 0c047ce52dc8..442b3cd864d6 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -549,7 +549,7 @@ function AttachmentModal({
// @ts-expect-error TODO: Remove this once Attachments (https://github.com/Expensify/App/issues/24969) is migrated to TypeScript.
containerStyles={[styles.mh5]}
source={sourceForAttachmentView}
- isAuthTokenRequired={isAuthTokenRequired}
+ isAuthTokenRequired={isAuthTokenRequiredState}
file={file}
onToggleKeyboard={updateConfirmButtonVisibility}
isWorkspaceAvatar={isWorkspaceAvatar}
diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx
index 56fe7c4d0b42..a46b37c986ba 100644
--- a/src/components/Banner.tsx
+++ b/src/components/Banner.tsx
@@ -109,3 +109,5 @@ function Banner({text, onClose, onPress, containerStyles, textStyles, shouldRend
Banner.displayName = 'Banner';
export default memo(Banner);
+
+export type {BannerProps};
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index 3abc5b17ae80..3b05d22fe6c4 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -352,3 +352,5 @@ function Button(
Button.displayName = 'Button';
export default withNavigationFallback(React.forwardRef(Button));
+
+export type {ButtonProps};
diff --git a/src/components/DistanceRequest/index.tsx b/src/components/DistanceRequest/index.tsx
deleted file mode 100644
index f9e8c0be12ff..000000000000
--- a/src/components/DistanceRequest/index.tsx
+++ /dev/null
@@ -1,320 +0,0 @@
-import type {RouteProp} from '@react-navigation/native';
-import lodashIsEqual from 'lodash/isEqual';
-import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
-// eslint-disable-next-line no-restricted-imports
-import type {ScrollView} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import Button from '@components/Button';
-import DotIndicatorMessage from '@components/DotIndicatorMessage';
-import DraggableList from '@components/DraggableList';
-import type {DraggableListData} from '@components/DraggableList/types';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import useLocalize from '@hooks/useLocalize';
-import useNetwork from '@hooks/useNetwork';
-import usePrevious from '@hooks/usePrevious';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as ErrorUtils from '@libs/ErrorUtils';
-import * as IOUUtils from '@libs/IOUUtils';
-import Navigation from '@libs/Navigation/Navigation';
-import * as TransactionUtils from '@libs/TransactionUtils';
-import * as MapboxToken from '@userActions/MapboxToken';
-import * as TransactionUserActions from '@userActions/Transaction';
-import * as TransactionEdit from '@userActions/TransactionEdit';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-import type {Report, Transaction} from '@src/types/onyx';
-import type {WaypointCollection} from '@src/types/onyx/Transaction';
-import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import DistanceRequestFooter from './DistanceRequestFooter';
-import DistanceRequestRenderItem from './DistanceRequestRenderItem';
-
-type DistanceRequestOnyxProps = {
- transaction: OnyxEntry;
-};
-
-type DistanceRequestProps = DistanceRequestOnyxProps & {
- /** The TransactionID of this request */
- transactionID?: string;
-
- /** The report to which the distance request is associated */
- report: OnyxEntry;
-
- /** Are we editing an existing distance request, or creating a new one? */
- isEditingRequest?: boolean;
-
- /** Are we editing the distance while creating a new distance request */
- isEditingNewRequest?: boolean;
-
- /** Called on submit of this page */
- onSubmit: (waypoints?: WaypointCollection) => void;
-
- /** React Navigation route */
- route: RouteProp<{
- /** Params from the route */
- params: {
- /** The type of IOU report, i.e. bill, request, send */
- iouType: string;
- /** The report ID of the IOU */
- reportID: string;
- };
- }>;
-};
-
-function DistanceRequest({transactionID = '', report, transaction, route, isEditingRequest = false, isEditingNewRequest = false, onSubmit}: DistanceRequestProps) {
- const styles = useThemeStyles();
- const {isOffline} = useNetwork();
- const {translate} = useLocalize();
-
- const [optimisticWaypoints, setOptimisticWaypoints] = useState();
- const [hasError, setHasError] = useState(false);
- const reportID = report?.reportID ?? '';
- const waypoints: WaypointCollection = useMemo(() => optimisticWaypoints ?? transaction?.comment?.waypoints ?? {waypoint0: {}, waypoint1: {}}, [optimisticWaypoints, transaction]);
- const waypointsList = Object.keys(waypoints);
- const iouType = route?.params?.iouType ?? '';
- const previousWaypoints = usePrevious(waypoints);
- const numberOfWaypoints = Object.keys(waypoints).length;
- const numberOfPreviousWaypoints = Object.keys(previousWaypoints).length;
- const scrollViewRef = useRef(null);
-
- const isLoadingRoute = transaction?.comment?.isLoading ?? false;
- const isLoading = transaction?.isLoading ?? false;
- const hasRouteError = Boolean(transaction?.errorFields?.route);
- const hasRoute = TransactionUtils.hasRoute((transaction ?? {}) as Transaction);
- const validatedWaypoints = TransactionUtils.getValidWaypoints(waypoints);
- const previousValidatedWaypoints = usePrevious(validatedWaypoints);
- const haveValidatedWaypointsChanged = !lodashIsEqual(previousValidatedWaypoints, validatedWaypoints);
- const isRouteAbsentWithoutErrors = !hasRoute && !hasRouteError;
- const shouldFetchRoute = (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && Object.keys(validatedWaypoints).length > 1;
- const transactionWasSaved = useRef(false);
-
- useEffect(() => {
- MapboxToken.init();
- return MapboxToken.stop;
- }, []);
-
- useEffect(() => {
- if (!isEditingNewRequest && !isEditingRequest) {
- return () => {};
- }
- // This effect runs when the component is mounted and unmounted. It's purpose is to be able to properly
- // discard changes if the user cancels out of making any changes. This is accomplished by backing up the
- // original transaction, letting the user modify the current transaction, and then if the user ever
- // cancels out of the modal without saving changes, the original transaction is restored from the backup.
-
- // On mount, create the backup transaction.
- TransactionEdit.createBackupTransaction(transaction);
-
- return () => {
- // If the user cancels out of the modal without without saving changes, then the original transaction
- // needs to be restored from the backup so that all changes are removed.
- if (transactionWasSaved.current) {
- return;
- }
- TransactionEdit.restoreOriginalTransactionFromBackup(transaction?.transactionID ?? '');
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- const transactionWaypoints = transaction?.comment?.waypoints ?? {};
- if (!transaction?.transactionID || Object.keys(transactionWaypoints).length) {
- return;
- }
-
- // Create the initial start and stop waypoints
- TransactionUserActions.createInitialWaypoints(transactionID);
- return () => {
- // Whenever we reset the transaction, we need to set errors as empty/false.
- setHasError(false);
- };
- }, [transaction, transactionID]);
-
- useEffect(() => {
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- if (isOffline || !shouldFetchRoute) {
- return;
- }
-
- TransactionUserActions.getRoute(transactionID, validatedWaypoints);
- }, [shouldFetchRoute, transactionID, validatedWaypoints, isOffline]);
-
- useEffect(() => {
- if (numberOfWaypoints <= numberOfPreviousWaypoints) {
- return;
- }
- scrollViewRef.current?.scrollToEnd({animated: true});
- }, [numberOfPreviousWaypoints, numberOfWaypoints]);
-
- useEffect(() => {
- // Whenever we change waypoints we need to remove the error or it will keep showing the error.
- if (lodashIsEqual(previousWaypoints, waypoints)) {
- return;
- }
- setHasError(false);
- }, [waypoints, previousWaypoints]);
-
- const navigateBack = () => {
- Navigation.goBack(isEditingNewRequest ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME);
- };
-
- /**
- * Takes the user to the page for editing a specific waypoint
- */
- const navigateToWaypointEditPage = (index: number) => {
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(
- CONST.IOU.ACTION.EDIT,
- CONST.IOU.TYPE.REQUEST,
- transactionID,
- report?.reportID ?? '',
- index.toString(),
- Navigation.getActiveRouteWithoutParams(),
- ),
- );
- };
-
- const getError = useCallback(() => {
- // Get route error if available else show the invalid number of waypoints error.
- if (hasRouteError) {
- return ErrorUtils.getLatestErrorField((transaction ?? {}) as Transaction, 'route');
- }
-
- if (Object.keys(validatedWaypoints).length < 2) {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- return {0: 'iou.error.atLeastTwoDifferentWaypoints'};
- }
-
- if (Object.keys(validatedWaypoints).length < Object.keys(waypoints).length) {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- return {0: translate('iou.error.duplicateWaypointsErrorMessage')};
- }
- }, [translate, transaction, hasRouteError, validatedWaypoints, waypoints]);
-
- const updateWaypoints = useCallback(
- ({data}: DraggableListData) => {
- if (lodashIsEqual(waypointsList, data)) {
- return;
- }
-
- const newWaypoints: WaypointCollection = {};
- let emptyWaypointIndex = -1;
- data.forEach((waypoint, index) => {
- newWaypoints[`waypoint${index}`] = waypoints?.[waypoint] ?? {};
- // Find waypoint that BECOMES empty after dragging
- if (isEmptyObject(newWaypoints[`waypoint${index}`]) && !isEmptyObject(waypoints[`waypoint${index}`])) {
- emptyWaypointIndex = index;
- }
- });
-
- setOptimisticWaypoints(newWaypoints);
- // eslint-disable-next-line rulesdir/no-thenable-actions-in-views
- Promise.all([TransactionUserActions.removeWaypoint(transaction, emptyWaypointIndex.toString()), TransactionUserActions.updateWaypoints(transactionID, newWaypoints)]).then(() => {
- setOptimisticWaypoints(undefined);
- });
- },
- [transactionID, transaction, waypoints, waypointsList],
- );
-
- const submitWaypoints = useCallback(() => {
- // If there is any error or loading state, don't let user go to next page.
- if (!isEmptyObject(getError()) || isLoadingRoute || (isLoading && !isOffline)) {
- setHasError(true);
- return;
- }
-
- if (isEditingNewRequest || isEditingRequest) {
- transactionWasSaved.current = true;
- }
-
- onSubmit(waypoints);
- }, [onSubmit, setHasError, getError, isLoadingRoute, isLoading, waypoints, isEditingNewRequest, isEditingRequest, isOffline]);
-
- const content = (
- <>
-
- item}
- shouldUsePortal
- onDragEnd={updateWaypoints}
- ref={scrollViewRef}
- renderItem={({item, drag, isActive, getIndex}) => (
- number}
- onPress={navigateToWaypointEditPage}
- disabled={isLoadingRoute}
- />
- )}
- ListFooterComponent={
-
- }
- />
-
-
- {/* Show error message if there is route error or there are less than 2 routes and user has tried submitting, */}
- {((hasError && !isEmptyObject(getError())) || hasRouteError) && (
-
- )}
-
-
- >
- );
-
- if (!isEditingNewRequest) {
- return content;
- }
-
- return (
-
- {({safeAreaPaddingBottomStyle}) => (
-
-
-
- {content}
-
-
- )}
-
- );
-}
-
-DistanceRequest.displayName = 'DistanceRequest';
-export default withOnyx({
- transaction: {
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID || 0}`,
- },
-})(DistanceRequest);
diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx
index 21f3e9a3b605..f3293596aa46 100755
--- a/src/components/HeaderWithBackButton/index.tsx
+++ b/src/components/HeaderWithBackButton/index.tsx
@@ -15,7 +15,6 @@ import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useThrottledButtonState from '@hooks/useThrottledButtonState';
-import useWaitForNavigation from '@hooks/useWaitForNavigation';
import getButtonState from '@libs/getButtonState';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
@@ -58,7 +57,6 @@ function HeaderWithBackButton({
children = null,
shouldOverlayDots = false,
shouldOverlay = false,
- singleExecution = (func) => func,
shouldNavigateToTopMostReport = false,
style,
}: HeaderWithBackButtonProps) {
@@ -68,7 +66,6 @@ function HeaderWithBackButton({
const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState();
const {translate} = useLocalize();
const {isKeyboardShown} = useKeyboardState();
- const waitForNavigate = useWaitForNavigation();
// If the icon is present, the header bar should be taller and use different font.
const isCentralPaneSettings = !!icon;
@@ -175,7 +172,7 @@ function HeaderWithBackButton({
Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID, Navigation.getActiveRoute()))))}
+ onPress={() => Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID, Navigation.getActiveRoute()))}
style={[styles.touchableButtonImage]}
role="button"
accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')}
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index a4da7e551515..4550a7aef5d2 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -721,7 +721,17 @@ function MoneyRequestConfirmationList({
description={translate('common.distance')}
style={styles.moneyRequestMenuItem}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(iouType, reportID))}
+ onPress={() =>
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ iouType,
+ transaction?.transactionID ?? '',
+ reportID,
+ Navigation.getActiveRouteWithoutParams(),
+ ),
+ )
+ }
disabled={didConfirm || !canEditDistance}
interactive={!isReadOnly}
/>
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 138bfc937926..f27cd507d668 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -307,7 +307,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
const navigateBack = () => {
- Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(iouType, transaction.transactionID, reportID));
+ Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID));
};
const shouldDisplayFieldError = useMemo(() => {
@@ -729,7 +729,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
description={translate('common.distance')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
+ onPress={() =>
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()),
+ )
+ }
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
disabled={didConfirm || !canEditDistance}
interactive={!isReadOnly}
diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx
index 03f1e0f496a8..1c337e024116 100644
--- a/src/components/Picker/BasePicker.tsx
+++ b/src/components/Picker/BasePicker.tsx
@@ -72,11 +72,11 @@ function BasePicker(
*/
const onValueChange = (inputValue: TPickerValue, index: number) => {
if (inputID) {
- onInputChange(inputValue);
+ onInputChange?.(inputValue);
return;
}
- onInputChange(inputValue, index);
+ onInputChange?.(inputValue, index);
};
const enableHighlight = () => {
diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts
index 3f7c0282d35a..d935ebe8fdc5 100644
--- a/src/components/Picker/types.ts
+++ b/src/components/Picker/types.ts
@@ -80,7 +80,7 @@ type BasePickerProps = {
shouldShowOnlyTextWhenDisabled?: boolean;
/** A callback method that is called when the value changes and it receives the selected value as an argument */
- onInputChange: (value: TPickerValue, index?: number) => void;
+ onInputChange?: (value: TPickerValue, index?: number) => void;
/** Size of a picker component */
size?: PickerSize;
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 70c65d1d66ce..bb0308ee4509 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -327,7 +327,11 @@ function MoneyRequestView({
interactive={canEditDistance}
shouldShowRightIcon={canEditDistance}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))}
+ onPress={() =>
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID),
+ )
+ }
/>
) : (
diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx
index ffc12957dcb4..c66dc36d1ed5 100644
--- a/src/components/ReportActionItem/ReportActionItemImages.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImages.tsx
@@ -63,45 +63,45 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report
const triangleWidth = variables.reportActionItemImagesMoreCornerTriangleWidth;
return (
-
- {shownImages.map(({thumbnail, image, transaction, isLocalFile, filename}, index) => {
- const isLastImage = index === numberOfShownImages - 1;
-
- // Show a border to separate multiple images. Shown to the right for each except the last.
- const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1;
- const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {};
- return (
-
+
+ {shownImages.map(({thumbnail, image, transaction, isLocalFile, filename}, index) => {
+ // Show a border to separate multiple images. Shown to the right for each except the last.
+ const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1;
+ const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {};
+ return (
+
+
+
+ );
+ })}
+
+ {remaining > 0 && (
+
+
+
- );
- })}
+
+ {remaining > MAX_REMAINING ? `${MAX_REMAINING}+` : `+${remaining}`}
+
+ )}
);
}
diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx
index 827eec8088a6..b78e274371ca 100644
--- a/src/components/ScreenWrapper.tsx
+++ b/src/components/ScreenWrapper.tsx
@@ -23,6 +23,7 @@ import KeyboardAvoidingView from './KeyboardAvoidingView';
import OfflineIndicator from './OfflineIndicator';
import SafeAreaConsumer from './SafeAreaConsumer';
import TestToolsModal from './TestToolsModal';
+import withNavigationFallback from './withNavigationFallback';
type ChildrenProps = {
insets: EdgeInsets;
@@ -279,4 +280,4 @@ function ScreenWrapper(
ScreenWrapper.displayName = 'ScreenWrapper';
-export default forwardRef(ScreenWrapper);
+export default withNavigationFallback(forwardRef(ScreenWrapper));
diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx
index 0ea8ea308d6a..690a9485f099 100644
--- a/src/components/SettlementButton.tsx
+++ b/src/components/SettlementButton.tsx
@@ -143,9 +143,8 @@ function SettlementButton({
const session = useSession();
const chatReport = ReportUtils.getReport(chatReportID);
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport as OnyxEntry);
- const shouldShowPaywithExpensifyOption =
- !isPaidGroupPolicy ||
- (!shouldHidePaymentOptions && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES && policy?.reimburserEmail === session?.email);
+ const shouldShowPaywithExpensifyOption = !isPaidGroupPolicy || (!shouldHidePaymentOptions && ReportUtils.isPayer(session, iouReport as OnyxEntry));
+ const shouldShowPayElsewhereOption = !isPaidGroupPolicy || policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL;
const paymentButtonOptions = useMemo(() => {
const buttonOptions = [];
const isExpenseReport = ReportUtils.isExpenseReport(iouReport);
@@ -189,7 +188,9 @@ function SettlementButton({
if (isExpenseReport && shouldShowPaywithExpensifyOption) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
}
- buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
+ if (shouldShowPayElsewhereOption) {
+ buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
+ }
if (shouldShowApproveButton) {
buttonOptions.push(approveButtonOption);
diff --git a/src/components/VideoPlayer/IconButton.js b/src/components/VideoPlayer/IconButton.js
index 71c1a2150692..5af9bc87e66f 100644
--- a/src/components/VideoPlayer/IconButton.js
+++ b/src/components/VideoPlayer/IconButton.js
@@ -1,8 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
-import Hoverable from '@components/Hoverable';
import Icon from '@components/Icon';
-import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
+import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import Tooltip from '@components/Tooltip';
import useThemeStyles from '@hooks/useThemeStyles';
import stylePropTypes from '@styles/stylePropTypes';
@@ -44,21 +43,18 @@ function IconButton({src, fill, onPress, style, hoverStyle, tooltipText, small,
text={tooltipText}
shouldForceRenderingBelow={shouldForceRenderingTooltipBelow}
>
-
- {(isHovered) => (
-
-
-
- )}
-
+
+
+
);
}
diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx
index a1e9568cc2ad..28415fa52837 100644
--- a/src/components/VideoPlayerPreview/index.tsx
+++ b/src/components/VideoPlayerPreview/index.tsx
@@ -7,6 +7,8 @@ import VideoPlayer from '@components/VideoPlayer';
import IconButton from '@components/VideoPlayer/IconButton';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useLocalize from '@hooks/useLocalize';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useThumbnailDimensions from '@hooks/useThumbnailDimensions';
import useWindowDimensions from '@hooks/useWindowDimensions';
@@ -39,6 +41,8 @@ type VideoPlayerPreviewProps = {
function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions, videoDuration, onShowModalPress}: VideoPlayerPreviewProps) {
const styles = useThemeStyles();
+ const theme = useTheme();
+ const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const {currentlyPlayingURL, updateCurrentlyPlayingURL} = usePlaybackContext();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -84,14 +88,16 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions,
shouldUseSmallVideoControls
style={[styles.w100, styles.h100]}
/>
-
-
+
+
+
>
)}
diff --git a/src/libs/API/parameters/EnablePolicyTaxesParams.ts b/src/libs/API/parameters/EnablePolicyTaxesParams.ts
index 4a235d5d6a1f..5517bd442231 100644
--- a/src/libs/API/parameters/EnablePolicyTaxesParams.ts
+++ b/src/libs/API/parameters/EnablePolicyTaxesParams.ts
@@ -1,6 +1,7 @@
type EnablePolicyTaxesParams = {
policyID: string;
enabled: boolean;
+ taxFields?: string;
};
export default EnablePolicyTaxesParams;
diff --git a/src/libs/API/parameters/GetRouteForDraftParams.ts b/src/libs/API/parameters/GetRouteForDraftParams.ts
deleted file mode 100644
index 5a213c3f2d49..000000000000
--- a/src/libs/API/parameters/GetRouteForDraftParams.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-type GetRouteForDraftParams = {
- transactionID: string;
- waypoints: string;
-};
-
-export default GetRouteForDraftParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 40f9c65f4ffd..1895c2426e1a 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -20,7 +20,6 @@ export type {default as GetMissingOnyxMessagesParams} from './GetMissingOnyxMess
export type {default as GetNewerActionsParams} from './GetNewerActionsParams';
export type {default as GetOlderActionsParams} from './GetOlderActionsParams';
export type {default as GetReportPrivateNoteParams} from './GetReportPrivateNoteParams';
-export type {default as GetRouteForDraftParams} from './GetRouteForDraftParams';
export type {default as GetRouteParams} from './GetRouteParams';
export type {default as GetStatementPDFParams} from './GetStatementPDFParams';
export type {default as HandleRestrictedEventParams} from './HandleRestrictedEventParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 709da3f353b1..9d6e6b3929b8 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -443,7 +443,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.SEARCH_FOR_REPORTS]: Parameters.SearchForReportsParams;
[READ_COMMANDS.SEND_PERFORMANCE_TIMING]: Parameters.SendPerformanceTimingParams;
[READ_COMMANDS.GET_ROUTE]: Parameters.GetRouteParams;
- [READ_COMMANDS.GET_ROUTE_FOR_DRAFT]: Parameters.GetRouteForDraftParams;
+ [READ_COMMANDS.GET_ROUTE_FOR_DRAFT]: Parameters.GetRouteParams;
[READ_COMMANDS.GET_STATEMENT_PDF]: Parameters.GetStatementPDFParams;
[READ_COMMANDS.OPEN_ONFIDO_FLOW]: EmptyObject;
[READ_COMMANDS.OPEN_INITIAL_SETTINGS_PAGE]: EmptyObject;
diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts
index 985499078c9a..9a7e0a568627 100644
--- a/src/libs/DistanceRequestUtils.ts
+++ b/src/libs/DistanceRequestUtils.ts
@@ -124,7 +124,8 @@ function getDistanceMerchant(
const formattedDistance = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate);
const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer');
- const ratePerUnit = PolicyUtils.getUnitRateValue({rate}, toLocaleDigit);
+ const ratePerUnit = PolicyUtils.getUnitRateValue(toLocaleDigit, {rate});
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const currencySymbol = CurrencyUtils.getCurrencySymbol(currency) || `${currency} `;
diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts
index 07bb22f43b31..65390982f18c 100644
--- a/src/libs/IOUUtils.ts
+++ b/src/libs/IOUUtils.ts
@@ -11,13 +11,13 @@ function navigateToStartMoneyRequestStep(requestType: ValueOf require('../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType,
[SCREENS.IOU_SEND.ENABLE_PAYMENTS]: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.WAYPOINT]: () => require('../../../pages/iou/MoneyRequestWaypointPage').default as React.ComponentType,
- [SCREENS.MONEY_REQUEST.DISTANCE]: () => require('../../../pages/iou/NewDistanceRequestPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.RECEIPT]: () => require('../../../pages/EditRequestReceiptPage').default as React.ComponentType,
});
diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts
index c55145a5d580..d57d0272738b 100644
--- a/src/libs/Navigation/Navigation.ts
+++ b/src/libs/Navigation/Navigation.ts
@@ -94,7 +94,7 @@ function getActiveRouteIndex(stateOrRoute: StateOrRoute, index?: number): number
function parseHybridAppUrl(url: HybridAppRoute | Route): Route {
switch (url) {
case HYBRID_APP_ROUTES.MONEY_REQUEST_CREATE:
- return ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID());
+ return ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, ReportUtils.generateReportID());
default:
return url;
}
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index d86c7190fc7a..b8b9280bc576 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -516,7 +516,6 @@ const config: LinkingOptions['config'] = {
[SCREENS.MONEY_REQUEST.CONFIRMATION]: ROUTES.MONEY_REQUEST_CONFIRMATION.route,
[SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route,
[SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route,
- [SCREENS.MONEY_REQUEST.DISTANCE]: ROUTES.MONEY_REQUEST_DISTANCE.route,
[SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS,
[SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT,
[SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 0980fbb2afef..3f85aec3a560 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -402,9 +402,12 @@ type MoneyRequestNavigatorParamList = {
waypointIndex: string;
threadReportID: number;
};
- [SCREENS.MONEY_REQUEST.DISTANCE]: {
+ [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: {
+ action: string;
iouType: ValueOf;
+ transactionID: string;
reportID: string;
+ backTo: string;
};
[SCREENS.MONEY_REQUEST.RECEIPT]: {
iouType: string;
diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts
index cdcfb13eeb72..edc5e51ceeb9 100644
--- a/src/libs/PolicyDistanceRatesUtils.ts
+++ b/src/libs/PolicyDistanceRatesUtils.ts
@@ -1,5 +1,7 @@
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
+import CONST from '@src/CONST';
import type ONYXKEYS from '@src/ONYXKEYS';
+import type {Rate} from '@src/types/onyx/Policy';
import * as CurrencyUtils from './CurrencyUtils';
import getPermittedDecimalSeparator from './getPermittedDecimalSeparator';
import * as MoneyRequestUtils from './MoneyRequestUtils';
@@ -22,4 +24,13 @@ function validateRateValue(values: FormOnyxValues, currency: stri
return errors;
}
-export default validateRateValue;
+/**
+ * Get the optimistic rate name in a way that matches BE logic
+ * @param rates
+ */
+function getOptimisticRateName(rates: Record): string {
+ const existingRatesWithSameName = Object.values(rates ?? {}).filter((rate) => (rate.name ?? '').startsWith(CONST.CUSTOM_UNITS.DEFAULT_RATE));
+ return existingRatesWithSameName.length ? `${CONST.CUSTOM_UNITS.DEFAULT_RATE} ${existingRatesWithSameName.length}` : CONST.CUSTOM_UNITS.DEFAULT_RATE;
+}
+
+export {validateRateValue, getOptimisticRateName};
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 39e6c8932aad..43bda05c9e21 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -5,13 +5,12 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx';
-import type {PolicyFeatureName} from '@src/types/onyx/Policy';
+import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Navigation from './Navigation/Navigation';
type MemberEmailsToAccountIDs = Record;
-type UnitRate = {rate: number};
/**
* Filter out the active policies, which will exclude policies with pending deletion
@@ -66,7 +65,7 @@ function hasCustomUnitsError(policy: OnyxEntry): boolean {
return Object.keys(policy?.customUnits?.errors ?? {}).length > 0;
}
-function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string {
+function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => string): number | string {
const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.'));
if (Number.isNaN(numValue)) {
return NaN;
@@ -82,7 +81,7 @@ function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => stri
return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length);
}
-function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) {
+function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate?: Rate) {
return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit);
}
@@ -240,7 +239,7 @@ function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean {
* Note: Free policies have "instant" submit always enabled.
*/
function isInstantSubmitEnabled(policy: OnyxEntry | EmptyObject): boolean {
- return policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT || policy?.type === CONST.POLICY.TYPE.FREE;
+ return policy?.type === CONST.POLICY.TYPE.FREE || (policy?.autoReporting === true && policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT);
}
/**
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 05701c3e321f..1df59508257a 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -269,7 +269,9 @@ function getContinuousReportActionChain(sortedReportActions: ReportAction[], id?
}
if (index === -1) {
- return [];
+ // if no non-pending action is found, that means all actions on the report are optimistic
+ // in this case, we'll assume the whole chain of reportActions is continuous and return it in its entirety
+ return id ? [] : sortedReportActions;
}
let startIndex = index;
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index d0ce53a0e10b..e7a6af8c6f3a 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -1286,7 +1286,7 @@ function isPayer(session: OnyxEntry, iouReport: OnyxEntry) {
if (isPaidGroupPolicy(iouReport)) {
if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES) {
const isReimburser = session?.email === policy?.reimburserEmail;
- return isReimburser && (isApproved || isManager);
+ return (!policy?.reimburserEmail || isReimburser) && (isApproved || isManager);
}
if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL) {
return isAdmin && (isApproved || isManager);
@@ -4097,13 +4097,13 @@ function buildOptimisticTaskReport(
* @param reportAction - the parent IOU report action from which to create the thread
* @param moneyRequestReport - the report which the report action belongs to
*/
-function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReport: Report): OptimisticChatReport {
+function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReport: OnyxEntry): OptimisticChatReport {
const participantAccountIDs = [...new Set([currentUserAccountID, Number(reportAction?.actorAccountID)])].filter(Boolean) as number[];
return buildOptimisticChatReport(
participantAccountIDs,
getTransactionReportName(reportAction),
undefined,
- moneyRequestReport.policyID,
+ moneyRequestReport?.policyID,
CONST.POLICY.OWNER_ACCOUNT_ID_FAKE,
false,
'',
@@ -4111,7 +4111,7 @@ function buildTransactionThread(reportAction: OnyxEntry): boolean {
* - The action is listed in the thread-disabled list
* - The action is a split bill action
* - The action is deleted and is not threaded
+ * - The report is archived and the action is not threaded
* - The action is a whisper action and it's neither a report preview nor IOU action
* - The action is the thread's first chat
*/
@@ -5350,11 +5351,13 @@ function shouldDisableThread(reportAction: OnyxEntry, reportID: st
const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction);
const isIOUAction = ReportActionsUtils.isMoneyRequestAction(reportAction);
const isWhisperAction = ReportActionsUtils.isWhisperAction(reportAction);
+ const isArchivedReport = isArchivedRoom(getReport(reportID));
return (
CONST.REPORT.ACTIONS.THREAD_DISABLED.some((action: string) => action === reportAction?.actionName) ||
isSplitBillAction ||
(isDeletedAction && !reportAction?.childVisibleActionCount) ||
+ (isArchivedReport && !reportAction?.childVisibleActionCount) ||
(isWhisperAction && !isReportPreviewAction && !isIOUAction) ||
isThreadFirstChat(reportAction, reportID)
);
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 2f2bfc752720..5632268ef6ca 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -36,7 +36,6 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import * as Localize from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
import * as NextStepUtils from '@libs/NextStepUtils';
-import * as NumberUtils from '@libs/NumberUtils';
import Permissions from '@libs/Permissions';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as PolicyUtils from '@libs/PolicyUtils';
@@ -326,7 +325,7 @@ function updateMoneyRequestTypeParams(routes: StackNavigationState, reportID: string) {
clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID);
- Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, reportID));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.ACTION.CREATE, iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, reportID));
}
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -4485,30 +4484,6 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency:
Report.notifyNewAction(params.chatReportID, managerID);
}
-function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) {
- const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
- const iouCanceled = ReportUtils.isArchivedRoom(chatReport);
-
- if (isEmptyObject(iouReport)) {
- return false;
- }
-
- const isPayer = ReportUtils.isPayer(
- {
- email: currentUserEmail,
- accountID: userAccountID,
- },
- iouReport,
- );
-
- const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport);
- const iouSettled = ReportUtils.isSettled(iouReport?.reportID);
-
- const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
- const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy);
- return isPayer && !isOpenExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable;
-}
-
function canApproveIOU(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) {
if (isEmptyObject(chatReport)) {
return false;
@@ -4535,6 +4510,36 @@ function canApproveIOU(iouReport: OnyxEntry | EmptyObject, cha
return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled;
}
+function canIOUBePaid(iouReport: OnyxEntry | EmptyObject, chatReport: OnyxEntry | EmptyObject, policy: OnyxEntry | EmptyObject) {
+ const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
+ const iouCanceled = ReportUtils.isArchivedRoom(chatReport);
+
+ if (isEmptyObject(iouReport)) {
+ return false;
+ }
+
+ if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO) {
+ return false;
+ }
+
+ const isPayer = ReportUtils.isPayer(
+ {
+ email: currentUserEmail,
+ accountID: userAccountID,
+ },
+ iouReport,
+ );
+
+ const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport);
+ const iouSettled = ReportUtils.isSettled(iouReport?.reportID);
+
+ const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
+ const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy);
+ const shouldBeApproved = canApproveIOU(iouReport, chatReport, policy);
+
+ return isPayer && !isOpenExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable && !shouldBeApproved;
+}
+
function hasIOUToApproveOrPay(chatReport: OnyxEntry | EmptyObject, excludedIOUReportID: string): boolean {
const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? '');
@@ -4934,7 +4939,7 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On
const shouldAddAsReport = !isEmptyObject(chatReport) && ReportUtils.isSelfDM(chatReport);
const participants: Participant[] =
ReportUtils.isPolicyExpenseChat(chatReport) || shouldAddAsReport
- ? [{reportID: chatReport?.reportID, isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), selected: true}]
+ ? [{accountID: 0, reportID: chatReport?.reportID, isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), selected: true}]
: (chatReport?.participantAccountIDs ?? []).filter((accountID) => currentUserAccountID !== accountID).map((accountID) => ({accountID, selected: true}));
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true});
@@ -4972,15 +4977,6 @@ function setShownHoldUseExplanation() {
Onyx.set(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, true);
}
-function setUpDistanceTransaction() {
- const transactionID = NumberUtils.rand64();
- Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {
- transactionID,
- comment: {type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT, customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE}},
- });
- Onyx.merge(ONYXKEYS.IOU, {transactionID});
-}
-
/** Navigates to the next IOU page based on where the IOU request was started */
function navigateToNextPage(iou: OnyxEntry, iouType: string, report?: OnyxTypes.Report, path = '') {
const moneyRequestID = `${iouType}${report?.reportID ?? ''}`;
@@ -5197,7 +5193,6 @@ export {
setMoneyRequestTag,
setMoneyRequestTaxAmount,
setMoneyRequestTaxRate,
- setUpDistanceTransaction,
setShownHoldUseExplanation,
navigateToNextPage,
updateMoneyRequestDate,
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 580898a2f869..ef5a09483c67 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -83,6 +83,7 @@ import type {
ReimbursementAccount,
Report,
ReportAction,
+ TaxRatesWithDefault,
Transaction,
} from '@src/types/onyx';
import type {ErrorFields, Errors, OnyxValueWithOfflineFeedback, PendingAction} from '@src/types/onyx/OnyxCommon';
@@ -3669,6 +3670,62 @@ function enablePolicyTags(policyID: string, enabled: boolean) {
}
function enablePolicyTaxes(policyID: string, enabled: boolean) {
+ const defaultTaxRates: TaxRatesWithDefault = CONST.DEFAULT_TAX;
+ const taxRatesData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ taxRates: {
+ ...defaultTaxRates,
+ taxes: {
+ ...Object.keys(defaultTaxRates.taxes).reduce(
+ (prevTaxesData, taxKey) => ({
+ ...prevTaxesData,
+ [taxKey]: {
+ ...defaultTaxRates.taxes[taxKey],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ }),
+ {},
+ ),
+ },
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ taxRates: {
+ taxes: {
+ ...Object.keys(defaultTaxRates.taxes).reduce(
+ (prevTaxesData, taxKey) => ({
+ ...prevTaxesData,
+ [taxKey]: {pendingAction: null},
+ }),
+ {},
+ ),
+ },
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ taxRates: undefined,
+ },
+ },
+ ],
+ };
+ const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
+ const shouldAddDefaultTaxRatesData = (!policy?.taxRates || isEmptyObject(policy.taxRates)) && enabled;
const onyxData: OnyxData = {
optimisticData: [
{
@@ -3683,6 +3740,7 @@ function enablePolicyTaxes(policyID: string, enabled: boolean) {
},
},
},
+ ...(shouldAddDefaultTaxRatesData ? taxRatesData.optimisticData ?? [] : []),
],
successData: [
{
@@ -3694,6 +3752,7 @@ function enablePolicyTaxes(policyID: string, enabled: boolean) {
},
},
},
+ ...(shouldAddDefaultTaxRatesData ? taxRatesData.successData ?? [] : []),
],
failureData: [
{
@@ -3708,11 +3767,14 @@ function enablePolicyTaxes(policyID: string, enabled: boolean) {
},
},
},
+ ...(shouldAddDefaultTaxRatesData ? taxRatesData.failureData ?? [] : []),
],
};
const parameters: EnablePolicyTaxesParams = {policyID, enabled};
-
+ if (shouldAddDefaultTaxRatesData) {
+ parameters.taxFields = JSON.stringify(defaultTaxRates);
+ }
API.write(WRITE_COMMANDS.ENABLE_POLICY_TAXES, parameters, onyxData);
if (enabled) {
@@ -4623,3 +4685,5 @@ export {
setPolicyDistanceRatesEnabled,
deletePolicyDistanceRates,
};
+
+export type {NewCustomUnit};
diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts
index aad6ae39810a..72d1fe9fe63d 100644
--- a/src/libs/actions/ReportActions.ts
+++ b/src/libs/actions/ReportActions.ts
@@ -1,4 +1,5 @@
import Onyx from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
import * as ReportActionUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
@@ -6,10 +7,10 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type ReportAction from '@src/types/onyx/ReportAction';
import * as Report from './Report';
-function clearReportActionErrors(reportID: string, reportAction: ReportAction) {
+function clearReportActionErrors(reportID: string, reportAction: OnyxEntry) {
const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction);
- if (!reportAction.reportActionID) {
+ if (!reportAction?.reportActionID) {
return;
}
diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts
index 3d9aa5646098..123614f2e0bb 100644
--- a/src/libs/actions/Transaction.ts
+++ b/src/libs/actions/Transaction.ts
@@ -4,7 +4,7 @@ import lodashHas from 'lodash/has';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
-import type {GetRouteForDraftParams, GetRouteParams} from '@libs/API/parameters';
+import type {GetRouteParams} from '@libs/API/parameters';
import {READ_COMMANDS} from '@libs/API/types';
import * as CollectionUtils from '@libs/CollectionUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
@@ -218,26 +218,13 @@ function getOnyxDataForRouteRequest(transactionID: string, isDraft = false): Ony
* Gets the route for a set of waypoints
* Used so we can generate a map view of the provided waypoints
*/
-function getRoute(transactionID: string, waypoints: WaypointCollection) {
+function getRoute(transactionID: string, waypoints: WaypointCollection, isDraft: boolean) {
const parameters: GetRouteParams = {
transactionID,
waypoints: JSON.stringify(waypoints),
};
- API.read(READ_COMMANDS.GET_ROUTE, parameters, getOnyxDataForRouteRequest(transactionID));
-}
-
-/**
- * Gets the route for a set of waypoints
- * Used so we can generate a map view of the provided waypoints
- */
-function getRouteForDraft(transactionID: string, waypoints: WaypointCollection) {
- const parameters: GetRouteForDraftParams = {
- transactionID,
- waypoints: JSON.stringify(waypoints),
- };
-
- API.read(READ_COMMANDS.GET_ROUTE_FOR_DRAFT, parameters, getOnyxDataForRouteRequest(transactionID, true));
+ API.read(isDraft ? READ_COMMANDS.GET_ROUTE_FOR_DRAFT : READ_COMMANDS.GET_ROUTE, parameters, getOnyxDataForRouteRequest(transactionID, isDraft));
}
/**
@@ -277,4 +264,4 @@ function clearError(transactionID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null});
}
-export {addStop, createInitialWaypoints, saveWaypoint, removeWaypoint, getRoute, getRouteForDraft, updateWaypoints, clearError};
+export {addStop, createInitialWaypoints, saveWaypoint, removeWaypoint, getRoute, updateWaypoints, clearError};
diff --git a/src/pages/EditRequestDistancePage.js b/src/pages/EditRequestDistancePage.js
deleted file mode 100644
index f3ea76a3390a..000000000000
--- a/src/pages/EditRequestDistancePage.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
-import React, {useEffect, useRef} from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import DistanceRequest from '@components/DistanceRequest';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import transactionPropTypes from '@components/transactionPropTypes';
-import useLocalize from '@hooks/useLocalize';
-import useNetwork from '@hooks/useNetwork';
-import usePrevious from '@hooks/usePrevious';
-import Navigation from '@libs/Navigation/Navigation';
-import * as IOU from '@userActions/IOU';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import reportPropTypes from './reportPropTypes';
-
-const propTypes = {
- /** The transactionID we're currently editing */
- transactionID: PropTypes.string.isRequired,
-
- /** The report to with which the distance request is associated */
- report: reportPropTypes.isRequired,
-
- /** Passed from the navigator */
- route: PropTypes.shape({
- /** Parameters the route gets */
- params: PropTypes.shape({
- /** Type of IOU */
- iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)),
-
- /** Id of the report on which the distance request is being created */
- reportID: PropTypes.string,
- }),
- }).isRequired,
-
- /* Onyx props */
- /** The original transaction that is being edited */
- transaction: transactionPropTypes,
-
- /** backup version of the original transaction */
- transactionBackup: transactionPropTypes,
-};
-
-const defaultProps = {
- transaction: {},
- transactionBackup: {},
-};
-
-function EditRequestDistancePage({report, route, transaction, transactionBackup}) {
- const {isOffline} = useNetwork();
- const {translate} = useLocalize();
- const hasWaypointError = useRef(false);
- const prevIsLoading = usePrevious(transaction.isLoading);
-
- useEffect(() => {
- hasWaypointError.current = Boolean(lodashGet(transaction, 'errorFields.route') || lodashGet(transaction, 'errorFields.waypoints'));
-
- // When the loading goes from true to false, then we know the transaction has just been
- // saved to the server. Check for errors. If there are no errors, then the modal can be closed.
- if (prevIsLoading && !transaction.isLoading && !hasWaypointError.current) {
- Navigation.dismissModal(report.reportID);
- }
- }, [transaction, prevIsLoading, report]);
-
- /**
- * Save the changes to the original transaction object
- * @param {Object} waypoints
- */
- const saveTransaction = (waypoints) => {
- // If nothing was changed, simply go to transaction thread
- // We compare only addresses because numbers are rounded while backup
- const oldWaypoints = lodashGet(transactionBackup, 'comment.waypoints', {});
- const oldAddresses = _.mapObject(oldWaypoints, (waypoint) => _.pick(waypoint, 'address'));
- const addresses = _.mapObject(waypoints, (waypoint) => _.pick(waypoint, 'address'));
- if (_.isEqual(oldAddresses, addresses)) {
- Navigation.dismissModal(report.reportID);
- return;
- }
-
- IOU.updateMoneyRequestDistance(transaction.transactionID, report.reportID, waypoints);
-
- // If the client is offline, then the modal can be closed as well (because there are no errors or other feedback to show them
- // until they come online again and sync with the server).
- if (isOffline) {
- Navigation.dismissModal(report.reportID);
- }
- };
-
- return (
-
- Navigation.goBack()}
- />
-
-
- );
-}
-
-EditRequestDistancePage.propTypes = propTypes;
-EditRequestDistancePage.defaultProps = defaultProps;
-EditRequestDistancePage.displayName = 'EditRequestDistancePage';
-export default withOnyx({
- transaction: {
- key: (props) => `${ONYXKEYS.COLLECTION.TRANSACTION}${props.transactionID}`,
- },
- transactionBackup: {
- key: (props) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${props.transactionID}`,
- },
-})(EditRequestDistancePage);
diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js
index de17d16a7c38..7d10e0e55e79 100644
--- a/src/pages/EditRequestPage.js
+++ b/src/pages/EditRequestPage.js
@@ -20,7 +20,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import EditRequestAmountPage from './EditRequestAmountPage';
-import EditRequestDistancePage from './EditRequestDistancePage';
import EditRequestReceiptPage from './EditRequestReceiptPage';
import EditRequestTagPage from './EditRequestTagPage';
import reportActionPropTypes from './home/report/reportActionPropTypes';
@@ -176,16 +175,6 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p
);
}
- if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DISTANCE) {
- return (
-
- );
- }
-
return (
{
if (!sortedAllReportActions.length) {
return [];
@@ -243,9 +243,11 @@ function ReportScreen({
return currentRangeOfReportActions;
}, [reportActionIDFromRoute, sortedAllReportActions]);
- // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. If we have cached reportActions, they will be shown immediately. We aim to display a loader first, then fetch relevant reportActions, and finally show them.
+ // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger.
+ // If we have cached reportActions, they will be shown immediately.
+ // We aim to display a loader first, then fetch relevant reportActions, and finally show them.
useLayoutEffect(() => {
- setLinkingToMessage(!!reportActionIDFromRoute);
+ setIsLinkingToMessage(!!reportActionIDFromRoute);
}, [route, reportActionIDFromRoute]);
const [isBannerVisible, setIsBannerVisible] = useState(true);
@@ -261,15 +263,12 @@ function ReportScreen({
const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report);
const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}];
const isEmptyChat = useMemo((): boolean => reportActions.length === 0, [reportActions]);
- // There are no reportActions at all to display and we are still in the process of loading the next set of actions.
- const isLoadingInitialReportActions = reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions;
const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
// If there's a non-404 error for the report we should show it instead of blocking the screen
const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound');
const shouldHideReport = !hasHelpfulErrors && !ReportUtils.canAccessReport(report, policies, betas);
- const isLoading = !reportIDFromRoute || !isSidebarLoaded || PersonalDetailsUtils.isPersonalDetailsEmpty();
const lastReportAction: OnyxEntry = useMemo(
() =>
reportActions.length
@@ -325,16 +324,34 @@ function ReportScreen({
/**
* When false the ReportActionsView will completely unmount and we will show a loader until it returns true.
*/
- const isReportReadyForDisplay = useMemo((): boolean => {
+ const isCurrentReportLoadedFromOnyx = useMemo((): boolean => {
// This is necessary so that when we are retrieving the next report data from Onyx the ReportActionsView will remount completely
const isTransitioning = report && report.reportID !== reportIDFromRoute;
return reportIDFromRoute !== '' && !!report.reportID && !isTransitioning;
}, [report, reportIDFromRoute]);
+ const isLoading = !ReportUtils.isValidReportIDFromPath(reportIDFromRoute) || !isSidebarLoaded || PersonalDetailsUtils.isPersonalDetailsEmpty();
const shouldShowSkeleton =
- isLinkingToMessage || !isReportReadyForDisplay || isLoadingInitialReportActions || isLoading || (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions);
-
- const shouldShowReportActionList = isReportReadyForDisplay && !isLoading;
+ isLinkingToMessage ||
+ !isCurrentReportLoadedFromOnyx ||
+ (reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) ||
+ isLoading ||
+ (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions);
+ const shouldShowReportActionList = isCurrentReportLoadedFromOnyx && !isLoading;
+ // eslint-disable-next-line rulesdir/no-negated-variables
+ const shouldShowNotFoundPage = useMemo(
+ (): boolean =>
+ !shouldShowSkeleton &&
+ ((!wasReportAccessibleRef.current &&
+ !firstRenderRef.current &&
+ !report.reportID &&
+ !isOptimisticDelete &&
+ !reportMetadata?.isLoadingInitialReportActions &&
+ !userLeavingStatus) ||
+ shouldHideReport ||
+ (!!reportIDFromRoute && !ReportUtils.isValidReportIDFromPath(reportIDFromRoute))),
+ [shouldShowSkeleton, report.reportID, isOptimisticDelete, reportMetadata?.isLoadingInitialReportActions, userLeavingStatus, shouldHideReport, reportIDFromRoute],
+ );
const fetchReport = useCallback(() => {
Report.openReport(reportIDFromRoute, reportActionIDFromRoute);
@@ -399,7 +416,7 @@ function ReportScreen({
});
return () => {
interactionTask.cancel();
- if (!didSubscribeToReportLeavingEvents) {
+ if (!didSubscribeToReportLeavingEvents.current) {
return;
}
@@ -490,6 +507,10 @@ function ReportScreen({
if (!ReportUtils.isValidReportIDFromPath(reportIDFromRoute)) {
return;
}
+ // Ensures the optimistic report is created successfully
+ if (reportIDFromRoute !== report.reportID) {
+ return;
+ }
// Ensures subscription event succeeds when the report/workspace room is created optimistically.
// Check if the optimistic `OpenReport` or `AddWorkspaceRoom` has succeeded by confirming
// any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null.
@@ -520,34 +541,19 @@ function ReportScreen({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- // eslint-disable-next-line rulesdir/no-negated-variables
- const shouldShowNotFoundPage = useMemo(
- (): boolean =>
- (!wasReportAccessibleRef.current &&
- !firstRenderRef.current &&
- !report.reportID &&
- !isOptimisticDelete &&
- !reportMetadata?.isLoadingInitialReportActions &&
- !isLoading &&
- !userLeavingStatus) ||
- shouldHideReport ||
- (!!reportIDFromRoute && !ReportUtils.isValidReportIDFromPath(reportIDFromRoute)),
- [report, reportMetadata, isLoading, shouldHideReport, isOptimisticDelete, userLeavingStatus, reportIDFromRoute],
- );
-
const actionListValue = useMemo((): ActionListContextType => ({flatListRef, scrollPosition, setScrollPosition}), [flatListRef, scrollPosition, setScrollPosition]);
// This helps in tracking from the moment 'route' triggers useMemo until isLoadingInitialReportActions becomes true. It prevents blinking when loading reportActions from cache.
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
- setLinkingToMessage(false);
+ setIsLinkingToMessage(false);
});
}, [reportMetadata?.isLoadingInitialReportActions]);
- const onLinkPress = () => {
+ const navigateToEndOfReport = useCallback(() => {
Navigation.setParams({reportActionID: ''});
fetchReport();
- };
+ }, [fetchReport]);
const isLinkedReportActionDeleted = useMemo(() => {
if (!reportActionIDFromRoute || !sortedAllReportActions) {
@@ -566,7 +572,7 @@ function ReportScreen({
title={translate('notFound.notHere')}
shouldShowLink
linkKey="notFound.noAccess"
- onLinkPress={onLinkPress}
+ onLinkPress={navigateToEndOfReport}
/>
);
}
@@ -614,7 +620,7 @@ function ReportScreen({
shouldShowCloseButton
/>
)}
-
+ }
- {isReportReadyForDisplay ? (
+ {isCurrentReportLoadedFromOnyx ? (
+
@@ -707,7 +707,7 @@ function ReportActionItem({
if (ReportUtils.isTaskReport(report)) {
if (ReportUtils.isCanceledTaskReport(report, parentReportAction)) {
return (
-
+
@@ -725,7 +725,7 @@ function ReportActionItem({
);
}
return (
-
+ {
if (draftMessage === action?.message?.[0].html) {
@@ -124,11 +126,11 @@ function ReportActionItemMessageEdit(
const draftRef = useRef(draft);
useEffect(() => {
- if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) {
+ if (ReportActionsUtils.isDeletedAction(action) || Boolean(action.message && draftMessage === action.message[0].html) || Boolean(prevDraftMessage === draftMessage)) {
return;
}
setDraft(Str.htmlDecode(draftMessage));
- }, [draftMessage, action]);
+ }, [draftMessage, action, prevDraftMessage]);
useEffect(() => {
// required for keeping last state of isFocused variable
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index c74bb40a18b6..68cd069608da 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -264,6 +264,10 @@ function ReportActionsView({
}, [isSmallScreenWidth, reportActions, isReportFullyVisible]);
useEffect(() => {
+ // Ensures the optimistic report is created successfully
+ if (route?.params?.reportID !== reportID) {
+ return;
+ }
// Ensures subscription event succeeds when the report/workspace room is created optimistically.
// Check if the optimistic `OpenReport` or `AddWorkspaceRoom` has succeeded by confirming
// any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null.
@@ -278,7 +282,7 @@ function ReportActionsView({
interactionTask.cancel();
};
}
- }, [report.pendingFields, didSubscribeToReportTypingEvents, reportID]);
+ }, [report.pendingFields, didSubscribeToReportTypingEvents, route, reportID]);
const onContentSizeChange = useCallback((w: number, h: number) => {
contentListHeight.current = h;
diff --git a/src/pages/iou/NewDistanceRequestPage.js b/src/pages/iou/NewDistanceRequestPage.js
deleted file mode 100644
index 750ac5d0141e..000000000000
--- a/src/pages/iou/NewDistanceRequestPage.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
-import React, {useCallback, useEffect} from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import DistanceRequest from '@components/DistanceRequest';
-import Navigation from '@libs/Navigation/Navigation';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as IOU from '@userActions/IOU';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-import {iouPropTypes} from './propTypes';
-
-const propTypes = {
- /** Holds data related to Money Request view state, rather than the underlying Money Request data. */
- iou: iouPropTypes,
-
- /** The report on which the request is initiated on */
- report: reportPropTypes,
-
- /** Passed from the navigator */
- route: PropTypes.shape({
- /** Parameters the route gets */
- params: PropTypes.shape({
- /** Type of IOU */
- iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)),
- /** Id of the report on which the distance request is being created */
- reportID: PropTypes.string,
- }),
- }),
-};
-
-const defaultProps = {
- iou: {},
- report: {},
- route: {
- params: {
- iouType: '',
- reportID: '',
- },
- },
-};
-
-// This component is responsible for getting the transactionID from the IOU key, or creating the transaction if it doesn't exist yet, and then passing the transactionID.
-// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that DistanceRequest can subscribe to the transaction.
-function NewDistanceRequestPage({iou, report, route}) {
- const iouType = lodashGet(route, 'params.iouType', 'request');
- const isEditingNewRequest = Navigation.getActiveRoute().includes('address');
-
- useEffect(() => {
- if (iou.transactionID) {
- return;
- }
- IOU.setUpDistanceTransaction();
- }, [iou.transactionID]);
-
- const onSubmit = useCallback(() => {
- if (isEditingNewRequest) {
- Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID));
- return;
- }
- IOU.navigateToNextPage(iou, iouType, report);
- }, [iou, iouType, isEditingNewRequest, report]);
-
- return (
-
- );
-}
-
-NewDistanceRequestPage.displayName = 'NewDistanceRequestPage';
-NewDistanceRequestPage.propTypes = propTypes;
-NewDistanceRequestPage.defaultProps = defaultProps;
-export default withOnyx({
- iou: {key: ONYXKEYS.IOU},
- report: {
- key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID')}`,
- },
-})(NewDistanceRequestPage);
diff --git a/src/pages/iou/request/IOURequestRedirectToStartPage.js b/src/pages/iou/request/IOURequestRedirectToStartPage.js
index ee98c8006cdb..2da235743705 100644
--- a/src/pages/iou/request/IOURequestRedirectToStartPage.js
+++ b/src/pages/iou/request/IOURequestRedirectToStartPage.js
@@ -41,11 +41,11 @@ function IOURequestRedirectToStartPage({
// Redirect the person to the right start page using a rendom reportID
const optimisticReportID = ReportUtils.generateReportID();
if (iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE) {
- Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_DISTANCE.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_DISTANCE.getRoute(CONST.IOU.ACTION.CREATE, iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID));
} else if (iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL) {
- Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL.getRoute(CONST.IOU.ACTION.CREATE, iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID));
} else if (iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN) {
- Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID));
+ Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, optimisticReportID));
}
// This useEffect should only run on mount which is why there are no dependencies being passed in the second parameter
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js
index 3dd6f08c0ce0..435121a76028 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.js
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js
@@ -112,8 +112,8 @@ function IOURequestStepConfirmation({
const participants = useMemo(
() =>
_.map(transaction.participants, (participant) => {
- const participantReportID = lodashGet(participant, 'reportID', '');
- return participantReportID ? OptionsListUtils.getReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails);
+ const participantAccountID = lodashGet(participant, 'accountID', 0);
+ return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant);
}),
[transaction.participants, personalDetails],
);
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.js b/src/pages/iou/request/step/IOURequestStepDistance.js
index 9e12381bc497..dad610cbc636 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.js
+++ b/src/pages/iou/request/step/IOURequestStepDistance.js
@@ -1,6 +1,7 @@
import lodashGet from 'lodash/get';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import Button from '@components/Button';
import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter';
@@ -22,6 +23,7 @@ import * as IOU from '@userActions/IOU';
import * as MapboxToken from '@userActions/MapboxToken';
import * as Transaction from '@userActions/Transaction';
import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
import StepScreenWrapper from './StepScreenWrapper';
@@ -38,19 +40,24 @@ const propTypes = {
/** The transaction object being modified in Onyx */
transaction: transactionPropTypes,
+
+ /** backup version of the original transaction */
+ transactionBackup: transactionPropTypes,
};
const defaultProps = {
report: {},
transaction: {},
+ transactionBackup: {},
};
function IOURequestStepDistance({
report,
route: {
- params: {iouType, reportID, transactionID, backTo},
+ params: {action, iouType, reportID, transactionID, backTo},
},
transaction,
+ transactionBackup,
}) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();
@@ -76,6 +83,8 @@ function IOURequestStepDistance({
const nonEmptyWaypointsCount = useMemo(() => _.filter(_.keys(waypoints), (key) => !_.isEmpty(waypoints[key])).length, [waypoints]);
const duplicateWaypointsError = useMemo(() => nonEmptyWaypointsCount >= 2 && _.size(validatedWaypoints) !== nonEmptyWaypointsCount, [nonEmptyWaypointsCount, validatedWaypoints]);
const atLeastTwoDifferentWaypointsError = useMemo(() => _.size(validatedWaypoints) < 2, [validatedWaypoints]);
+ const isEditing = action === CONST.IOU.ACTION.EDIT;
+ const isCreatingNewRequest = Navigation.getActiveRoute().includes('start');
useEffect(() => {
MapboxToken.init();
@@ -86,8 +95,8 @@ function IOURequestStepDistance({
if (isOffline || !shouldFetchRoute) {
return;
}
- Transaction.getRouteForDraft(transactionID, validatedWaypoints);
- }, [shouldFetchRoute, transactionID, validatedWaypoints, isOffline]);
+ Transaction.getRoute(transactionID, validatedWaypoints, action === CONST.IOU.ACTION.CREATE);
+ }, [shouldFetchRoute, transactionID, validatedWaypoints, isOffline, action]);
useEffect(() => {
if (numberOfWaypoints <= numberOfPreviousWaypoints) {
@@ -112,9 +121,7 @@ function IOURequestStepDistance({
* @param {Number} index of the waypoint to edit
*/
const navigateToWaypointEditPage = (index) => {
- Navigation.navigate(
- ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.REQUEST, transactionID, report.reportID, index, Navigation.getActiveRouteWithoutParams()),
- );
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, CONST.IOU.TYPE.REQUEST, transactionID, report.reportID, index, Navigation.getActiveRouteWithoutParams()));
};
const navigateToNextStep = useCallback(() => {
@@ -168,11 +175,14 @@ function IOURequestStepDistance({
setOptimisticWaypoints(newWaypoints);
// eslint-disable-next-line rulesdir/no-thenable-actions-in-views
- Promise.all([Transaction.removeWaypoint(transaction, emptyWaypointIndex.toString(), true), Transaction.updateWaypoints(transactionID, newWaypoints, true)]).then(() => {
- setOptimisticWaypoints(null);
+ Promise.all([
+ Transaction.removeWaypoint(transaction, emptyWaypointIndex.toString(), action === CONST.IOU.ACTION.CREATE),
+ Transaction.updateWaypoints(transactionID, newWaypoints, action === CONST.IOU.ACTION.CREATE),
+ ]).then(() => {
+ setOptimisticWaypoints(undefined);
});
},
- [transactionID, transaction, waypoints, waypointsList],
+ [transactionID, transaction, waypoints, waypointsList, action],
);
const submitWaypoints = useCallback(() => {
@@ -181,15 +191,42 @@ function IOURequestStepDistance({
setShouldShowAtLeastTwoDifferentWaypointsError(true);
return;
}
+ if (isEditing) {
+ // If nothing was changed, simply go to transaction thread
+ // We compare only addresses because numbers are rounded while backup
+ const oldWaypoints = lodashGet(transactionBackup, 'comment.waypoints', {});
+ const oldAddresses = _.mapObject(oldWaypoints, (waypoint) => _.pick(waypoint, 'address'));
+ const addresses = _.mapObject(waypoints, (waypoint) => _.pick(waypoint, 'address'));
+ if (_.isEqual(oldAddresses, addresses)) {
+ Navigation.dismissModal(report.reportID);
+ return;
+ }
+ IOU.updateMoneyRequestDistance(transaction.transactionID, report.reportID, waypoints);
+ Navigation.dismissModal(report.reportID);
+ return;
+ }
+
navigateToNextStep();
- }, [atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, isLoadingRoute, isLoading, navigateToNextStep]);
+ }, [
+ duplicateWaypointsError,
+ atLeastTwoDifferentWaypointsError,
+ hasRouteError,
+ isLoadingRoute,
+ isLoading,
+ isEditing,
+ navigateToNextStep,
+ transactionBackup,
+ waypoints,
+ transaction.transactionID,
+ report.reportID,
+ ]);
return (
<>
@@ -237,7 +274,7 @@ function IOURequestStepDistance({
large
style={[styles.w100, styles.mb4, styles.ph4, styles.flexShrink0]}
onPress={submitWaypoints}
- text={translate('common.next')}
+ text={translate(!isCreatingNewRequest ? 'common.save' : 'common.next')}
isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
/>
@@ -250,4 +287,12 @@ IOURequestStepDistance.displayName = 'IOURequestStepDistance';
IOURequestStepDistance.propTypes = propTypes;
IOURequestStepDistance.defaultProps = defaultProps;
-export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepDistance);
+export default compose(
+ withWritableReportOrNotFound,
+ withFullTransactionOrNotFound,
+ withOnyx({
+ transactionBackup: {
+ key: (props) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${props.transactionID}`,
+ },
+ }),
+)(IOURequestStepDistance);
diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx
index c645bc70ede0..4e61ac944aac 100644
--- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx
+++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx
@@ -107,7 +107,7 @@ function IOURequestStepWaypoint({
const waypointValue = values[`waypoint${pageIndex}`] ?? '';
// Allows letting you set a waypoint to an empty value
if (waypointValue === '') {
- Transaction.removeWaypoint(transaction, pageIndex, true);
+ Transaction.removeWaypoint(transaction, pageIndex, action === CONST.IOU.ACTION.CREATE);
}
// While the user is offline, the auto-complete address search will not work
@@ -123,13 +123,13 @@ function IOURequestStepWaypoint({
}
// Other flows will be handled by selecting a waypoint with selectWaypoint as this is mainly for the offline flow
- Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID));
+ Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID));
};
const deleteStopAndHideModal = () => {
- Transaction.removeWaypoint(transaction, pageIndex, true);
+ Transaction.removeWaypoint(transaction, pageIndex, action === CONST.IOU.ACTION.CREATE);
setIsDeleteStopModalOpen(false);
- Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID));
+ Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID));
};
const selectWaypoint = (values: Waypoint) => {
@@ -145,7 +145,7 @@ function IOURequestStepWaypoint({
Navigation.goBack(backTo);
return;
}
- Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_DISTANCE.getRoute(iouType, transactionID, reportID));
+ Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_DISTANCE.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID));
};
return (
@@ -160,7 +160,7 @@ function IOURequestStepWaypoint({
title={translate(waypointDescriptionKey)}
shouldShowBackButton
onBackButtonPress={() => {
- Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transactionID, reportID));
+ Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID));
}}
shouldShowThreeDotsButton={shouldShowThreeDotsButton}
shouldSetModalVisibility={false}
diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx
index d8b407d5cee9..662335d0b358 100644
--- a/src/pages/workspace/WorkspaceProfilePage.tsx
+++ b/src/pages/workspace/WorkspaceProfilePage.tsx
@@ -12,7 +12,6 @@ import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
-import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import Text from '@components/Text';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
@@ -119,133 +118,131 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
icon={Illustrations.House}
>
{(hasVBA?: boolean) => (
-
-
-
-
- Navigation.navigate(ROUTES.WORKSPACE_AVATAR.getRoute(policy?.id ?? ''))}
- source={policy?.avatar ?? ''}
- size={CONST.AVATAR_SIZE.XLARGE}
- avatarStyle={styles.avatarXLarge}
- enablePreview
- DefaultAvatar={DefaultAvatar}
- type={CONST.ICON_TYPE_WORKSPACE}
- fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
- style={[
- policy?.errorFields?.avatar ?? isSmallScreenWidth ? styles.mb1 : styles.mb3,
- isSmallScreenWidth ? styles.mtn17 : styles.mtn20,
- styles.alignItemsStart,
- styles.sectionMenuItemTopDescription,
- ]}
- editIconStyle={styles.smallEditIconWorkspace}
- isUsingDefaultAvatar={!policy?.avatar ?? null}
- onImageSelected={(file) => Policy.updateWorkspaceAvatar(policy?.id ?? '', file as File)}
- onImageRemoved={() => Policy.deleteWorkspaceAvatar(policy?.id ?? '')}
- editorMaskImage={Expensicons.ImageCropSquareMask}
- pendingAction={policy?.pendingFields?.avatar}
- errors={policy?.errorFields?.avatar}
- onErrorClose={() => Policy.clearAvatarErrors(policy?.id ?? '')}
- previewSource={UserUtils.getFullSizeAvatar(policy?.avatar ?? '')}
- headerTitle={translate('workspace.common.workspaceAvatar')}
- originalFileName={policy?.originalFileName}
+
+
+
+ Navigation.navigate(ROUTES.WORKSPACE_AVATAR.getRoute(policy?.id ?? ''))}
+ source={policy?.avatar ?? ''}
+ size={CONST.AVATAR_SIZE.XLARGE}
+ avatarStyle={styles.avatarXLarge}
+ enablePreview
+ DefaultAvatar={DefaultAvatar}
+ type={CONST.ICON_TYPE_WORKSPACE}
+ fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
+ style={[
+ policy?.errorFields?.avatar ?? isSmallScreenWidth ? styles.mb1 : styles.mb3,
+ isSmallScreenWidth ? styles.mtn17 : styles.mtn20,
+ styles.alignItemsStart,
+ styles.sectionMenuItemTopDescription,
+ ]}
+ editIconStyle={styles.smallEditIconWorkspace}
+ isUsingDefaultAvatar={!policy?.avatar ?? null}
+ onImageSelected={(file) => Policy.updateWorkspaceAvatar(policy?.id ?? '', file as File)}
+ onImageRemoved={() => Policy.deleteWorkspaceAvatar(policy?.id ?? '')}
+ editorMaskImage={Expensicons.ImageCropSquareMask}
+ pendingAction={policy?.pendingFields?.avatar}
+ errors={policy?.errorFields?.avatar}
+ onErrorClose={() => Policy.clearAvatarErrors(policy?.id ?? '')}
+ previewSource={UserUtils.getFullSizeAvatar(policy?.avatar ?? '')}
+ headerTitle={translate('workspace.common.workspaceAvatar')}
+ originalFileName={policy?.originalFileName}
+ disabled={readOnly}
+ disabledStyle={styles.cursorDefault}
+ errorRowStyles={styles.mt3}
+ />
+
+
-
+
+ {(!StringUtils.isEmptyString(policy?.description ?? '') || !readOnly) && (
+ Policy.clearPolicyErrorField(policy?.id ?? '', CONST.POLICY.COLLECTION_KEYS.DESCRIPTION)}
+ >
- {(!StringUtils.isEmptyString(policy?.description ?? '') || !readOnly) && (
- Policy.clearPolicyErrorField(policy?.id ?? '', CONST.POLICY.COLLECTION_KEYS.DESCRIPTION)}
- >
-
-
- )}
- Policy.clearPolicyErrorField(policy?.id ?? '', CONST.POLICY.COLLECTION_KEYS.GENERAL_SETTINGS)}
- errorRowStyles={[styles.mt2]}
- >
-
-
-
- {hasVBA ? translate('workspace.editor.currencyInputDisabledText') : translate('workspace.editor.currencyInputHelpText')}
-
-
-
- {!readOnly && (
-
-
-
- )}
-
- setIsDeleteModalOpen(false)}
- prompt={translate('workspace.common.deleteConfirmation')}
- confirmText={translate('common.delete')}
- cancelText={translate('common.cancel')}
- danger
- />
-
-
+ )}
+ Policy.clearPolicyErrorField(policy?.id ?? '', CONST.POLICY.COLLECTION_KEYS.GENERAL_SETTINGS)}
+ errorRowStyles={[styles.mt2]}
+ >
+
+
+
+ {hasVBA ? translate('workspace.editor.currencyInputDisabledText') : translate('workspace.editor.currencyInputHelpText')}
+
+
+
+ {!readOnly && (
+
+
+
+ )}
+
+ setIsDeleteModalOpen(false)}
+ prompt={translate('workspace.common.deleteConfirmation')}
+ confirmText={translate('common.delete')}
+ cancelText={translate('common.cancel')}
+ danger
+ />
+
)}
);
diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
index f3456c3875f5..46afcd624350 100644
--- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
+++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@@ -273,6 +273,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat
style={[styles.defaultModalContainer]}
testID={WorkspaceCategoriesPage.displayName}
shouldShowOfflineIndicatorInWideScreen
+ offlineIndicatorStyle={styles.mtAuto}
>
)}
- {!shouldShowEmptyState && (
+ {!shouldShowEmptyState && !isLoading && (
) => {
const newRate: Rate = {
currency,
- name: CONST.CUSTOM_UNITS.DEFAULT_RATE,
+ name: getOptimisticRateName(customUnits[customUnitID]?.rates),
rate: parseFloat(values.rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET,
customUnitRateID,
enabled: true,
@@ -70,6 +70,7 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) {
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={CreateDistanceRatePage.displayName}
+ shouldEnableMaxHeight
>
{
- if (selectedDistanceRates.length === Object.values(customUnitRates).length) {
+ const allSelectableRates = Object.values(customUnitRates).filter((rate) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+
+ if (selectedDistanceRates.length === allSelectableRates.length) {
setSelectedDistanceRates([]);
} else {
- setSelectedDistanceRates([...Object.values(customUnitRates).filter((rate) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)]);
+ setSelectedDistanceRates([...allSelectableRates]);
}
};
diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx
new file mode 100644
index 000000000000..8fef5f4dc6f9
--- /dev/null
+++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx
@@ -0,0 +1,158 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useEffect} from 'react';
+import {Keyboard, View} from 'react-native';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormOnyxValues} from '@components/Form/types';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import Picker from '@components/Picker';
+import TextInput from '@components/TextInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
+import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator';
+import Navigation from '@libs/Navigation/Navigation';
+import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import * as NumberUtils from '@libs/NumberUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
+import * as BankAccounts from '@userActions/BankAccounts';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Unit} from '@src/types/onyx/Policy';
+
+type WorkspaceRateAndUnitPageProps = WithPolicyProps & StackScreenProps;
+
+type ValidationError = {rate?: TranslationPaths | undefined};
+
+function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps) {
+ const {translate, toLocaleDigit} = useLocalize();
+ const styles = useThemeStyles();
+
+ useEffect(() => {
+ if ((policy?.customUnits ?? []).length !== 0) {
+ return;
+ }
+
+ BankAccounts.setReimbursementAccountLoading(true);
+ Policy.openWorkspaceReimburseView(policy?.id ?? '');
+ }, [policy?.customUnits, policy?.id]);
+
+ const unitItems = [
+ {label: translate('common.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS},
+ {label: translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES},
+ ];
+
+ const saveUnitAndRate = (unit: Unit, rate: string) => {
+ const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
+ if (!distanceCustomUnit) {
+ return;
+ }
+ const currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
+ const unitID = distanceCustomUnit.customUnitID ?? '';
+ const unitName = distanceCustomUnit.name ?? '';
+ const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit);
+
+ const newCustomUnit: Policy.NewCustomUnit = {
+ customUnitID: unitID,
+ name: unitName,
+ attributes: {unit},
+ rates: {
+ ...currentCustomUnitRate,
+ rate: Number(rateNumValue) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET,
+ },
+ };
+
+ Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, policy?.lastModified);
+ };
+
+ const submit = (values: FormOnyxValues) => {
+ saveUnitAndRate(values.unit as Unit, values.rate);
+ Keyboard.dismiss();
+ Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(policy?.id ?? ''));
+ };
+
+ const validate = (values: FormOnyxValues): ValidationError => {
+ const errors: ValidationError = {};
+ const decimalSeparator = toLocaleDigit('.');
+ const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD;
+ // Allow one more decimal place for accuracy
+ const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i');
+ if (!rateValueRegex.test(values.rate) || values.rate === '') {
+ errors.rate = 'workspace.reimburse.invalidRateError';
+ } else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) {
+ errors.rate = 'workspace.reimburse.lowRateError';
+ }
+ return errors;
+ };
+
+ const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
+ const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
+
+ return (
+
+ {() => (
+
+ Policy.clearCustomUnitErrors(policy?.id ?? '', distanceCustomUnit?.customUnitID ?? '', distanceCustomRate?.customUnitRateID ?? '')}
+ >
+
+
+
+
+
+
+
+ )}
+
+ );
+}
+
+WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage';
+
+export default withPolicy(WorkspaceRateAndUnitPage);
diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx
index 709e51cba383..8685cd3b1aee 100644
--- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx
+++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx
@@ -9,7 +9,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
-import validateRateValue from '@libs/PolicyDistanceRatesUtils';
+import {validateRateValue} from '@libs/PolicyDistanceRatesUtils';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js b/src/pages/workspace/reimburse/WorkspaceReimbursePage.js
deleted file mode 100644
index 94e72e30e4fb..000000000000
--- a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import compose from '@libs/compose';
-import withPolicy, {policyPropTypes} from '@pages/workspace/withPolicy';
-import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
-import CONST from '@src/CONST';
-import WorkspaceReimburseView from './WorkspaceReimburseView';
-
-const propTypes = {
- /** The route object passed to this page from the navigator */
- route: PropTypes.shape({
- /** Each parameter passed via the URL */
- params: PropTypes.shape({
- /** The policyID that is being configured */
- policyID: PropTypes.string.isRequired,
- }).isRequired,
- }).isRequired,
-
- ...policyPropTypes,
- ...withLocalizePropTypes,
-};
-
-function WorkspaceReimbursePage(props) {
- return (
-
- {() => }
-
- );
-}
-
-WorkspaceReimbursePage.propTypes = propTypes;
-WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage';
-
-export default compose(withPolicy, withLocalize)(WorkspaceReimbursePage);
diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx
new file mode 100644
index 000000000000..3ca99147a83b
--- /dev/null
+++ b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx
@@ -0,0 +1,33 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import useLocalize from '@hooks/useLocalize';
+import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import withPolicy from '@pages/workspace/withPolicy';
+import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
+import CONST from '@src/CONST';
+import type SCREENS from '@src/SCREENS';
+import WorkspaceReimburseView from './WorkspaceReimburseView';
+
+type WorkspaceReimbursePageProps = WithPolicyProps & StackScreenProps;
+
+function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) {
+ const {translate} = useLocalize();
+
+ return (
+
+ {() => }
+
+ );
+}
+
+WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage';
+
+export default withPolicy(WorkspaceReimbursePage);
diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx
similarity index 60%
rename from src/pages/workspace/reimburse/WorkspaceReimburseSection.js
rename to src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx
index e8ae0e0958b4..18bd121b3fcb 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx
@@ -1,45 +1,40 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
import ConnectBankAccountButton from '@components/ConnectBankAccountButton';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
-import networkPropTypes from '@components/networkPropTypes';
import Section from '@components/Section';
import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import usePrevious from '@hooks/usePrevious';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import BankAccount from '@libs/models/BankAccount';
-import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes';
import * as Link from '@userActions/Link';
+import type * as OnyxTypes from '@src/types/onyx';
-const propTypes = {
+type WorkspaceReimburseSectionProps = {
/** Policy values needed in the component */
- policy: PropTypes.shape({
- id: PropTypes.string,
- }).isRequired,
+ policy: OnyxEntry;
/** Bank account attached to free plan */
- reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired,
-
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
- /** Returns translated string for given locale and phrase */
- translate: PropTypes.func.isRequired,
+ reimbursementAccount: OnyxEntry;
};
-function WorkspaceReimburseSection(props) {
+function WorkspaceReimburseSection({policy, reimbursementAccount}: WorkspaceReimburseSectionProps) {
const theme = useTheme();
const styles = useThemeStyles();
+ const {translate} = useLocalize();
const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(true);
- const achState = lodashGet(props.reimbursementAccount, 'achData.state', '');
+ const achState = reimbursementAccount?.achData?.state ?? '';
const hasVBA = achState === BankAccount.STATE.OPEN;
- const reimburseReceiptsUrl = `reports?policyID=${props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`;
- const isLoading = lodashGet(props.reimbursementAccount, 'isLoading', false);
+ const policyId = policy?.id ?? '';
+ const reimburseReceiptsUrl = `reports?policyID=${policyId}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`;
+ const isLoading = reimbursementAccount?.isLoading ?? false;
const prevIsLoading = usePrevious(isLoading);
+ const {isOffline} = useNetwork();
useEffect(() => {
if (prevIsLoading === isLoading) {
@@ -48,15 +43,15 @@ function WorkspaceReimburseSection(props) {
setShouldShowLoadingSpinner(isLoading);
}, [prevIsLoading, isLoading]);
- if (props.network.isOffline) {
+ if (isOffline) {
return (
- {`${props.translate('common.youAppearToBeOffline')} ${props.translate('common.thisFeatureRequiresInternet')}`}
+ {`${translate('common.youAppearToBeOffline')} ${translate('common.thisFeatureRequiresInternet')}`}
);
@@ -75,12 +70,12 @@ function WorkspaceReimburseSection(props) {
return hasVBA ? (
Link.openOldDotLink(reimburseReceiptsUrl),
icon: Expensicons.Bank,
shouldShowRightIcon: true,
@@ -91,27 +86,26 @@ function WorkspaceReimburseSection(props) {
]}
>
- {props.translate('workspace.reimburse.fastReimbursementsVBACopy')}
+ {translate('workspace.reimburse.fastReimbursementsVBACopy')}
) : (
- {props.translate('workspace.reimburse.unlockNoVBACopy')}
+ {translate('workspace.reimburse.unlockNoVBACopy')}
);
}
-WorkspaceReimburseSection.propTypes = propTypes;
WorkspaceReimburseSection.displayName = 'WorkspaceReimburseSection';
export default WorkspaceReimburseSection;
diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx
similarity index 56%
rename from src/pages/workspace/reimburse/WorkspaceReimburseView.js
rename to src/pages/workspace/reimburse/WorkspaceReimburseView.tsx
index 636675098d23..9c7b1c493e8d 100644
--- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js
+++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx
@@ -1,87 +1,58 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
+import type {OnyxEntry} from 'react-native-onyx';
import CopyTextToClipboard from '@components/CopyTextToClipboard';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
-import networkPropTypes from '@components/networkPropTypes';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
-import {withNetwork} from '@components/OnyxProvider';
import Section from '@components/Section';
import Text from '@components/Text';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
-import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes';
import * as BankAccounts from '@userActions/BankAccounts';
import * as Link from '@userActions/Link';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
+import type {Unit} from '@src/types/onyx/Policy';
import WorkspaceReimburseSection from './WorkspaceReimburseSection';
-const propTypes = {
- /** Policy values needed in the component */
- policy: PropTypes.shape({
- id: PropTypes.string,
- customUnits: PropTypes.objectOf(
- PropTypes.shape({
- customUnitID: PropTypes.string,
- name: PropTypes.string,
- attributes: PropTypes.shape({
- unit: PropTypes.string,
- }),
- rates: PropTypes.objectOf(
- PropTypes.shape({
- customUnitRateID: PropTypes.string,
- name: PropTypes.string,
- rate: PropTypes.number,
- }),
- ),
- }),
- ),
- outputCurrency: PropTypes.string,
- lastModified: PropTypes.number,
- }).isRequired,
-
- /** From Onyx */
+type WorkspaceReimburseViewOnyxProps = {
/** Bank account attached to free plan */
- reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes,
-
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
- ...withLocalizePropTypes,
+ reimbursementAccount: OnyxEntry;
};
-const defaultProps = {
- reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps,
+type WorkspaceReimburseViewProps = WorkspaceReimburseViewOnyxProps & {
+ /** Policy values needed in the component */
+ policy: OnyxEntry;
};
-function WorkspaceReimburseView(props) {
+function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimburseViewProps) {
const styles = useThemeStyles();
- const [currentRatePerUnit, setCurrentRatePerUnit] = useState('');
+ const [currentRatePerUnit, setCurrentRatePerUnit] = useState('');
const {isSmallScreenWidth} = useWindowDimensions();
- const viewAllReceiptsUrl = `expenses?policyIDList=${props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`;
- const distanceCustomUnit = _.find(lodashGet(props.policy, 'customUnits', {}), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
- const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
- const {translate, toLocaleDigit} = props;
+ const viewAllReceiptsUrl = `expenses?policyIDList=${policy?.id ?? ''}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`;
+ const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
+ const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
+ const {translate, toLocaleDigit} = useLocalize();
+ const {isOffline} = useNetwork();
- const getUnitLabel = useCallback((value) => translate(`common.${value}`), [translate]);
+ const getUnitLabel = useCallback((value: Unit): string => translate(`common.${value}`), [translate]);
const getCurrentRatePerUnitLabel = useCallback(() => {
- const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', '{}'), (rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
- const currentUnit = getUnitLabel(lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES));
- const currentRate = PolicyUtils.getUnitRateValue(customUnitRate, toLocaleDigit);
+ const customUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
+ const currentUnit = getUnitLabel(distanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES);
+ const currentRate = PolicyUtils.getUnitRateValue(toLocaleDigit, customUnitRate);
const perWord = translate('common.per');
+
return `${currentRate} ${perWord} ${currentUnit}`;
}, [translate, distanceCustomUnit, toLocaleDigit, getUnitLabel]);
@@ -90,19 +61,19 @@ function WorkspaceReimburseView(props) {
// openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in
// updating Onyx with the optimistic data.
BankAccounts.setReimbursementAccountLoading(true);
- Policy.openWorkspaceReimburseView(props.policy.id);
- }, [props.policy.id]);
+ Policy.openWorkspaceReimburseView(policy?.id ?? '');
+ }, [policy?.id]);
useEffect(() => {
- if (props.network.isOffline) {
+ if (isOffline) {
return;
}
fetchData();
- }, [props.network.isOffline, fetchData]);
+ }, [isOffline, fetchData]);
useEffect(() => {
setCurrentRatePerUnit(getCurrentRatePerUnitLabel());
- }, [props.policy.customUnits, getCurrentRatePerUnitLabel]);
+ }, [policy?.customUnits, getCurrentRatePerUnitLabel]);
return (
@@ -117,7 +88,7 @@ function WorkspaceReimburseView(props) {
icon: Expensicons.Receipt,
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
- wrapperStyle: [styles.cardMenuItem],
+ wrapperStyle: styles.cardMenuItem,
link: () => Link.buildOldDotURL(viewAllReceiptsUrl),
},
]}
@@ -127,7 +98,7 @@ function WorkspaceReimburseView(props) {
{translate('workspace.reimburse.captureNoVBACopyBeforeEmail')}
{translate('workspace.reimburse.captureNoVBACopyAfterEmail')}
@@ -142,7 +113,7 @@ function WorkspaceReimburseView(props) {
{translate('workspace.reimburse.trackDistanceCopy')}
@@ -151,36 +122,29 @@ function WorkspaceReimburseView(props) {
description={translate('workspace.reimburse.trackDistanceRate')}
shouldShowRightIcon
onPress={() => {
- Policy.setPolicyIDForReimburseView(props.policy.id);
- Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy.id));
+ Policy.setPolicyIDForReimburseView(policy?.id ?? '');
+ Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(policy?.id ?? ''));
}}
wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3]}
- brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR}
+ brickRoadIndicator={(distanceCustomUnit?.errors ?? distanceCustomRate?.errors) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR}
/>
);
}
-WorkspaceReimburseView.defaultProps = defaultProps;
-WorkspaceReimburseView.propTypes = propTypes;
WorkspaceReimburseView.displayName = 'WorkspaceReimburseView';
-export default compose(
- withLocalize,
- withNetwork(),
- withOnyx({
- reimbursementAccount: {
- key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
- },
- }),
-)(WorkspaceReimburseView);
+export default withOnyx({
+ // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM
+ reimbursementAccount: {
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+ },
+})(WorkspaceReimburseView);
diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
index a355cc062f3d..f764f8d8e987 100644
--- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx
+++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
@@ -264,6 +264,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
style={[styles.defaultModalContainer]}
testID={WorkspaceTagsPage.displayName}
shouldShowOfflineIndicatorInWideScreen
+ offlineIndicatorStyle={styles.mtAuto}
>
)}
- {tagList.length > 0 && (
+ {tagList.length > 0 && !isLoading && (
%}
+ ref={inputCallbackRef}
/>
diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
index 5bcb631c21b0..db59980a2cf4 100644
--- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
+++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
@@ -1,6 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
-import {FlatList, View} from 'react-native';
+import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import ConfirmModal from '@components/ConfirmModal';
@@ -243,8 +243,11 @@ function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, ses
session?.accountID,
]);
- const renderOptionItem = ({item}: {item: ToggleSettingOptionRowProps}) => (
-
+ const renderOptionItem = (item: ToggleSettingOptionRowProps, index: number) => (
+ {translate('workflowsPage.workflowDescription')}
- item.title}
- />
+ {optionItems.map(renderOptionItem)}
;
/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
-export default {
+const story: ComponentMeta = {
title: 'Components/AddressSearch',
component: AddressSearch,
args: {
@@ -15,25 +20,26 @@ export default {
},
};
-function Template(args) {
- const [value, setValue] = useState('');
+function Template(props: AddressSearchProps) {
+ const [value, setValue] = useState('');
return (
setValue(street)}
+ value={value as string}
+ onInputChange={(inputValue) => setValue(inputValue)}
// eslint-disable-next-line react/jsx-props-no-spreading
- {...args}
+ {...props}
/>
);
}
// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Default = Template.bind({});
+const Default: AddressSearchStory = Template.bind({});
-const ErrorStory = Template.bind({});
+const ErrorStory: AddressSearchStory = Template.bind({});
ErrorStory.args = {
errorText: 'The street you are looking for does not exist',
};
+export default story;
export {Default, ErrorStory};
diff --git a/src/stories/Banner.stories.js b/src/stories/Banner.stories.tsx
similarity index 72%
rename from src/stories/Banner.stories.js
rename to src/stories/Banner.stories.tsx
index 3a6f454843d1..9328e3d513ab 100644
--- a/src/stories/Banner.stories.js
+++ b/src/stories/Banner.stories.tsx
@@ -1,6 +1,10 @@
+import type {ComponentStory} from '@storybook/react';
import React from 'react';
+import type {BannerProps} from '@components/Banner';
import Banner from '@components/Banner';
+type BannerStory = ComponentStory;
+
/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
@@ -11,25 +15,25 @@ const story = {
component: Banner,
};
-function Template(args) {
+function Template(props: BannerProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
- return ;
+ return ;
}
// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const InfoBanner = Template.bind({});
+const InfoBanner: BannerStory = Template.bind({});
InfoBanner.args = {
text: 'This is an informational banner',
};
-const HTMLBanner = Template.bind({});
+const HTMLBanner: BannerStory = Template.bind({});
HTMLBanner.args = {
text: 'This is a informational banner containing HTML',
shouldRenderHTML: true,
};
-const BannerWithLink = Template.bind({});
+const BannerWithLink: BannerStory = Template.bind({});
BannerWithLink.args = {
text: 'This is a informational banner containing internal Link and public link',
shouldRenderHTML: true,
diff --git a/src/stories/Button.stories.js b/src/stories/Button.stories.tsx
similarity index 79%
rename from src/stories/Button.stories.js
rename to src/stories/Button.stories.tsx
index 2bf254b9f382..3e094b0c65bf 100644
--- a/src/stories/Button.stories.js
+++ b/src/stories/Button.stories.tsx
@@ -1,29 +1,33 @@
/* eslint-disable react/jsx-props-no-spreading */
+import type {ComponentMeta, ComponentStory} from '@storybook/react';
import React, {useCallback, useState} from 'react';
import {View} from 'react-native';
+import type {ButtonProps} from '@components/Button';
import Button from '@components/Button';
import Text from '@components/Text';
+type ButtonStory = ComponentStory;
+
/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
-const story = {
+const story: ComponentMeta = {
title: 'Components/Button',
component: Button,
};
-function Template(args) {
+function Template(props: ButtonProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
- return ;
+ return ;
}
// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Default = Template.bind({});
-const Loading = Template.bind({});
-function PressOnEnter(props) {
+const Default: ButtonStory = Template.bind({});
+const Loading: ButtonStory = Template.bind({});
+function PressOnEnter(props: ButtonProps) {
const [text, setText] = useState('');
const onPress = useCallback(() => {
setText('Button Pressed!');
@@ -33,13 +37,13 @@ function PressOnEnter(props) {
);
}
-function PressOnEnterWithBubbling(props) {
+function PressOnEnterWithBubbling(props: ButtonProps) {
return (
<>
Both buttons will trigger on press of Enter as the Enter event will bubble across all instances of button.
diff --git a/src/stories/ButtonWithDropdownMenu.stories.js b/src/stories/ButtonWithDropdownMenu.stories.tsx
similarity index 51%
rename from src/stories/ButtonWithDropdownMenu.stories.js
rename to src/stories/ButtonWithDropdownMenu.stories.tsx
index b87bdc321d45..dd7d8a783aaf 100644
--- a/src/stories/ButtonWithDropdownMenu.stories.js
+++ b/src/stories/ButtonWithDropdownMenu.stories.tsx
@@ -1,5 +1,10 @@
+import type {ComponentStory} from '@storybook/react';
import React from 'react';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
+import type {ButtonWithDropdownMenuProps} from '@components/ButtonWithDropdownMenu/types';
+import * as Expensicons from '@components/Icon/Expensicons';
+
+type ButtonWithDropdownMenuStory = ComponentStory;
/**
* We use the Component Story Format for writing stories. Follow the docs here:
@@ -11,23 +16,23 @@ const story = {
component: ButtonWithDropdownMenu,
};
-function Template(args) {
+function Template(props: ButtonWithDropdownMenuProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
- return ;
+ return ;
}
// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Default = Template.bind({});
+const Default: ButtonWithDropdownMenuStory = Template.bind({});
Default.args = {
- buttonText: 'Pay using Expensify',
+ customText: 'Pay using Expensify',
onPress: (e, item) => {
- alert(`Button ${item} is pressed.`);
+ alert(`Button ${item as string} is pressed.`);
},
pressOnEnter: true,
options: [
- {value: 'One', text: 'One'},
- {value: 'Two', text: 'Two'},
+ {value: 'One', text: 'One', icon: Expensicons.Wallet},
+ {value: 'Two', text: 'Two', icon: Expensicons.Wallet},
],
};
diff --git a/src/stories/HeaderWithBackButton.stories.tsx b/src/stories/HeaderWithBackButton.stories.tsx
index 8306d8e19225..ca723715d5f0 100644
--- a/src/stories/HeaderWithBackButton.stories.tsx
+++ b/src/stories/HeaderWithBackButton.stories.tsx
@@ -2,25 +2,22 @@ import type {ComponentMeta, ComponentStory} from '@storybook/react';
import React from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/types';
-import withNavigationFallback from '@components/withNavigationFallback';
-const HeaderWithBackButtonWithNavigation = withNavigationFallback(HeaderWithBackButton);
-
-type HeaderWithBackButtonStory = ComponentStory;
+type HeaderWithBackButtonStory = ComponentStory;
/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
-const story: ComponentMeta = {
+const story: ComponentMeta = {
title: 'Components/HeaderWithBackButton',
- component: HeaderWithBackButtonWithNavigation,
+ component: HeaderWithBackButton,
};
function Template(props: HeaderWithBackButtonProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
- return ;
+ return ;
}
// Arguments can be passed to the component by binding
diff --git a/src/styles/index.ts b/src/styles/index.ts
index b0c88d151484..120b848bd5a4 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -4103,9 +4103,12 @@ const styles = (theme: ThemeColors) =>
gap: 16,
},
+ reportActionItemImagesContainer: {
+ margin: 4,
+ },
+
reportActionItemImages: {
flexDirection: 'row',
- margin: 4,
borderRadius: 12,
overflow: 'hidden',
height: variables.reportActionImagesSingleImageHeight,
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 4c66967f52b9..fa4c19539072 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -755,7 +755,7 @@ function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean, isMon
if (isSmallScreenWidth) {
return {
height: emptyStateBackground.SMALL_SCREEN.IMAGE_HEIGHT,
- width: '200%',
+ width: '100%',
position: 'absolute',
};
}
diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js
deleted file mode 100644
index 5e9efcc00617..000000000000
--- a/tests/actions/IOUTest.js
+++ /dev/null
@@ -1,2978 +0,0 @@
-import Onyx from 'react-native-onyx';
-import _ from 'underscore';
-import CONST from '../../src/CONST';
-import * as IOU from '../../src/libs/actions/IOU';
-import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager';
-import * as PolicyActions from '../../src/libs/actions/Policy';
-import * as Report from '../../src/libs/actions/Report';
-import * as ReportActions from '../../src/libs/actions/ReportActions';
-import * as User from '../../src/libs/actions/User';
-import DateUtils from '../../src/libs/DateUtils';
-import Navigation from '../../src/libs/Navigation/Navigation';
-import * as NumberUtils from '../../src/libs/NumberUtils';
-import * as PersonalDetailsUtils from '../../src/libs/PersonalDetailsUtils';
-import * as ReportActionsUtils from '../../src/libs/ReportActionsUtils';
-import * as ReportUtils from '../../src/libs/ReportUtils';
-import ONYXKEYS from '../../src/ONYXKEYS';
-import ROUTES from '../../src/ROUTES';
-import PusherHelper from '../utils/PusherHelper';
-import * as TestHelper from '../utils/TestHelper';
-import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
-import waitForNetworkPromises from '../utils/waitForNetworkPromises';
-
-jest.mock('../../src/libs/Navigation/Navigation', () => ({
- navigate: jest.fn(),
- dismissModal: jest.fn(),
- dismissModalWithReport: jest.fn(),
- goBack: jest.fn(),
-}));
-
-const CARLOS_EMAIL = 'cmartins@expensifail.com';
-const CARLOS_ACCOUNT_ID = 1;
-const JULES_EMAIL = 'jules@expensifail.com';
-const JULES_ACCOUNT_ID = 2;
-const RORY_EMAIL = 'rory@expensifail.com';
-const RORY_ACCOUNT_ID = 3;
-const VIT_EMAIL = 'vit@expensifail.com';
-const VIT_ACCOUNT_ID = 4;
-
-OnyxUpdateManager();
-describe('actions/IOU', () => {
- beforeAll(() => {
- Onyx.init({
- keys: ONYXKEYS,
- });
- });
-
- beforeEach(() => {
- global.fetch = TestHelper.getGlobalFetchMock();
- return Onyx.clear().then(waitForBatchedUpdates);
- });
-
- describe('requestMoney', () => {
- it('creates new chat if needed', () => {
- const amount = 10000;
- const comment = 'Giv money plz';
- const merchant = 'KFC';
- let iouReportID;
- let createdAction;
- let iouAction;
- let transactionID;
- let transactionThread;
- let transactionThreadCreatedAction;
- fetch.pause();
- IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- // A chat report, a transaction thread, and an iou report should be created
- const chatReports = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT);
- const iouReports = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- expect(_.size(chatReports)).toBe(2);
- expect(_.size(iouReports)).toBe(1);
- const chatReport = chatReports[0];
- const transactionThreadReport = chatReports[1];
- const iouReport = iouReports[0];
- iouReportID = iouReport.reportID;
- transactionThread = transactionThreadReport;
-
- expect(iouReport.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- // They should be linked together
- expect(chatReport.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]);
- expect(chatReport.iouReportID).toBe(iouReport.reportID);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
-
- // The IOU report should have a CREATED action and IOU action
- expect(_.size(reportActionsForIOUReport)).toBe(2);
- const createdActions = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- const iouActions = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(_.size(createdActions)).toBe(1);
- expect(_.size(iouActions)).toBe(1);
- createdAction = createdActions[0];
- iouAction = iouActions[0];
-
- // The CREATED action should not be created after the IOU action
- expect(Date.parse(createdAction.created)).toBeLessThan(Date.parse(iouAction.created));
-
- // The IOUReportID should be correct
- expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
-
- // The comment should be included in the IOU action
- expect(iouAction.originalMessage.comment).toBe(comment);
-
- // The amount in the IOU action should be correct
- expect(iouAction.originalMessage.amount).toBe(amount);
-
- // The IOU type should be correct
- expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
-
- // Both actions should be pending
- expect(createdAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(iouAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForTransactionThread) => {
- Onyx.disconnect(connectionID);
-
- // The transaction thread should have a CREATED action
- expect(_.size(reportActionsForTransactionThread)).toBe(1);
- const createdActions = _.filter(reportActionsForTransactionThread, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- expect(_.size(createdActions)).toBe(1);
- transactionThreadCreatedAction = createdActions[0];
-
- expect(transactionThreadCreatedAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- // There should be one transaction
- expect(_.size(allTransactions)).toBe(1);
- const transaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- transactionID = transaction.transactionID;
-
- // The transaction should be attached to the IOU report
- expect(transaction.reportID).toBe(iouReportID);
-
- // Its amount should match the amount of the request
- expect(transaction.amount).toBe(amount);
-
- // The comment should be correct
- expect(transaction.comment.comment).toBe(comment);
-
- // It should be pending
- expect(transaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- // The transactionID on the iou action should match the one from the transactions collection
- expect(iouAction.originalMessage.IOUTransactionID).toBe(transactionID);
-
- expect(transaction.merchant).toBe(merchant);
-
- resolve();
- },
- });
- }),
- )
- .then(fetch.resume)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(_.size(reportActionsForIOUReport)).toBe(2);
- _.each(reportActionsForIOUReport, (reportAction) => expect(reportAction.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
- waitForCollectionCallback: true,
- callback: (transaction) => {
- Onyx.disconnect(connectionID);
- expect(transaction.pendingAction).toBeFalsy();
- resolve();
- },
- });
- }),
- );
- });
-
- it('updates existing chat report if there is one', () => {
- const amount = 10000;
- const comment = 'Giv money plz';
- let chatReport = {
- reportID: 1234,
- type: CONST.REPORT.TYPE.CHAT,
- participantAccountIDs: [CARLOS_ACCOUNT_ID],
- };
- const createdAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- created: DateUtils.getDBTime(),
- };
- let iouReportID;
- let iouAction;
- let iouCreatedAction;
- let transactionID;
- fetch.pause();
- return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport)
- .then(() =>
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, {
- [createdAction.reportActionID]: createdAction,
- }),
- )
- .then(() => {
- IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- // The same chat report should be reused, a transaction thread and an IOU report should be created
- expect(_.size(allReports)).toBe(3);
- expect(_.find(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT).reportID).toBe(chatReport.reportID);
- chatReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT);
- const iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- iouReportID = iouReport.reportID;
-
- expect(iouReport.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- // They should be linked together
- expect(chatReport.iouReportID).toBe(iouReportID);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (allIOUReportActions) => {
- Onyx.disconnect(connectionID);
-
- // The chat report should have a CREATED and an IOU action
- expect(_.size(allIOUReportActions)).toBe(2);
- iouCreatedAction = _.find(allIOUReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- iouAction = _.find(allIOUReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
-
- // The CREATED action should not be created after the IOU action
- expect(Date.parse(iouCreatedAction.created)).toBeLessThan(Date.parse(iouAction.created));
-
- // The IOUReportID should be correct
- expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
-
- // The comment should be included in the IOU action
- expect(iouAction.originalMessage.comment).toBe(comment);
-
- // The amount in the IOU action should be correct
- expect(iouAction.originalMessage.amount).toBe(amount);
-
- // The IOU action type should be correct
- expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
-
- // The IOU action should be pending
- expect(iouAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- // There should be one transaction
- expect(_.size(allTransactions)).toBe(1);
- const transaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- transactionID = transaction.transactionID;
-
- // The transaction should be attached to the IOU report
- expect(transaction.reportID).toBe(iouReportID);
-
- // Its amount should match the amount of the request
- expect(transaction.amount).toBe(amount);
-
- // The comment should be correct
- expect(transaction.comment.comment).toBe(comment);
-
- expect(transaction.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
-
- // It should be pending
- expect(transaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- // The transactionID on the iou action should match the one from the transactions collection
- expect(iouAction.originalMessage.IOUTransactionID).toBe(transactionID);
-
- resolve();
- },
- });
- }),
- )
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(_.size(reportActionsForIOUReport)).toBe(2);
- _.each(reportActionsForIOUReport, (reportAction) => expect(reportAction.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
- waitForCollectionCallback: true,
- callback: (transaction) => {
- Onyx.disconnect(connectionID);
- expect(transaction.pendingAction).toBeFalsy();
- resolve();
- },
- });
- }),
- );
- });
-
- it('updates existing IOU report if there is one', () => {
- const amount = 10000;
- const comment = 'Giv money plz';
- const chatReportID = 1234;
- const iouReportID = 5678;
- let chatReport = {
- reportID: chatReportID,
- type: CONST.REPORT.TYPE.CHAT,
- iouReportID,
- participantAccountIDs: [CARLOS_ACCOUNT_ID],
- };
- const createdAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- created: DateUtils.getDBTime(),
- };
- const existingTransaction = {
- transactionID: NumberUtils.rand64(),
- amount: 1000,
- comment: '',
- created: DateUtils.getDBTime(),
- };
- let iouReport = {
- reportID: iouReportID,
- chatReportID,
- type: CONST.REPORT.TYPE.IOU,
- ownerAccountID: RORY_ACCOUNT_ID,
- managerID: CARLOS_ACCOUNT_ID,
- currency: CONST.CURRENCY.USD,
- total: existingTransaction.amount,
- };
- const iouAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
- actorAccountID: RORY_ACCOUNT_ID,
- created: DateUtils.getDBTime(),
- originalMessage: {
- IOUReportID: iouReportID,
- IOUTransactionID: existingTransaction.transactionID,
- amount: existingTransaction.amount,
- currency: CONST.CURRENCY.USD,
- type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
- participantAccountIDs: [RORY_ACCOUNT_ID, CARLOS_ACCOUNT_ID],
- },
- };
- let newIOUAction;
- let newTransaction;
- fetch.pause();
- return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport)
- .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, iouReport))
- .then(() =>
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {
- [createdAction.reportActionID]: createdAction,
- [iouAction.reportActionID]: iouAction,
- }),
- )
- .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransaction.transactionID}`, existingTransaction))
- .then(() => {
- IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- // No new reports should be created
- expect(_.size(allReports)).toBe(3);
- expect(_.find(allReports, (report) => report.reportID === chatReportID)).toBeTruthy();
- expect(_.find(allReports, (report) => report.reportID === iouReportID)).toBeTruthy();
-
- chatReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT);
- iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
-
- // The total on the iou report should be updated
- expect(iouReport.total).toBe(11000);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
-
- expect(_.size(reportActionsForIOUReport)).toBe(3);
- newIOUAction = _.find(
- reportActionsForIOUReport,
- (reportAction) => reportAction.reportActionID !== createdAction.reportActionID && reportAction.reportActionID !== iouAction.reportActionID,
- );
-
- // The IOUReportID should be correct
- expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
-
- // The comment should be included in the IOU action
- expect(newIOUAction.originalMessage.comment).toBe(comment);
-
- // The amount in the IOU action should be correct
- expect(newIOUAction.originalMessage.amount).toBe(amount);
-
- // The type of the IOU action should be correct
- expect(newIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
-
- // The IOU action should be pending
- expect(newIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- // There should be two transactions
- expect(_.size(allTransactions)).toBe(2);
-
- newTransaction = _.find(allTransactions, (transaction) => transaction.transactionID !== existingTransaction.transactionID);
-
- expect(newTransaction.reportID).toBe(iouReportID);
- expect(newTransaction.amount).toBe(amount);
- expect(newTransaction.comment.comment).toBe(comment);
- expect(newTransaction.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
- expect(newTransaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- // The transactionID on the iou action should match the one from the transactions collection
- expect(newIOUAction.originalMessage.IOUTransactionID).toBe(newTransaction.transactionID);
-
- resolve();
- },
- });
- }),
- )
- .then(fetch.resume)
- .then(waitForNetworkPromises)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(_.size(reportActionsForIOUReport)).toBe(3);
- _.each(reportActionsForIOUReport, (reportAction) => expect(reportAction.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- _.each(allTransactions, (transaction) => expect(transaction.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- );
- });
-
- it('correctly implements RedBrickRoad error handling', () => {
- const amount = 10000;
- const comment = 'Giv money plz';
- let chatReportID;
- let iouReportID;
- let createdAction;
- let iouAction;
- let transactionID;
- let transactionThreadReport;
- let transactionThreadAction;
- fetch.pause();
- IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- // A chat report, transaction thread and an iou report should be created
- const chatReports = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT);
- const iouReports = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- expect(_.size(chatReports)).toBe(2);
- expect(_.size(iouReports)).toBe(1);
- const chatReport = chatReports[0];
- chatReportID = chatReport.reportID;
- transactionThreadReport = chatReports[1];
-
- const iouReport = iouReports[0];
- iouReportID = iouReport.reportID;
-
- expect(iouReport.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- // They should be linked together
- expect(chatReport.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]);
- expect(chatReport.iouReportID).toBe(iouReport.reportID);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
-
- // The chat report should have a CREATED action and IOU action
- expect(_.size(reportActionsForIOUReport)).toBe(2);
- const createdActions = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- const iouActions = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(_.size(createdActions)).toBe(1);
- expect(_.size(iouActions)).toBe(1);
- createdAction = createdActions[0];
- iouAction = iouActions[0];
-
- // The CREATED action should not be created after the IOU action
- expect(Date.parse(createdAction.created)).toBeLessThan(Date.parse(iouAction.created));
-
- // The IOUReportID should be correct
- expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
-
- // The comment should be included in the IOU action
- expect(iouAction.originalMessage.comment).toBe(comment);
-
- // The amount in the IOU action should be correct
- expect(iouAction.originalMessage.amount).toBe(amount);
-
- // The type should be correct
- expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
-
- // Both actions should be pending
- expect(createdAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(iouAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- // There should be one transaction
- expect(_.size(allTransactions)).toBe(1);
- const transaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- transactionID = transaction.transactionID;
-
- expect(transaction.reportID).toBe(iouReportID);
- expect(transaction.amount).toBe(amount);
- expect(transaction.comment.comment).toBe(comment);
- expect(transaction.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
- expect(transaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- // The transactionID on the iou action should match the one from the transactions collection
- expect(iouAction.originalMessage.IOUTransactionID).toBe(transactionID);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- fetch.fail();
- return fetch.resume();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(_.size(reportActionsForIOUReport)).toBe(2);
- iouAction = _.find(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(iouAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForTransactionThread) => {
- Onyx.disconnect(connectionID);
- expect(_.size(reportActionsForTransactionThread)).toBe(3);
- transactionThreadAction = _.find(
- reportActionsForTransactionThread[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport.reportID}`],
- (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
- );
- expect(transactionThreadAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
- waitForCollectionCallback: true,
- callback: (transaction) => {
- Onyx.disconnect(connectionID);
- expect(transaction.pendingAction).toBeFalsy();
- expect(transaction.errors).toBeTruthy();
- expect(_.values(transaction.errors)[0]).toEqual(expect.arrayContaining(['iou.error.genericCreateFailureMessage', {isTranslated: false}]));
- resolve();
- },
- });
- }),
- )
-
- // If the user clears the errors on the IOU action
- .then(
- () =>
- new Promise((resolve) => {
- ReportActions.clearReportActionErrors(iouReportID, iouAction);
- ReportActions.clearReportActionErrors(transactionThreadReport.reportID, transactionThreadAction);
- resolve();
- }),
- )
-
- // Then the reportAction should be removed from Onyx
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForReport) => {
- Onyx.disconnect(connectionID);
- iouAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(iouAction).toBeFalsy();
- resolve();
- },
- });
- }),
- )
-
- // Along with the associated transaction
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
- waitForCollectionCallback: true,
- callback: (transaction) => {
- Onyx.disconnect(connectionID);
- expect(transaction).toBeFalsy();
- resolve();
- },
- });
- }),
- )
-
- // If a user clears the errors on the CREATED action (which, technically are just errors on the report)
- .then(
- () =>
- new Promise((resolve) => {
- Report.deleteReport(chatReportID);
- Report.deleteReport(transactionThreadReport.reportID);
- resolve();
- }),
- )
-
- // Then the report should be deleted
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- _.each(allReports, (report) => expect(report).toBeFalsy());
- resolve();
- },
- });
- }),
- )
-
- // All reportActions should also be deleted
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
- _.each(allReportActions, (reportAction) => expect(reportAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
-
- // All transactions should also be deleted
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- _.each(allTransactions, (transaction) => expect(transaction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
-
- // Cleanup
- .then(fetch.succeed)
- );
- });
- });
-
- describe('split bill', () => {
- it('creates and updates new chats and IOUs as needed', () => {
- jest.setTimeout(10 * 1000);
- /*
- * Given that:
- * - Rory and Carlos have chatted before
- * - Rory and Jules have chatted before and have an active IOU report
- * - Rory and Vit have never chatted together before
- * - There is no existing group chat with the four of them
- */
- const amount = 400;
- const comment = 'Yes, I am splitting a bill for $4 USD';
- const merchant = 'Yema Kitchen';
- let carlosChatReport = {
- reportID: NumberUtils.rand64(),
- type: CONST.REPORT.TYPE.CHAT,
- participantAccountIDs: [CARLOS_ACCOUNT_ID],
- };
- const carlosCreatedAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- created: DateUtils.getDBTime(),
- };
- const julesIOUReportID = NumberUtils.rand64();
- let julesChatReport = {
- reportID: NumberUtils.rand64(),
- type: CONST.REPORT.TYPE.CHAT,
- iouReportID: julesIOUReportID,
- participantAccountIDs: [JULES_ACCOUNT_ID],
- };
- const julesChatCreatedAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- created: DateUtils.getDBTime(),
- };
- const julesCreatedAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- created: DateUtils.getDBTime(),
- };
- jest.advanceTimersByTime(200);
- const julesExistingTransaction = {
- transactionID: NumberUtils.rand64(),
- amount: 1000,
- comment: 'This is an existing transaction',
- created: DateUtils.getDBTime(),
- };
- let julesIOUReport = {
- reportID: julesIOUReportID,
- chatReportID: julesChatReport.reportID,
- type: CONST.REPORT.TYPE.IOU,
- ownerAccountID: RORY_ACCOUNT_ID,
- managerID: JULES_ACCOUNT_ID,
- currency: CONST.CURRENCY.USD,
- total: julesExistingTransaction.amount,
- };
- const julesExistingIOUAction = {
- reportActionID: NumberUtils.rand64(),
- actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
- actorAccountID: RORY_ACCOUNT_ID,
- created: DateUtils.getDBTime(),
- originalMessage: {
- IOUReportID: julesIOUReportID,
- IOUTransactionID: julesExistingTransaction.transactionID,
- amount: julesExistingTransaction.amount,
- currency: CONST.CURRENCY.USD,
- type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
- participantAccountIDs: [RORY_ACCOUNT_ID, JULES_ACCOUNT_ID],
- },
- };
-
- let carlosIOUReport;
- let carlosIOUAction;
- let carlosIOUCreatedAction;
- let carlosTransaction;
-
- let julesIOUAction;
- let julesIOUCreatedAction;
- let julesTransaction;
-
- let vitChatReport;
- let vitIOUReport;
- let vitCreatedAction;
- let vitIOUAction;
- let vitTransaction;
-
- let groupChat;
- let groupCreatedAction;
- let groupIOUAction;
- let groupTransaction;
-
- return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
- [`${ONYXKEYS.COLLECTION.REPORT}${carlosChatReport.reportID}`]: carlosChatReport,
- [`${ONYXKEYS.COLLECTION.REPORT}${julesChatReport.reportID}`]: julesChatReport,
- [`${ONYXKEYS.COLLECTION.REPORT}${julesIOUReport.reportID}`]: julesIOUReport,
- })
- .then(() =>
- Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
- [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${carlosChatReport.reportID}`]: {
- [carlosCreatedAction.reportActionID]: carlosCreatedAction,
- },
- [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${julesChatReport.reportID}`]: {
- [julesChatCreatedAction.reportActionID]: julesChatCreatedAction,
- },
- [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${julesIOUReport.reportID}`]: {
- [julesCreatedAction.reportActionID]: julesCreatedAction,
- [julesExistingIOUAction.reportActionID]: julesExistingIOUAction,
- },
- }),
- )
- .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction.transactionID}`, julesExistingTransaction))
- .then(() => {
- // When we split a bill offline
- fetch.pause();
- IOU.splitBill(
- // TODO: Migrate after the backend accepts accountIDs
- _.map(
- [
- [CARLOS_EMAIL, CARLOS_ACCOUNT_ID],
- [JULES_EMAIL, JULES_ACCOUNT_ID],
- [VIT_EMAIL, VIT_ACCOUNT_ID],
- ],
- ([email, accountID]) => ({login: email, accountID}),
- ),
- RORY_EMAIL,
- RORY_ACCOUNT_ID,
- amount,
- comment,
- CONST.CURRENCY.USD,
- merchant,
- );
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- // There should now be 10 reports
- expect(_.size(allReports)).toBe(10);
-
- // 1. The chat report with Rory + Carlos
- carlosChatReport = _.find(allReports, (report) => report.reportID === carlosChatReport.reportID);
- expect(_.isEmpty(carlosChatReport)).toBe(false);
- expect(carlosChatReport.pendingFields).toBeFalsy();
-
- // 2. The IOU report with Rory + Carlos (new)
- carlosIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU && report.managerID === CARLOS_ACCOUNT_ID);
- expect(_.isEmpty(carlosIOUReport)).toBe(false);
- expect(carlosIOUReport.total).toBe(amount / 4);
-
- // 3. The chat report with Rory + Jules
- julesChatReport = _.find(allReports, (report) => report.reportID === julesChatReport.reportID);
- expect(_.isEmpty(julesChatReport)).toBe(false);
- expect(julesChatReport.pendingFields).toBeFalsy();
-
- // 4. The IOU report with Rory + Jules
- julesIOUReport = _.find(allReports, (report) => report.reportID === julesIOUReport.reportID);
- expect(_.isEmpty(julesIOUReport)).toBe(false);
- expect(julesChatReport.pendingFields).toBeFalsy();
- expect(julesIOUReport.total).toBe(julesExistingTransaction.amount + amount / 4);
-
- // 5. The chat report with Rory + Vit (new)
- vitChatReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT && _.isEqual(report.participantAccountIDs, [VIT_ACCOUNT_ID]));
- expect(_.isEmpty(vitChatReport)).toBe(false);
- expect(vitChatReport.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
-
- // 6. The IOU report with Rory + Vit (new)
- vitIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU && report.managerID === VIT_ACCOUNT_ID);
- expect(_.isEmpty(vitIOUReport)).toBe(false);
- expect(vitIOUReport.total).toBe(amount / 4);
-
- // 7. The group chat with everyone
- groupChat = _.find(
- allReports,
- (report) => report.type === CONST.REPORT.TYPE.CHAT && _.isEqual(report.participantAccountIDs, [CARLOS_ACCOUNT_ID, JULES_ACCOUNT_ID, VIT_ACCOUNT_ID]),
- );
- expect(_.isEmpty(groupChat)).toBe(false);
- expect(groupChat.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
-
- // The 1:1 chat reports and the IOU reports should be linked together
- expect(carlosChatReport.iouReportID).toBe(carlosIOUReport.reportID);
- expect(carlosIOUReport.chatReportID).toBe(carlosChatReport.reportID);
- expect(carlosIOUReport.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- expect(julesChatReport.iouReportID).toBe(julesIOUReport.reportID);
- expect(julesIOUReport.chatReportID).toBe(julesChatReport.reportID);
-
- expect(vitChatReport.iouReportID).toBe(vitIOUReport.reportID);
- expect(vitIOUReport.chatReportID).toBe(vitChatReport.reportID);
- expect(carlosIOUReport.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
-
- // There should be reportActions on all 7 chat reports + 3 IOU reports in each 1:1 chat
- expect(_.size(allReportActions)).toBe(10);
-
- const carlosReportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${carlosChatReport.iouReportID}`];
- const julesReportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${julesChatReport.iouReportID}`];
- const vitReportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${vitChatReport.iouReportID}`];
- const groupReportActions = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChat.reportID}`];
-
- // Carlos DM should have two reportActions – the existing CREATED action and a pending IOU action
- expect(_.size(carlosReportActions)).toBe(2);
- carlosIOUCreatedAction = _.find(carlosReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- carlosIOUAction = _.find(carlosReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(carlosIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(carlosIOUAction.originalMessage.IOUReportID).toBe(carlosIOUReport.reportID);
- expect(carlosIOUAction.originalMessage.amount).toBe(amount / 4);
- expect(carlosIOUAction.originalMessage.comment).toBe(comment);
- expect(carlosIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- expect(Date.parse(carlosIOUCreatedAction.created)).toBeLessThan(Date.parse(carlosIOUAction.created));
-
- // Jules DM should have three reportActions, the existing CREATED action, the existing IOU action, and a new pending IOU action
- expect(_.size(julesReportActions)).toBe(3);
- expect(julesReportActions[julesCreatedAction.reportActionID]).toStrictEqual(julesCreatedAction);
- julesIOUCreatedAction = _.find(julesReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- julesIOUAction = _.find(
- julesReportActions,
- (reportAction) =>
- reportAction.reportActionID !== julesCreatedAction.reportActionID && reportAction.reportActionID !== julesExistingIOUAction.reportActionID,
- );
- expect(julesIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(julesIOUAction.originalMessage.IOUReportID).toBe(julesIOUReport.reportID);
- expect(julesIOUAction.originalMessage.amount).toBe(amount / 4);
- expect(julesIOUAction.originalMessage.comment).toBe(comment);
- expect(julesIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- expect(Date.parse(julesIOUCreatedAction.created)).toBeLessThan(Date.parse(julesIOUAction.created));
-
- // Vit DM should have two reportActions – a pending CREATED action and a pending IOU action
- expect(_.size(vitReportActions)).toBe(2);
- vitCreatedAction = _.find(vitReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- vitIOUAction = _.find(vitReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(vitCreatedAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(vitIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(vitIOUAction.originalMessage.IOUReportID).toBe(vitIOUReport.reportID);
- expect(vitIOUAction.originalMessage.amount).toBe(amount / 4);
- expect(vitIOUAction.originalMessage.comment).toBe(comment);
- expect(vitIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- expect(Date.parse(vitCreatedAction.created)).toBeLessThan(Date.parse(vitIOUAction.created));
-
- // Group chat should have two reportActions – a pending CREATED action and a pending IOU action w/ type SPLIT
- expect(_.size(groupReportActions)).toBe(2);
- groupCreatedAction = _.find(groupReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED);
- groupIOUAction = _.find(groupReportActions, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(groupCreatedAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(groupIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(groupIOUAction.originalMessage).not.toHaveProperty('IOUReportID');
- expect(groupIOUAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.SPLIT);
- expect(Date.parse(groupCreatedAction.created)).toBeLessThanOrEqual(Date.parse(groupIOUAction.created));
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- /* There should be 5 transactions
- * – one existing one with Jules
- * - one for each of the three IOU reports
- * - one on the group chat w/ deleted report
- */
- expect(_.size(allTransactions)).toBe(5);
- expect(allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction.transactionID}`]).toBeTruthy();
-
- carlosTransaction = _.find(allTransactions, (transaction) => transaction.transactionID === carlosIOUAction.originalMessage.IOUTransactionID);
- julesTransaction = _.find(allTransactions, (transaction) => transaction.transactionID === julesIOUAction.originalMessage.IOUTransactionID);
- vitTransaction = _.find(allTransactions, (transaction) => transaction.transactionID === vitIOUAction.originalMessage.IOUTransactionID);
- groupTransaction = _.find(allTransactions, (transaction) => transaction.reportID === CONST.REPORT.SPLIT_REPORTID);
-
- expect(carlosTransaction.reportID).toBe(carlosIOUReport.reportID);
- expect(julesTransaction.reportID).toBe(julesIOUReport.reportID);
- expect(vitTransaction.reportID).toBe(vitIOUReport.reportID);
- expect(groupTransaction).toBeTruthy();
-
- expect(carlosTransaction.amount).toBe(amount / 4);
- expect(julesTransaction.amount).toBe(amount / 4);
- expect(vitTransaction.amount).toBe(amount / 4);
- expect(groupTransaction.amount).toBe(amount);
-
- expect(carlosTransaction.comment.comment).toBe(comment);
- expect(julesTransaction.comment.comment).toBe(comment);
- expect(vitTransaction.comment.comment).toBe(comment);
- expect(groupTransaction.comment.comment).toBe(comment);
-
- expect(carlosTransaction.merchant).toBe(merchant);
- expect(julesTransaction.merchant).toBe(merchant);
- expect(vitTransaction.merchant).toBe(merchant);
- expect(groupTransaction.merchant).toBe(merchant);
-
- expect(carlosTransaction.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
- expect(julesTransaction.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
- expect(vitTransaction.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
-
- expect(carlosTransaction.comment.originalTransactionID).toBe(groupTransaction.transactionID);
- expect(julesTransaction.comment.originalTransactionID).toBe(groupTransaction.transactionID);
- expect(vitTransaction.comment.originalTransactionID).toBe(groupTransaction.transactionID);
-
- expect(carlosTransaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(julesTransaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(vitTransaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(groupTransaction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- waitForCollectionCallback: true,
- callback: (allPersonalDetails) => {
- Onyx.disconnect(connectionID);
- expect(allPersonalDetails).toMatchObject({
- [VIT_ACCOUNT_ID]: {
- accountID: VIT_ACCOUNT_ID,
- displayName: VIT_EMAIL,
- login: VIT_EMAIL,
- },
- });
- resolve();
- },
- });
- }),
- )
- .then(fetch.resume)
- .then(waitForNetworkPromises)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- _.each(allReports, (report) => {
- if (!report.pendingFields) {
- return;
- }
- _.each(report.pendingFields, (pendingField) => expect(pendingField).toBeFalsy());
- });
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
- _.each(allReportActions, (reportAction) => expect(reportAction.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- _.each(allTransactions, (transaction) => expect(transaction.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- );
- });
- });
-
- describe('payMoneyRequestElsewhere', () => {
- it('clears outstanding IOUReport', () => {
- const amount = 10000;
- const comment = 'Giv money plz';
- let chatReport;
- let iouReport;
- let createIOUAction;
- let payIOUAction;
- let transaction;
- IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- expect(_.size(allReports)).toBe(3);
-
- const chatReports = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT);
- chatReport = chatReports[0];
- expect(chatReport).toBeTruthy();
- expect(chatReport).toHaveProperty('reportID');
- expect(chatReport).toHaveProperty('iouReportID');
-
- iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
-
- expect(chatReport.iouReportID).toBe(iouReport.reportID);
- expect(iouReport.chatReportID).toBe(chatReport.reportID);
-
- expect(chatReport.pendingFields).toBeFalsy();
- expect(iouReport.pendingFields).toBeFalsy();
-
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.SUBMITTED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
-
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
-
- createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction).toBeTruthy();
- expect(createIOUAction.originalMessage.IOUReportID).toBe(iouReport.reportID);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- expect(_.size(allTransactions)).toBe(1);
- transaction = _.find(allTransactions, (t) => t);
- expect(transaction).toBeTruthy();
- expect(transaction.amount).toBe(amount);
- expect(transaction.reportID).toBe(iouReport.reportID);
- expect(createIOUAction.originalMessage.IOUTransactionID).toBe(transaction.transactionID);
- resolve();
- },
- });
- }),
- )
- .then(() => {
- fetch.pause();
- IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- expect(_.size(allReports)).toBe(3);
-
- chatReport = _.find(allReports, (r) => r.type === CONST.REPORT.TYPE.CHAT);
- iouReport = _.find(allReports, (r) => r.type === CONST.REPORT.TYPE.IOU);
-
- expect(chatReport.iouReportID).toBeFalsy();
-
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
-
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`];
- expect(_.size(reportActionsForIOUReport)).toBe(3);
-
- payIOUAction = _.find(
- reportActionsForIOUReport,
- (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && ra.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
- );
- expect(payIOUAction).toBeTruthy();
- expect(payIOUAction.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- .then(fetch.resume)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- expect(_.size(allReports)).toBe(3);
-
- chatReport = _.find(allReports, (r) => r.type === CONST.REPORT.TYPE.CHAT);
- iouReport = _.find(allReports, (r) => r.type === CONST.REPORT.TYPE.IOU);
-
- expect(chatReport.iouReportID).toBeFalsy();
-
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
-
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`];
- expect(_.size(reportActionsForIOUReport)).toBe(3);
-
- payIOUAction = _.find(
- reportActionsForIOUReport,
- (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && ra.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
- );
- expect(payIOUAction).toBeTruthy();
- expect(payIOUAction.pendingAction).toBeFalsy();
-
- resolve();
- },
- });
- }),
- );
- });
- });
-
- describe('edit money request', () => {
- const amount = 10000;
- const comment = '💸💸💸💸';
- const merchant = 'NASDAQ';
-
- afterEach(() => {
- fetch.resume();
- });
-
- it('updates the IOU request and IOU report when offline', () => {
- let thread = {};
- let iouReport = {};
- let iouAction = {};
- let transaction = {};
-
- fetch.pause();
- IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates()
- .then(() => {
- Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID});
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
-
- [iouAction] = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- transaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- resolve();
- },
- });
- }),
- )
- .then(() => {
- thread = ReportUtils.buildTransactionThread(iouAction, iouReport);
- Onyx.set(`report_${thread.reportID}`, thread);
- return waitForBatchedUpdates();
- })
- .then(() => {
- IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'});
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- const updatedTransaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- expect(updatedTransaction.modifiedAmount).toBe(20000);
- expect(updatedTransaction.comment).toMatchObject({comment: 'Double the amount!'});
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (allActions) => {
- Onyx.disconnect(connectionID);
- const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction));
- expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE');
- expect(updatedAction.originalMessage).toEqual(
- expect.objectContaining({amount: 20000, newComment: 'Double the amount!', oldAmount: amount, oldComment: comment}),
- );
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- const updatedChatReport = _.find(allReports, (report) => report.reportID === iouReport.chatReportID);
- expect(updatedIOUReport).toEqual(
- expect.objectContaining({
- total: 20000,
- cachedTotal: '$200.00',
- lastMessageHtml: 'requested $200.00',
- lastMessageText: 'requested $200.00',
- }),
- );
- expect(updatedChatReport).toEqual(
- expect.objectContaining({
- lastMessageHtml: `${CARLOS_EMAIL} owes $200.00`,
- lastMessageText: `${CARLOS_EMAIL} owes $200.00`,
- }),
- );
- resolve();
- },
- });
- }),
- )
- .then(() => {
- fetch.resume();
- });
- });
-
- it('resets the IOU request and IOU report when api returns an error', () => {
- let thread = {};
- let iouReport = {};
- let iouAction = {};
- let transaction = {};
-
- IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates()
- .then(() => {
- Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID});
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- [iouReport] = _.filter(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
-
- [iouAction] = _.filter(reportActionsForIOUReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- transaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- resolve();
- },
- });
- }),
- )
- .then(() => {
- thread = ReportUtils.buildTransactionThread(iouAction, iouReport);
- Onyx.set(`report_${thread.reportID}`, thread);
- return waitForBatchedUpdates();
- })
- .then(() => {
- fetch.fail();
- IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'});
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
-
- const updatedTransaction = _.find(allTransactions, (t) => !_.isEmpty(t));
- expect(updatedTransaction.modifiedAmount).toBe(undefined);
- expect(updatedTransaction.amount).toBe(10000);
- expect(updatedTransaction.comment).toMatchObject({comment});
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (allActions) => {
- Onyx.disconnect(connectionID);
- const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction));
- expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE');
- expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining([['iou.error.genericEditFailureMessage', {isTranslated: false}]]));
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- const updatedChatReport = _.find(allReports, (report) => report.reportID === iouReport.chatReportID);
- expect(updatedIOUReport).toEqual(
- expect.objectContaining({
- total: 10000,
- cachedTotal: '$100.00',
- lastMessageHtml: `requested $${amount / 100}.00 for ${comment}`,
- lastMessageText: `requested $${amount / 100}.00 for ${comment}`,
- }),
- );
- expect(updatedChatReport).toEqual(
- expect.objectContaining({
- lastMessageHtml: '',
- }),
- );
- resolve();
- },
- });
- }),
- );
- });
- });
-
- describe('pay expense report via ACH', () => {
- const amount = 10000;
- const comment = '💸💸💸💸';
- const merchant = 'NASDAQ';
-
- afterEach(() => {
- fetch.resume();
- });
-
- it('updates the expense request and expense report when paid while offline', () => {
- let expenseReport = {};
- let chatReport = {};
-
- fetch.pause();
- Onyx.set(ONYXKEYS.SESSION, {email: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID});
- return waitForBatchedUpdates()
- .then(() => {
- PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- chatReport = _.find(allReports, (report) => report.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.VBBA, chatReport, expenseReport);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (allActions) => {
- Onyx.disconnect(connectionID);
- expect(_.values(allActions)).toEqual(
- expect.arrayContaining([
- expect.objectContaining({
- message: expect.arrayContaining([
- expect.objectContaining({
- html: `paid $${amount / 100}.00 with Expensify`,
- text: `paid $${amount / 100}.00 with Expensify`,
- }),
- ]),
- originalMessage: expect.objectContaining({
- amount,
- paymentType: CONST.IOU.PAYMENT_TYPE.VBBA,
- type: 'pay',
- }),
- }),
- ]),
- );
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- const updatedIOUReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- const updatedChatReport = _.find(allReports, (report) => report.reportID === expenseReport.chatReportID);
- expect(updatedIOUReport).toEqual(
- expect.objectContaining({
- lastMessageHtml: `paid $${amount / 100}.00 with Expensify`,
- lastMessageText: `paid $${amount / 100}.00 with Expensify`,
- statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
- stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
- }),
- );
- expect(updatedChatReport).toEqual(
- expect.objectContaining({
- lastMessageHtml: `paid $${amount / 100}.00 with Expensify`,
- lastMessageText: `paid $${amount / 100}.00 with Expensify`,
- }),
- );
- resolve();
- },
- });
- }),
- );
- });
-
- it('shows an error when paying results in an error', () => {
- let expenseReport = {};
- let chatReport = {};
-
- Onyx.set(ONYXKEYS.SESSION, {email: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID});
- return waitForBatchedUpdates()
- .then(() => {
- PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- chatReport = _.find(allReports, (report) => report.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- fetch.fail();
- IOU.payMoneyRequest('ACH', chatReport, expenseReport);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (allActions) => {
- Onyx.disconnect(connectionID);
- const erroredAction = _.find(_.values(allActions), (action) => !_.isEmpty(action.errors));
- expect(_.values(erroredAction.errors)).toEqual(expect.arrayContaining([['iou.error.other', {isTranslated: false}]]));
- resolve();
- },
- });
- }),
- );
- });
- });
-
- describe('deleteMoneyRequest', () => {
- const amount = 10000;
- const comment = 'Send me money please';
- let chatReport;
- let iouReport;
- let createIOUAction;
- let transaction;
- let thread;
- const TEST_USER_ACCOUNT_ID = 1;
- const TEST_USER_LOGIN = 'test@test.com';
- let IOU_REPORT_ID;
- let reportActionID;
- const REPORT_ACTION = {
- actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
- actorAccountID: TEST_USER_ACCOUNT_ID,
- automatic: false,
- avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
- message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment', translationKey: ''}],
- person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
- shouldShow: true,
- };
-
- let reportActions;
-
- beforeEach(async () => {
- // Given mocks are cleared and helpers are set up
- jest.clearAllMocks();
- PusherHelper.setup();
-
- // Given a test user is signed in with Onyx setup and some initial data
- await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN);
- User.subscribeToUserEvents();
- await waitForBatchedUpdates();
- await TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID);
-
- // When an IOU request for money is made
- IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', '', TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID, {login: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}, comment);
- await waitForBatchedUpdates();
-
- // When fetching all reports from Onyx
- const allReports = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (reports) => {
- Onyx.disconnect(connectionID);
- resolve(reports);
- },
- });
- });
-
- // Then we should have exactly 3 reports
- expect(_.size(allReports)).toBe(3);
-
- // Then one of them should be a chat report with relevant properties
- chatReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.CHAT);
- expect(chatReport).toBeTruthy();
- expect(chatReport).toHaveProperty('reportID');
- expect(chatReport).toHaveProperty('iouReportID');
-
- // Then one of them should be an IOU report with relevant properties
- iouReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.IOU);
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
-
- // Then their IDs should reference each other
- expect(chatReport.iouReportID).toBe(iouReport.reportID);
- expect(iouReport.chatReportID).toBe(chatReport.reportID);
-
- // Storing IOU Report ID for further reference
- IOU_REPORT_ID = chatReport.iouReportID;
-
- await waitForBatchedUpdates();
-
- // When fetching all report actions from Onyx
- const allReportActions = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (actions) => {
- Onyx.disconnect(connectionID);
- resolve(actions);
- },
- });
- });
-
- // Then we should find an IOU action with specific properties
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
- createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction).toBeTruthy();
- expect(createIOUAction.originalMessage.IOUReportID).toBe(iouReport.reportID);
-
- // When fetching all transactions from Onyx
- const allTransactions = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (transactions) => {
- Onyx.disconnect(connectionID);
- resolve(transactions);
- },
- });
- });
-
- // Then we should find a specific transaction with relevant properties
- transaction = _.find(allTransactions, (t) => t);
- expect(transaction).toBeTruthy();
- expect(transaction.amount).toBe(amount);
- expect(transaction.reportID).toBe(iouReport.reportID);
- expect(createIOUAction.originalMessage.IOUTransactionID).toBe(transaction.transactionID);
- });
-
- afterEach(PusherHelper.teardown);
-
- it('delete a money request (IOU Action and transaction) successfully', async () => {
- // Given the fetch operations are paused and a money request is initiated
- fetch.pause();
-
- // When the money request is deleted
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true);
- await waitForBatchedUpdates();
-
- // Then we check if the IOU report action is removed from the report actions collection
- let reportActionsForReport = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (actionsForReport) => {
- Onyx.disconnect(connectionID);
- resolve(actionsForReport);
- },
- });
- });
-
- createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- // Then the IOU Action should be truthy for offline support.
- expect(createIOUAction).toBeTruthy();
-
- // Then we check if the transaction is removed from the transactions collection
- const t = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
- waitForCollectionCallback: true,
- callback: (transactionResult) => {
- Onyx.disconnect(connectionID);
- resolve(transactionResult);
- },
- });
- });
-
- expect(t).toBeFalsy();
-
- // Given fetch operations are resumed
- fetch.resume();
- await waitForBatchedUpdates();
-
- // Then we recheck the IOU report action from the report actions collection
- reportActionsForReport = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (actionsForReport) => {
- Onyx.disconnect(connectionID);
- resolve(actionsForReport);
- },
- });
- });
-
- createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction).toBeFalsy();
-
- // Then we recheck the transaction from the transactions collection
- const tr = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
- waitForCollectionCallback: true,
- callback: (transactionResult) => {
- Onyx.disconnect(connectionID);
- resolve(transactionResult);
- },
- });
- });
-
- expect(tr).toBeFalsy();
- });
-
- it('delete the IOU report when there are no visible comments left in the IOU report', async () => {
- // Given an IOU report and a paused fetch state
- fetch.pause();
-
- // When the IOU money request is deleted
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true);
- await waitForBatchedUpdates();
-
- let report = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (res) => {
- Onyx.disconnect(connectionID);
- resolve(res);
- },
- });
- });
-
- // Then the report should be truthy for offline support
- expect(report).toBeTruthy();
-
- // Given the resumed fetch state
- fetch.resume();
- await waitForBatchedUpdates();
-
- report = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (res) => {
- Onyx.disconnect(connectionID);
- resolve(res);
- },
- });
- });
-
- // Then the report should be falsy so that there is no trace of the money request.
- expect(report).toBeFalsy();
- });
-
- it('does not delete the IOU report when there are visible comments left in the IOU report', async () => {
- // Given the initial setup is completed
- await waitForBatchedUpdates();
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${IOU_REPORT_ID}`,
- callback: (val) => (reportActions = val),
- });
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
-
- // When a comment is added to the IOU report
- Report.addComment(IOU_REPORT_ID, 'Testing a comment');
- await waitForBatchedUpdates();
-
- // Then verify that the comment is correctly added
- const resultAction = _.find(reportActions, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
- reportActionID = resultAction.reportActionID;
-
- expect(resultAction.message).toEqual(REPORT_ACTION.message);
- expect(resultAction.person).toEqual(REPORT_ACTION.person);
- expect(resultAction.pendingAction).toBeUndefined();
-
- await waitForBatchedUpdates();
-
- // Verify there are three actions (created + iou + addcomment) and our optimistic comment has been removed
- expect(_.size(reportActions)).toBe(3);
-
- // Then check the loading state of our action
- const resultActionAfterUpdate = reportActions[reportActionID];
- expect(resultActionAfterUpdate.pendingAction).toBeUndefined();
-
- // When we attempt to delete a money request from the IOU report
- fetch.pause();
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- await waitForBatchedUpdates();
-
- // Then expect that the IOU report still exists
- let allReports = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (reports) => {
- Onyx.disconnect(connectionID);
- resolve(reports);
- },
- });
- });
-
- await waitForBatchedUpdates();
-
- iouReport = _.find(allReports, (report) => ReportUtils.isIOUReport(report));
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
-
- // Given the resumed fetch state
- fetch.resume();
-
- allReports = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (reports) => {
- Onyx.disconnect(connectionID);
- resolve(reports);
- },
- });
- });
- // Then expect that the IOU report still exists
- iouReport = _.find(allReports, (report) => ReportUtils.isIOUReport(report));
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
- });
-
- it('delete the transaction thread if there are no visible comments in the thread', async () => {
- // Given all promises are resolved
- await waitForBatchedUpdates();
- jest.advanceTimersByTime(10);
-
- // Given a transaction thread
- thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
-
- expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- callback: (val) => (reportActions = val),
- });
-
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
-
- // Given User logins from the participant accounts
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs);
-
- // When Opening a thread report with the given details
- Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction.reportActionID);
- await waitForBatchedUpdates();
-
- // Then The iou action has the transaction report id as a child report ID
- const allReportActions = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (actions) => {
- Onyx.disconnect(connectionID);
- resolve(actions);
- },
- });
- });
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
- createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction.childReportID).toBe(thread.reportID);
-
- await waitForBatchedUpdates();
-
- // Given Fetch is paused and timers have advanced
- fetch.pause();
- jest.advanceTimersByTime(10);
-
- // When Deleting a money request
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- await waitForBatchedUpdates();
-
- // Then The report for the given thread ID does not exist
- let report = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportData) => {
- Onyx.disconnect(connectionID);
- resolve(reportData);
- },
- });
- });
-
- expect(report).toBeFalsy();
- fetch.resume();
-
- // Then After resuming fetch, the report for the given thread ID still does not exist
- report = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportData) => {
- Onyx.disconnect(connectionID);
- resolve(reportData);
- },
- });
- });
-
- expect(report).toBeFalsy();
- });
-
- it('delete the transaction thread if there are only changelogs (i.e. MODIFIEDEXPENSE actions) in the thread', async () => {
- // Given all promises are resolved
- await waitForBatchedUpdates();
- jest.advanceTimersByTime(10);
-
- // Given a transaction thread
- thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- callback: (val) => (reportActions = val),
- });
-
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
-
- // Given User logins from the participant accounts
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs);
-
- // When Opening a thread report with the given details
- Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction.reportActionID);
- await waitForBatchedUpdates();
-
- // Then The iou action has the transaction report id as a child report ID
- const allReportActions = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (actions) => {
- Onyx.disconnect(connectionID);
- resolve(actions);
- },
- });
- });
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
- createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction.childReportID).toBe(thread.reportID);
-
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
- IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'});
- await waitForBatchedUpdates();
-
- // Verify there are two actions (created + changelog)
- expect(_.size(reportActions)).toBe(2);
-
- // Fetch the updated IOU Action from Onyx
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForReport) => {
- Onyx.disconnect(connectionID);
- createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- resolve();
- },
- });
- });
-
- // When Deleting a money request
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- await waitForBatchedUpdates();
-
- // Then, the report for the given thread ID does not exist
- const report = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportData) => {
- Onyx.disconnect(connectionID);
- resolve(reportData);
- },
- });
- });
-
- expect(report).toBeFalsy();
- });
-
- it('does not delete the transaction thread if there are visible comments in the thread', async () => {
- // Given initial environment is set up
- await waitForBatchedUpdates();
-
- // Given a transaction thread
- thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
-
- expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs);
- jest.advanceTimersByTime(10);
- Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction.reportActionID);
- await waitForBatchedUpdates();
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- callback: (val) => (reportActions = val),
- });
- await waitForBatchedUpdates();
-
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (report) => {
- Onyx.disconnect(connectionID);
- expect(report).toBeTruthy();
- resolve();
- },
- });
- });
-
- jest.advanceTimersByTime(10);
-
- // When a comment is added
- Report.addComment(thread.reportID, 'Testing a comment');
- await waitForBatchedUpdates();
-
- // Then comment details should match the expected report action
- const resultAction = _.find(reportActions, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
- reportActionID = resultAction.reportActionID;
- expect(resultAction.message).toEqual(REPORT_ACTION.message);
- expect(resultAction.person).toEqual(REPORT_ACTION.person);
-
- await waitForBatchedUpdates();
-
- // Then the report should have 2 actions
- expect(_.size(reportActions)).toBe(2);
- const resultActionAfter = reportActions[reportActionID];
- expect(resultActionAfter.pendingAction).toBeUndefined();
-
- fetch.pause();
- // When deleting money request
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- await waitForBatchedUpdates();
-
- // Then the transaction thread report should still exist
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (report) => {
- Onyx.disconnect(connectionID);
- expect(report).toBeTruthy();
- resolve();
- },
- });
- });
-
- // When fetch resumes
- // Then the transaction thread report should still exist
- fetch.resume();
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
- waitForCollectionCallback: true,
- callback: (report) => {
- Onyx.disconnect(connectionID);
- expect(report).toBeTruthy();
- resolve();
- },
- });
- });
- });
-
- it('update the moneyRequestPreview to show [Deleted request] when appropriate', async () => {
- await waitForBatchedUpdates();
-
- // Given a thread report
-
- jest.advanceTimersByTime(10);
- thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
-
- expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- callback: (val) => (reportActions = val),
- });
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs);
- Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction.reportActionID);
-
- await waitForBatchedUpdates();
-
- const allReportActions = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (actions) => {
- Onyx.disconnect(connectionID);
- resolve(actions);
- },
- });
- });
-
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
- createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction.childReportID).toBe(thread.reportID);
-
- await waitForBatchedUpdates();
-
- // Given an added comment to the thread report
-
- jest.advanceTimersByTime(10);
-
- Report.addComment(thread.reportID, 'Testing a comment');
- await waitForBatchedUpdates();
-
- // Fetch the updated IOU Action from Onyx due to addition of comment to transaction thread.
- // This needs to be fetched as `deleteMoneyRequest` depends on `childVisibleActionCount` in `createIOUAction`.
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForReport) => {
- Onyx.disconnect(connectionID);
- createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- resolve();
- },
- });
- });
-
- let resultAction = _.find(reportActions, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
- reportActionID = resultAction.reportActionID;
-
- expect(resultAction.message).toEqual(REPORT_ACTION.message);
- expect(resultAction.person).toEqual(REPORT_ACTION.person);
- expect(resultAction.pendingAction).toBeUndefined();
-
- await waitForBatchedUpdates();
-
- // Verify there are three actions (created + addcomment) and our optimistic comment has been removed
- expect(_.size(reportActions)).toBe(2);
-
- let resultActionAfterUpdate = reportActions[reportActionID];
-
- // Verify that our action is no longer in the loading state
- expect(resultActionAfterUpdate.pendingAction).toBeUndefined();
-
- await waitForBatchedUpdates();
-
- // Given an added comment to the IOU report
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${IOU_REPORT_ID}`,
- callback: (val) => (reportActions = val),
- });
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
-
- Report.addComment(IOU_REPORT_ID, 'Testing a comment');
- await waitForBatchedUpdates();
-
- resultAction = _.find(reportActions, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
- reportActionID = resultAction.reportActionID;
-
- expect(resultAction.message).toEqual(REPORT_ACTION.message);
- expect(resultAction.person).toEqual(REPORT_ACTION.person);
- expect(resultAction.pendingAction).toBeUndefined();
-
- await waitForBatchedUpdates();
-
- // Verify there are three actions (created + iou + addcomment) and our optimistic comment has been removed
- expect(_.size(reportActions)).toBe(3);
-
- resultActionAfterUpdate = reportActions[reportActionID];
-
- // Verify that our action is no longer in the loading state
- expect(resultActionAfterUpdate.pendingAction).toBeUndefined();
-
- fetch.pause();
- // When we delete the money request
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- await waitForBatchedUpdates();
-
- // Then we expect the moneyRequestPreview to show [Deleted request]
-
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForReport) => {
- Onyx.disconnect(connectionID);
- createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction.message[0].isDeletedParentAction).toBeTruthy();
- resolve();
- },
- });
- });
-
- // When we resume fetch
-
- fetch.resume();
-
- // Then we expect the moneyRequestPreview to show [Deleted request]
-
- await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
- waitForCollectionCallback: true,
- callback: (reportActionsForReport) => {
- Onyx.disconnect(connectionID);
- createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction.message[0].isDeletedParentAction).toBeTruthy();
- resolve();
- },
- });
- });
- });
-
- it('update IOU report and reportPreview with new totals and messages if the IOU report is not deleted', async () => {
- await waitForBatchedUpdates();
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
- callback: (val) => (iouReport = val),
- });
- await waitForBatchedUpdates();
-
- // Given a second money request in addition to the first one
-
- jest.advanceTimersByTime(10);
- const amount2 = 20000;
- const comment2 = 'Send me money please 2';
- IOU.requestMoney(chatReport, amount2, CONST.CURRENCY.USD, '', '', TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID, {login: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}, comment2);
-
- await waitForBatchedUpdates();
-
- // Then we expect the IOU report and reportPreview to update with new totals
-
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
- expect(iouReport.total).toBe(30000);
-
- const ioupreview = ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID);
- expect(ioupreview).toBeTruthy();
- expect(ioupreview.message[0].text).toBe('rory@expensifail.com owes $300.00');
-
- // When we delete the first money request
- fetch.pause();
- jest.advanceTimersByTime(10);
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- await waitForBatchedUpdates();
-
- // Then we expect the IOU report and reportPreview to update with new totals
-
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
- expect(iouReport.total).toBe(20000);
-
- // When we resume fetch
- fetch.resume();
-
- // Then we expect the IOU report and reportPreview to update with new totals
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
- expect(iouReport.total).toBe(20000);
- });
-
- it('navigate the user correctly to the iou Report when appropriate', async () => {
- await waitForBatchedUpdates();
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${IOU_REPORT_ID}`,
- callback: (val) => (reportActions = val),
- });
- await waitForBatchedUpdates();
-
- // Given an added comment to the iou report
-
- jest.advanceTimersByTime(10);
-
- Report.addComment(IOU_REPORT_ID, 'Testing a comment');
- await waitForBatchedUpdates();
-
- const resultAction = _.find(reportActions, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
- reportActionID = resultAction.reportActionID;
-
- expect(resultAction.message).toEqual(REPORT_ACTION.message);
- expect(resultAction.person).toEqual(REPORT_ACTION.person);
-
- await waitForBatchedUpdates();
-
- // Verify there are three actions (created + iou + addcomment) and our optimistic comment has been removed
- expect(_.size(reportActions)).toBe(3);
-
- await waitForBatchedUpdates();
-
- // Given a thread report
-
- jest.advanceTimersByTime(10);
- thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID});
-
- expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
- callback: (val) => (reportActions = val),
- });
- await waitForBatchedUpdates();
-
- jest.advanceTimersByTime(10);
- const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs);
- Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction.reportActionID);
- await waitForBatchedUpdates();
-
- const allReportActions = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (actions) => {
- Onyx.disconnect(connectionID);
- resolve(actions);
- },
- });
- });
-
- const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
- createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
- expect(createIOUAction.childReportID).toBe(thread.reportID);
-
- await waitForBatchedUpdates();
-
- // When we delete the money request in SingleTransactionView and we should not delete the IOU report
-
- fetch.pause();
-
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true);
- await waitForBatchedUpdates();
-
- let allReports = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (reports) => {
- Onyx.disconnect(connectionID);
- resolve(reports);
- },
- });
- });
-
- await waitForBatchedUpdates();
-
- iouReport = _.find(allReports, (report) => ReportUtils.isIOUReport(report));
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
-
- fetch.resume();
-
- allReports = await new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (reports) => {
- Onyx.disconnect(connectionID);
- resolve(reports);
- },
- });
- });
-
- iouReport = _.find(allReports, (report) => ReportUtils.isIOUReport(report));
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
-
- // Then we expect to navigate to the iou report
-
- expect(Navigation.goBack).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(IOU_REPORT_ID));
- });
-
- it('navigate the user correctly to the chat Report when appropriate', () => {
- // When we delete the money request and we should delete the IOU report
- IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
- // Then we expect to navigate to the chat report
- expect(Navigation.goBack).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(chatReport.reportID));
- });
- });
-
- describe('submitReport', () => {
- it('correctly submits a report', () => {
- const amount = 10000;
- const comment = '💸💸💸💸';
- const merchant = 'NASDAQ';
- let expenseReport = {};
- let chatReport = {};
- return waitForBatchedUpdates()
- .then(() => {
- PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- chatReport = _.find(allReports, (report) => report.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- IOU.requestMoney(
- chatReport,
- amount,
- CONST.CURRENCY.USD,
- '',
- merchant,
- RORY_EMAIL,
- RORY_ACCOUNT_ID,
- {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID, isPolicyExpenseChat: true, reportID: chatReport.reportID},
- comment,
- );
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.EXPENSE);
- Onyx.merge(`report_${expenseReport.reportID}`, {
- statusNum: 0,
- stateNum: 0,
- });
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.EXPENSE);
-
- // Verify report is a draft
- expect(expenseReport.stateNum).toBe(0);
- expect(expenseReport.statusNum).toBe(0);
- resolve();
- },
- });
- }),
- )
- .then(() => {
- IOU.submitReport(expenseReport);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.EXPENSE);
-
- // Report was submitted correctly
- expect(expenseReport.stateNum).toBe(1);
- expect(expenseReport.statusNum).toBe(1);
- resolve();
- },
- });
- }),
- );
- });
- it('correctly implements error handling', () => {
- const amount = 10000;
- const comment = '💸💸💸💸';
- const merchant = 'NASDAQ';
- let expenseReport = {};
- let chatReport = {};
- return waitForBatchedUpdates()
- .then(() => {
- PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- chatReport = _.find(allReports, (report) => report.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
-
- resolve();
- },
- });
- }),
- )
- .then(() => {
- IOU.requestMoney(
- chatReport,
- amount,
- CONST.CURRENCY.USD,
- '',
- merchant,
- RORY_EMAIL,
- RORY_ACCOUNT_ID,
- {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID, isPolicyExpenseChat: true, reportID: chatReport.reportID},
- comment,
- );
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.EXPENSE);
- Onyx.merge(`report_${expenseReport.reportID}`, {
- statusNum: 0,
- stateNum: 0,
- });
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.EXPENSE);
-
- // Verify report is a draft
- expect(expenseReport.stateNum).toBe(0);
- expect(expenseReport.statusNum).toBe(0);
- resolve();
- },
- });
- }),
- )
- .then(() => {
- fetch.fail();
- IOU.submitReport(expenseReport);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- expenseReport = _.find(allReports, (report) => report.type === CONST.REPORT.TYPE.EXPENSE);
-
- // Report was submitted with some fail
- expect(expenseReport.stateNum).toBe(0);
- expect(expenseReport.statusNum).toBe(0);
- resolve();
- },
- });
- }),
- );
- });
- });
-});
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
new file mode 100644
index 000000000000..3298cd51c9f1
--- /dev/null
+++ b/tests/actions/IOUTest.ts
@@ -0,0 +1,3225 @@
+import isEqual from 'lodash/isEqual';
+import Onyx from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {OptimisticChatReport} from '@libs/ReportUtils';
+import CONST from '@src/CONST';
+import * as IOU from '@src/libs/actions/IOU';
+import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
+import * as PolicyActions from '@src/libs/actions/Policy';
+import * as Report from '@src/libs/actions/Report';
+import * as ReportActions from '@src/libs/actions/ReportActions';
+import * as User from '@src/libs/actions/User';
+import DateUtils from '@src/libs/DateUtils';
+import Navigation from '@src/libs/Navigation/Navigation';
+import * as NumberUtils from '@src/libs/NumberUtils';
+import * as PersonalDetailsUtils from '@src/libs/PersonalDetailsUtils';
+import * as ReportActionsUtils from '@src/libs/ReportActionsUtils';
+import * as ReportUtils from '@src/libs/ReportUtils';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
+import type {IOUMessage, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
+import type {ReportActionBase} from '@src/types/onyx/ReportAction';
+import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import PusherHelper from '../utils/PusherHelper';
+import * as TestHelper from '../utils/TestHelper';
+import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
+import waitForNetworkPromises from '../utils/waitForNetworkPromises';
+
+jest.mock('@src/libs/Navigation/Navigation', () => ({
+ navigate: jest.fn(),
+ dismissModal: jest.fn(),
+ dismissModalWithReport: jest.fn(),
+ goBack: jest.fn(),
+}));
+
+const CARLOS_EMAIL = 'cmartins@expensifail.com';
+const CARLOS_ACCOUNT_ID = 1;
+const JULES_EMAIL = 'jules@expensifail.com';
+const JULES_ACCOUNT_ID = 2;
+const RORY_EMAIL = 'rory@expensifail.com';
+const RORY_ACCOUNT_ID = 3;
+const VIT_EMAIL = 'vit@expensifail.com';
+const VIT_ACCOUNT_ID = 4;
+
+OnyxUpdateManager();
+describe('actions/IOU', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ });
+ });
+
+ beforeEach(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ global.fetch = TestHelper.getGlobalFetchMock();
+ return Onyx.clear().then(waitForBatchedUpdates);
+ });
+
+ describe('requestMoney', () => {
+ it('creates new chat if needed', () => {
+ const amount = 10000;
+ const comment = 'Giv money plz';
+ const merchant = 'KFC';
+ let iouReportID: string | undefined;
+ let createdAction: OnyxEntry;
+ let iouAction: OnyxEntry;
+ let transactionID: string | undefined;
+ let transactionThread: OnyxEntry;
+ let transactionThreadCreatedAction: OnyxEntry;
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return (
+ waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ // A chat report, a transaction thread, and an iou report should be created
+ const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
+ const iouReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ expect(Object.keys(chatReports).length).toBe(2);
+ expect(Object.keys(iouReports).length).toBe(1);
+ const chatReport = chatReports[0];
+ const transactionThreadReport = chatReports[1];
+ const iouReport = iouReports[0];
+ iouReportID = iouReport?.reportID;
+ transactionThread = transactionThreadReport;
+
+ expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ // They should be linked together
+ expect(chatReport?.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]);
+ expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+
+ // The IOU report should have a CREATED action and IOU action
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ const createdActions = Object.values(reportActionsForIOUReport ?? {}).filter(
+ (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ );
+ const iouActions = Object.values(reportActionsForIOUReport ?? {}).filter(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ );
+ expect(Object.values(createdActions).length).toBe(1);
+ expect(Object.values(iouActions).length).toBe(1);
+ createdAction = createdActions?.[0] ?? null;
+ iouAction = iouActions?.[0] ?? null;
+
+ // The CREATED action should not be created after the IOU action
+ expect(Date.parse(createdAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? ''));
+
+ // The IOUReportID should be correct
+ expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
+
+ // The comment should be included in the IOU action
+ expect(iouAction.originalMessage.comment).toBe(comment);
+
+ // The amount in the IOU action should be correct
+ expect(iouAction.originalMessage.amount).toBe(amount);
+
+ // The IOU type should be correct
+ expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+
+ // Both actions should be pending
+ expect(createdAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForTransactionThread) => {
+ Onyx.disconnect(connectionID);
+
+ // The transaction thread should have a CREATED action
+ expect(Object.values(reportActionsForTransactionThread ?? {}).length).toBe(1);
+ const createdActions = Object.values(reportActionsForTransactionThread ?? {}).filter(
+ (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ );
+ expect(Object.values(createdActions).length).toBe(1);
+ transactionThreadCreatedAction = createdActions[0];
+
+ expect(transactionThreadCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ // There should be one transaction
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ const transaction = Object.values(allTransactions ?? []).find((t) => !isEmptyObject(t));
+ transactionID = transaction?.transactionID;
+
+ // The transaction should be attached to the IOU report
+ expect(transaction?.reportID).toBe(iouReportID);
+
+ // Its amount should match the amount of the request
+ expect(transaction?.amount).toBe(amount);
+
+ // The comment should be correct
+ expect(transaction?.comment.comment).toBe(comment);
+
+ // It should be pending
+ expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
+
+ expect(transaction?.merchant).toBe(merchant);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ .then(fetch.resume)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
+ waitForCollectionCallback: false,
+ callback: (transaction) => {
+ Onyx.disconnect(connectionID);
+ expect(transaction?.pendingAction).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+ );
+ });
+
+ it('updates existing chat report if there is one', () => {
+ const amount = 10000;
+ const comment = 'Giv money plz';
+ let chatReport: OnyxTypes.Report = {
+ reportID: '1234',
+ type: CONST.REPORT.TYPE.CHAT,
+ participantAccountIDs: [CARLOS_ACCOUNT_ID],
+ };
+ const createdAction: OnyxTypes.ReportAction = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ created: DateUtils.getDBTime(),
+ };
+ let iouReportID: string | undefined;
+ let iouAction: OnyxEntry;
+ let iouCreatedAction: OnyxEntry;
+ let transactionID: string | undefined;
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ return (
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport)
+ .then(() =>
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, {
+ [createdAction.reportActionID]: createdAction,
+ }),
+ )
+ .then(() => {
+ IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ // The same chat report should be reused, a transaction thread and an IOU report should be created
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT)?.reportID).toBe(chatReport.reportID);
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? chatReport;
+ const iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ iouReportID = iouReport?.reportID;
+
+ expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ // They should be linked together
+ expect(chatReport.iouReportID).toBe(iouReportID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (allIOUReportActions) => {
+ Onyx.disconnect(connectionID);
+
+ iouCreatedAction =
+ Object.values(allIOUReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
+ iouAction =
+ Object.values(allIOUReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+
+ // The CREATED action should not be created after the IOU action
+ expect(Date.parse(iouCreatedAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? ''));
+
+ // The IOUReportID should be correct
+ expect(iouAction?.originalMessage?.IOUReportID).toBe(iouReportID);
+
+ // The comment should be included in the IOU action
+ expect(iouAction?.originalMessage?.comment).toBe(comment);
+
+ // The amount in the IOU action should be correct
+ expect(iouAction?.originalMessage?.amount).toBe(amount);
+
+ // The IOU action type should be correct
+ expect(iouAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+
+ // The IOU action should be pending
+ expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ // There should be one transaction
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ const transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t));
+ transactionID = transaction?.transactionID;
+
+ // The transaction should be attached to the IOU report
+ expect(transaction?.reportID).toBe(iouReportID);
+
+ // Its amount should match the amount of the request
+ expect(transaction?.amount).toBe(amount);
+
+ // The comment should be correct
+ expect(transaction?.comment.comment).toBe(comment);
+
+ expect(transaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
+
+ // It should be pending
+ expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ .then(fetch.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
+ waitForCollectionCallback: true,
+ callback: (transaction) => {
+ Onyx.disconnect(connectionID);
+ expect(transaction?.pendingAction).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+ );
+ });
+
+ it('updates existing IOU report if there is one', () => {
+ const amount = 10000;
+ const comment = 'Giv money plz';
+ const chatReportID = '1234';
+ const iouReportID = '5678';
+ let chatReport: OnyxEntry = {
+ reportID: chatReportID,
+ type: CONST.REPORT.TYPE.CHAT,
+ iouReportID,
+ participantAccountIDs: [CARLOS_ACCOUNT_ID],
+ };
+ const createdAction: OnyxTypes.ReportAction = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ created: DateUtils.getDBTime(),
+ };
+ const existingTransaction: OnyxTypes.Transaction = {
+ transactionID: NumberUtils.rand64(),
+ amount: 1000,
+ comment: {
+ comment: 'Existing transaction',
+ },
+ created: DateUtils.getDBTime(),
+ currency: CONST.CURRENCY.USD,
+ merchant: '',
+ reportID: '',
+ };
+ let iouReport: OnyxEntry = {
+ reportID: iouReportID,
+ chatReportID,
+ type: CONST.REPORT.TYPE.IOU,
+ ownerAccountID: RORY_ACCOUNT_ID,
+ managerID: CARLOS_ACCOUNT_ID,
+ currency: CONST.CURRENCY.USD,
+ total: existingTransaction.amount,
+ };
+ const iouAction: OnyxEntry = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
+ actorAccountID: RORY_ACCOUNT_ID,
+ created: DateUtils.getDBTime(),
+ originalMessage: {
+ IOUReportID: iouReportID,
+ IOUTransactionID: existingTransaction.transactionID,
+ amount: existingTransaction.amount,
+ currency: CONST.CURRENCY.USD,
+ type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
+ participantAccountIDs: [RORY_ACCOUNT_ID, CARLOS_ACCOUNT_ID],
+ },
+ };
+ let newIOUAction: OnyxEntry;
+ let newTransaction: OnyxEntry;
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ return (
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport)
+ .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, iouReport))
+ .then(() =>
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {
+ [createdAction.reportActionID]: createdAction,
+ [iouAction.reportActionID]: iouAction,
+ }),
+ )
+ .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransaction.transactionID}`, existingTransaction))
+ .then(() => {
+ if (chatReport) {
+ IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ // No new reports should be created
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).find((report) => report?.reportID === chatReportID)).toBeTruthy();
+ expect(Object.values(allReports ?? {}).find((report) => report?.reportID === iouReportID)).toBeTruthy();
+
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+
+ // The total on the iou report should be updated
+ expect(iouReport?.total).toBe(11000);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+ newIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU =>
+ reportAction?.reportActionID !== createdAction.reportActionID && reportAction?.reportActionID !== iouAction?.reportActionID,
+ ) ?? null;
+
+ // The IOUReportID should be correct
+ expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
+
+ // The comment should be included in the IOU action
+ expect(newIOUAction?.originalMessage.comment).toBe(comment);
+
+ // The amount in the IOU action should be correct
+ expect(newIOUAction?.originalMessage.amount).toBe(amount);
+
+ // The type of the IOU action should be correct
+ expect(newIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+
+ // The IOU action should be pending
+ expect(newIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ // There should be two transactions
+ expect(Object.values(allTransactions ?? {}).length).toBe(2);
+
+ newTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.transactionID !== existingTransaction.transactionID) ?? null;
+
+ expect(newTransaction?.reportID).toBe(iouReportID);
+ expect(newTransaction?.amount).toBe(amount);
+ expect(newTransaction?.comment.comment).toBe(comment);
+ expect(newTransaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
+ expect(newTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((newIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(newTransaction?.transactionID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ .then(fetch.resume)
+ .then(waitForNetworkPromises)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+ Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ );
+ });
+
+ it('correctly implements RedBrickRoad error handling', () => {
+ const amount = 10000;
+ const comment = 'Giv money plz';
+ let chatReportID: string | undefined;
+ let iouReportID: string | undefined;
+ let createdAction: OnyxEntry;
+ let iouAction: OnyxEntry;
+ let transactionID: string;
+ let transactionThreadReport: OnyxEntry;
+ let transactionThreadAction: OnyxEntry;
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return (
+ waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ // A chat report, transaction thread and an iou report should be created
+ const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
+ const iouReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ expect(Object.values(chatReports).length).toBe(2);
+ expect(Object.values(iouReports).length).toBe(1);
+ const chatReport = chatReports[0];
+ chatReportID = chatReport?.reportID;
+ transactionThreadReport = chatReports[1];
+
+ const iouReport = iouReports[0];
+ iouReportID = iouReport?.reportID;
+
+ expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ // They should be linked together
+ expect(chatReport?.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]);
+ expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+
+ // The chat report should have a CREATED action and IOU action
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ const createdActions =
+ Object.values(reportActionsForIOUReport ?? {}).filter((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
+ const iouActions =
+ Object.values(reportActionsForIOUReport ?? {}).filter(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(Object.values(createdActions).length).toBe(1);
+ expect(Object.values(iouActions).length).toBe(1);
+ createdAction = createdActions[0];
+ iouAction = iouActions[0];
+
+ // The CREATED action should not be created after the IOU action
+ expect(Date.parse(createdAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? {}));
+
+ // The IOUReportID should be correct
+ expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
+
+ // The comment should be included in the IOU action
+ expect(iouAction.originalMessage.comment).toBe(comment);
+
+ // The amount in the IOU action should be correct
+ expect(iouAction.originalMessage.amount).toBe(amount);
+
+ // The type should be correct
+ expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+
+ // Both actions should be pending
+ expect(createdAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ // There should be one transaction
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ const transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t));
+ transactionID = transaction?.transactionID ?? '';
+
+ expect(transaction?.reportID).toBe(iouReportID);
+ expect(transaction?.amount).toBe(amount);
+ expect(transaction?.comment.comment).toBe(comment);
+ expect(transaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
+ expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then((): Promise => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.fail();
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ return fetch.resume() as Promise;
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ iouAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (reportActionsForTransactionThread) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForTransactionThread ?? {}).length).toBe(3);
+ transactionThreadAction =
+ Object.values(reportActionsForTransactionThread?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReport?.reportID}`] ?? {}).find(
+ (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ expect(transactionThreadAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
+ waitForCollectionCallback: false,
+ callback: (transaction) => {
+ Onyx.disconnect(connectionID);
+ expect(transaction?.pendingAction).toBeFalsy();
+ expect(transaction?.errors).toBeTruthy();
+ expect(Object.values(transaction?.errors ?? {})[0]).toEqual(expect.arrayContaining(['iou.error.genericCreateFailureMessage', {isTranslated: false}]));
+ resolve();
+ },
+ });
+ }),
+ )
+
+ // If the user clears the errors on the IOU action
+ .then(
+ () =>
+ new Promise((resolve) => {
+ ReportActions.clearReportActionErrors(iouReportID ?? '', iouAction);
+ ReportActions.clearReportActionErrors(transactionThreadReport?.reportID ?? '', transactionThreadAction);
+ resolve();
+ }),
+ )
+
+ // Then the reportAction should be removed from Onyx
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForReport) => {
+ Onyx.disconnect(connectionID);
+ iouAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(iouAction).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+
+ // Along with the associated transaction
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
+ waitForCollectionCallback: false,
+ callback: (transaction) => {
+ Onyx.disconnect(connectionID);
+ expect(transaction).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+
+ // If a user clears the errors on the CREATED action (which, technically are just errors on the report)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ Report.deleteReport(chatReportID ?? '');
+ Report.deleteReport(transactionThreadReport?.reportID ?? '');
+ resolve();
+ }),
+ )
+
+ // Then the report should be deleted
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allReports ?? {}).forEach((report) => expect(report).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+
+ // All reportActions should also be deleted
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: false,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allReportActions ?? {}).forEach((reportAction) => expect(reportAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+
+ // All transactions should also be deleted
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+
+ // Cleanup
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ .then(fetch.succeed)
+ );
+ });
+ });
+
+ describe('split bill', () => {
+ it('creates and updates new chats and IOUs as needed', () => {
+ jest.setTimeout(10 * 1000);
+ /*
+ * Given that:
+ * - Rory and Carlos have chatted before
+ * - Rory and Jules have chatted before and have an active IOU report
+ * - Rory and Vit have never chatted together before
+ * - There is no existing group chat with the four of them
+ */
+ const amount = 400;
+ const comment = 'Yes, I am splitting a bill for $4 USD';
+ const merchant = 'Yema Kitchen';
+ let carlosChatReport: OnyxEntry = {
+ reportID: NumberUtils.rand64(),
+ type: CONST.REPORT.TYPE.CHAT,
+ participantAccountIDs: [CARLOS_ACCOUNT_ID],
+ };
+ const carlosCreatedAction: OnyxEntry = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ created: DateUtils.getDBTime(),
+ reportID: carlosChatReport.reportID,
+ };
+ const julesIOUReportID = NumberUtils.rand64();
+ let julesChatReport: OnyxEntry = {
+ reportID: NumberUtils.rand64(),
+ type: CONST.REPORT.TYPE.CHAT,
+ iouReportID: julesIOUReportID,
+ participantAccountIDs: [JULES_ACCOUNT_ID],
+ };
+ const julesChatCreatedAction: OnyxEntry = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ created: DateUtils.getDBTime(),
+ reportID: julesChatReport.reportID,
+ };
+ const julesCreatedAction: OnyxEntry = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ created: DateUtils.getDBTime(),
+ reportID: julesIOUReportID,
+ };
+ jest.advanceTimersByTime(200);
+ const julesExistingTransaction: OnyxEntry = {
+ transactionID: NumberUtils.rand64(),
+ amount: 1000,
+ comment: {
+ comment: 'This is an existing transaction',
+ },
+ created: DateUtils.getDBTime(),
+ currency: '',
+ merchant: '',
+ reportID: '',
+ };
+ let julesIOUReport: OnyxEntry = {
+ reportID: julesIOUReportID,
+ chatReportID: julesChatReport.reportID,
+ type: CONST.REPORT.TYPE.IOU,
+ ownerAccountID: RORY_ACCOUNT_ID,
+ managerID: JULES_ACCOUNT_ID,
+ currency: CONST.CURRENCY.USD,
+ total: julesExistingTransaction?.amount,
+ };
+ const julesExistingIOUAction: OnyxEntry = {
+ reportActionID: NumberUtils.rand64(),
+ actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
+ actorAccountID: RORY_ACCOUNT_ID,
+ created: DateUtils.getDBTime(),
+ originalMessage: {
+ IOUReportID: julesIOUReportID,
+ IOUTransactionID: julesExistingTransaction?.transactionID,
+ amount: julesExistingTransaction?.amount ?? 0,
+ currency: CONST.CURRENCY.USD,
+ type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
+ participantAccountIDs: [RORY_ACCOUNT_ID, JULES_ACCOUNT_ID],
+ },
+ reportID: julesIOUReportID,
+ };
+
+ let carlosIOUReport: OnyxEntry;
+ let carlosIOUAction: OnyxEntry;
+ let carlosIOUCreatedAction: OnyxEntry;
+ let carlosTransaction: OnyxEntry;
+
+ let julesIOUAction: OnyxEntry;
+ let julesIOUCreatedAction: OnyxEntry;
+ let julesTransaction: OnyxEntry;
+
+ let vitChatReport: OnyxEntry;
+ let vitIOUReport: OnyxEntry;
+ let vitCreatedAction: OnyxEntry;
+ let vitIOUAction: OnyxEntry;
+ let vitTransaction: OnyxEntry;
+
+ let groupChat: OnyxEntry;
+ let groupCreatedAction: OnyxEntry;
+ let groupIOUAction: OnyxEntry;
+ let groupTransaction: OnyxEntry;
+
+ const reportCollectionDataSet = toCollectionDataSet(ONYXKEYS.COLLECTION.REPORT, [carlosChatReport, julesChatReport, julesIOUReport], (item) => item.reportID);
+
+ const carlosActionsCollectionDataSet = toCollectionDataSet(
+ `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}`,
+ [
+ {
+ [carlosCreatedAction.reportActionID]: carlosCreatedAction,
+ },
+ ],
+ (item) => item[carlosCreatedAction.reportActionID].reportID ?? '',
+ );
+
+ const julesActionsCollectionDataSet = toCollectionDataSet(
+ `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}`,
+ [
+ {
+ [julesCreatedAction.reportActionID]: julesCreatedAction,
+ [julesExistingIOUAction.reportActionID]: julesExistingIOUAction,
+ },
+ ],
+ (item) => item[julesCreatedAction.reportActionID].reportID ?? '',
+ );
+
+ const julesCreatedActionsCollectionDataSet = toCollectionDataSet(
+ `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}`,
+ [
+ {
+ [julesChatCreatedAction.reportActionID]: julesChatCreatedAction,
+ },
+ ],
+ (item) => item[julesChatCreatedAction.reportActionID].reportID ?? '',
+ );
+
+ return (
+ Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
+ ...reportCollectionDataSet,
+ })
+ .then(() =>
+ Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
+ ...carlosActionsCollectionDataSet,
+ ...julesCreatedActionsCollectionDataSet,
+ ...julesActionsCollectionDataSet,
+ }),
+ )
+ .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`, julesExistingTransaction))
+ .then(() => {
+ // When we split a bill offline
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ IOU.splitBill(
+ // TODO: Migrate after the backend accepts accountIDs
+ [
+ [CARLOS_EMAIL, String(CARLOS_ACCOUNT_ID)],
+ [JULES_EMAIL, String(JULES_ACCOUNT_ID)],
+ [VIT_EMAIL, String(VIT_ACCOUNT_ID)],
+ ].map(([email, accountID]) => ({login: email, accountID: Number(accountID)})),
+ RORY_EMAIL,
+ RORY_ACCOUNT_ID,
+ amount,
+ comment,
+ CONST.CURRENCY.USD,
+ merchant,
+ '',
+ '',
+ '',
+ );
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ // There should now be 10 reports
+ expect(Object.values(allReports ?? {}).length).toBe(10);
+
+ // 1. The chat report with Rory + Carlos
+ carlosChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === carlosChatReport?.reportID) ?? null;
+ expect(isEmptyObject(carlosChatReport)).toBe(false);
+ expect(carlosChatReport?.pendingFields).toBeFalsy();
+
+ // 2. The IOU report with Rory + Carlos (new)
+ carlosIOUReport =
+ Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU && report.managerID === CARLOS_ACCOUNT_ID) ?? null;
+ expect(isEmptyObject(carlosIOUReport)).toBe(false);
+ expect(carlosIOUReport?.total).toBe(amount / 4);
+
+ // 3. The chat report with Rory + Jules
+ julesChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === julesChatReport?.reportID) ?? null;
+ expect(isEmptyObject(julesChatReport)).toBe(false);
+ expect(julesChatReport?.pendingFields).toBeFalsy();
+
+ // 4. The IOU report with Rory + Jules
+ julesIOUReport = Object.values(allReports ?? {}).find((report) => report?.reportID === julesIOUReport?.reportID) ?? null;
+ expect(isEmptyObject(julesIOUReport)).toBe(false);
+ expect(julesChatReport?.pendingFields).toBeFalsy();
+ expect(julesIOUReport?.total).toBe((julesExistingTransaction?.amount ?? 0) + amount / 4);
+
+ // 5. The chat report with Rory + Vit (new)
+ vitChatReport =
+ Object.values(allReports ?? {}).find(
+ (report) => report?.type === CONST.REPORT.TYPE.CHAT && isEqual(report.participantAccountIDs, [VIT_ACCOUNT_ID]),
+ ) ?? null;
+ expect(isEmptyObject(vitChatReport)).toBe(false);
+ expect(vitChatReport?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
+
+ // 6. The IOU report with Rory + Vit (new)
+ vitIOUReport =
+ Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU && report.managerID === VIT_ACCOUNT_ID) ?? null;
+ expect(isEmptyObject(vitIOUReport)).toBe(false);
+ expect(vitIOUReport?.total).toBe(amount / 4);
+
+ // 7. The group chat with everyone
+ groupChat =
+ Object.values(allReports ?? {}).find(
+ (report) =>
+ report?.type === CONST.REPORT.TYPE.CHAT && isEqual(report.participantAccountIDs, [CARLOS_ACCOUNT_ID, JULES_ACCOUNT_ID, VIT_ACCOUNT_ID]),
+ ) ?? null;
+ expect(isEmptyObject(groupChat)).toBe(false);
+ expect(groupChat?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
+
+ // The 1:1 chat reports and the IOU reports should be linked together
+ expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID);
+ expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID);
+ expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID);
+ expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID);
+
+ expect(vitChatReport?.iouReportID).toBe(vitIOUReport?.reportID);
+ expect(vitIOUReport?.chatReportID).toBe(vitChatReport?.reportID);
+ expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+
+ // There should be reportActions on all 7 chat reports + 3 IOU reports in each 1:1 chat
+ expect(Object.values(allReportActions ?? {}).length).toBe(10);
+
+ const carlosReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${carlosChatReport?.iouReportID}`];
+ const julesReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${julesChatReport?.iouReportID}`];
+ const vitReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${vitChatReport?.iouReportID}`];
+ const groupReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChat?.reportID}`];
+
+ // Carlos DM should have two reportActions – the existing CREATED action and a pending IOU action
+ expect(Object.values(carlosReportActions ?? {}).length).toBe(2);
+ carlosIOUCreatedAction =
+ Object.values(carlosReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ carlosIOUAction =
+ Object.values(carlosReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(carlosIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(carlosIOUAction?.originalMessage.IOUReportID).toBe(carlosIOUReport?.reportID);
+ expect(carlosIOUAction?.originalMessage.amount).toBe(amount / 4);
+ expect(carlosIOUAction?.originalMessage.comment).toBe(comment);
+ expect(carlosIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ expect(Date.parse(carlosIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(carlosIOUAction?.created ?? ''));
+
+ // Jules DM should have three reportActions, the existing CREATED action, the existing IOU action, and a new pending IOU action
+ expect(Object.values(julesReportActions ?? {}).length).toBe(3);
+ expect(julesReportActions?.[julesCreatedAction.reportActionID]).toStrictEqual(julesCreatedAction);
+ julesIOUCreatedAction =
+ Object.values(julesReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ julesIOUAction =
+ Object.values(julesReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU =>
+ reportAction.reportActionID !== julesCreatedAction.reportActionID &&
+ reportAction.reportActionID !== julesExistingIOUAction.reportActionID,
+ ) ?? null;
+ expect(julesIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(julesIOUAction?.originalMessage.IOUReportID).toBe(julesIOUReport?.reportID);
+ expect(julesIOUAction?.originalMessage.amount).toBe(amount / 4);
+ expect(julesIOUAction?.originalMessage.comment).toBe(comment);
+ expect(julesIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ expect(Date.parse(julesIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(julesIOUAction?.created ?? ''));
+
+ // Vit DM should have two reportActions – a pending CREATED action and a pending IOU action
+ expect(Object.values(vitReportActions ?? {}).length).toBe(2);
+ vitCreatedAction =
+ Object.values(vitReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ vitIOUAction =
+ Object.values(vitReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(vitCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(vitIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(vitIOUAction?.originalMessage.IOUReportID).toBe(vitIOUReport?.reportID);
+ expect(vitIOUAction?.originalMessage.amount).toBe(amount / 4);
+ expect(vitIOUAction?.originalMessage.comment).toBe(comment);
+ expect(vitIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ expect(Date.parse(vitCreatedAction?.created ?? '')).toBeLessThan(Date.parse(vitIOUAction?.created ?? ''));
+
+ // Group chat should have two reportActions – a pending CREATED action and a pending IOU action w/ type SPLIT
+ expect(Object.values(groupReportActions ?? {}).length).toBe(2);
+ groupCreatedAction =
+ Object.values(groupReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
+ groupIOUAction =
+ Object.values(groupReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(groupCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(groupIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(groupIOUAction?.originalMessage).not.toHaveProperty('IOUReportID');
+ expect(groupIOUAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.SPLIT);
+ expect(Date.parse(groupCreatedAction?.created ?? '')).toBeLessThanOrEqual(Date.parse(groupIOUAction?.created ?? ''));
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ /* There should be 5 transactions
+ * – one existing one with Jules
+ * - one for each of the three IOU reports
+ * - one on the group chat w/ deleted report
+ */
+ expect(Object.values(allTransactions ?? {}).length).toBe(5);
+ expect(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`]).toBeTruthy();
+
+ carlosTransaction =
+ Object.values(allTransactions ?? {}).find(
+ (transaction) => transaction?.transactionID === (carlosIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
+ ) ?? null;
+ julesTransaction =
+ Object.values(allTransactions ?? {}).find(
+ (transaction) => transaction?.transactionID === (julesIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
+ ) ?? null;
+ vitTransaction =
+ Object.values(allTransactions ?? {}).find(
+ (transaction) => transaction?.transactionID === (vitIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
+ ) ?? null;
+ groupTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.reportID === CONST.REPORT.SPLIT_REPORTID) ?? null;
+
+ expect(carlosTransaction?.reportID).toBe(carlosIOUReport?.reportID);
+ expect(julesTransaction?.reportID).toBe(julesIOUReport?.reportID);
+ expect(vitTransaction?.reportID).toBe(vitIOUReport?.reportID);
+ expect(groupTransaction).toBeTruthy();
+
+ expect(carlosTransaction?.amount).toBe(amount / 4);
+ expect(julesTransaction?.amount).toBe(amount / 4);
+ expect(vitTransaction?.amount).toBe(amount / 4);
+ expect(groupTransaction?.amount).toBe(amount);
+
+ expect(carlosTransaction?.comment.comment).toBe(comment);
+ expect(julesTransaction?.comment.comment).toBe(comment);
+ expect(vitTransaction?.comment.comment).toBe(comment);
+ expect(groupTransaction?.comment.comment).toBe(comment);
+
+ expect(carlosTransaction?.merchant).toBe(merchant);
+ expect(julesTransaction?.merchant).toBe(merchant);
+ expect(vitTransaction?.merchant).toBe(merchant);
+ expect(groupTransaction?.merchant).toBe(merchant);
+
+ expect(carlosTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+ expect(julesTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+ expect(vitTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+
+ expect(carlosTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+ expect(julesTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+ expect(vitTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+
+ expect(carlosTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(julesTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(vitTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(groupTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ waitForCollectionCallback: false,
+ callback: (allPersonalDetails) => {
+ Onyx.disconnect(connectionID);
+ expect(allPersonalDetails).toMatchObject({
+ [VIT_ACCOUNT_ID]: {
+ accountID: VIT_ACCOUNT_ID,
+ displayName: VIT_EMAIL,
+ login: VIT_EMAIL,
+ },
+ });
+ resolve();
+ },
+ });
+ }),
+ )
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ .then(fetch.resume)
+ .then(waitForNetworkPromises)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allReports ?? {}).forEach((report) => {
+ if (!report?.pendingFields) {
+ return;
+ }
+ Object.values(report?.pendingFields).forEach((pendingField) => expect(pendingField).toBeFalsy());
+ });
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allReportActions ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ );
+ });
+ });
+
+ describe('payMoneyRequestElsewhere', () => {
+ it('clears outstanding IOUReport', () => {
+ const amount = 10000;
+ const comment = 'Giv money plz';
+ let chatReport: OnyxEntry;
+ let iouReport: OnyxEntry;
+ let createIOUAction: OnyxEntry;
+ let payIOUAction: OnyxEntry;
+ let transaction: OnyxEntry;
+ IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return (
+ waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+
+ const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
+ chatReport = chatReports[0];
+ expect(chatReport).toBeTruthy();
+ expect(chatReport).toHaveProperty('reportID');
+ expect(chatReport).toHaveProperty('iouReportID');
+
+ iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+
+ expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
+ expect(iouReport?.chatReportID).toBe(chatReport?.reportID);
+
+ expect(chatReport?.pendingFields).toBeFalsy();
+ expect(iouReport?.pendingFields).toBeFalsy();
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+
+ createIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction).toBeTruthy();
+ expect((createIOUAction?.originalMessage as IOUMessage)?.IOUReportID).toBe(iouReport?.reportID);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ transaction = Object.values(allTransactions ?? {}).find((t) => t) ?? null;
+ expect(transaction).toBeTruthy();
+ expect(transaction?.amount).toBe(amount);
+ expect(transaction?.reportID).toBe(iouReport?.reportID);
+ expect((createIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transaction?.transactionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ if (chatReport && iouReport) {
+ IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport);
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+
+ chatReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ iouReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.IOU) ?? null;
+
+ expect(chatReport?.iouReportID).toBeFalsy();
+
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`];
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+
+ payIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction) =>
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
+ ) ?? null;
+ expect(payIOUAction).toBeTruthy();
+ expect(payIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ .then(fetch.resume)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+
+ chatReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ iouReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.IOU) ?? null;
+
+ expect(chatReport?.iouReportID).toBeFalsy();
+
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`];
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+
+ payIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction) =>
+ reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
+ ) ?? null;
+ expect(payIOUAction).toBeTruthy();
+ expect(payIOUAction?.pendingAction).toBeFalsy();
+
+ resolve();
+ },
+ });
+ }),
+ )
+ );
+ });
+ });
+
+ describe('edit money request', () => {
+ const amount = 10000;
+ const comment = '💸💸💸💸';
+ const merchant = 'NASDAQ';
+
+ afterEach(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+ });
+
+ it('updates the IOU request and IOU report when offline', () => {
+ let thread: OptimisticChatReport;
+ let iouReport: OnyxEntry = null;
+ let iouAction: OnyxEntry = null;
+ let transaction: OnyxEntry = null;
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return waitForBatchedUpdates()
+ .then(() => {
+ Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID});
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+
+ [iouAction] = Object.values(reportActionsForIOUReport ?? {}).filter((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t)) ?? null;
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ thread = ReportUtils.buildTransactionThread(iouAction, iouReport) ?? null;
+ Onyx.set(`report_${thread?.reportID ?? ''}`, thread);
+ return waitForBatchedUpdates();
+ })
+ .then(() => {
+ if (transaction) {
+ IOU.editMoneyRequest(
+ transaction,
+ thread.reportID,
+ {amount: 20000, comment: 'Double the amount!'},
+ {
+ id: '123',
+ role: 'user',
+ type: 'free',
+ name: '',
+ owner: '',
+ outputCurrency: '',
+ isPolicyExpenseChatEnabled: false,
+ },
+ {},
+ {},
+ );
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ const updatedTransaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t));
+ expect(updatedTransaction?.modifiedAmount).toBe(20000);
+ expect(updatedTransaction?.comment).toMatchObject({comment: 'Double the amount!'});
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (allActions) => {
+ Onyx.disconnect(connectionID);
+ const updatedAction = Object.values(allActions ?? {}).find((reportAction) => !isEmptyObject(reportAction));
+ expect(updatedAction?.actionName).toEqual('MODIFIEDEXPENSE');
+ expect(updatedAction?.originalMessage).toEqual(
+ expect.objectContaining({amount: 20000, newComment: 'Double the amount!', oldAmount: amount, oldComment: comment}),
+ );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ const updatedIOUReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ const updatedChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === iouReport?.chatReportID);
+ expect(updatedIOUReport).toEqual(
+ expect.objectContaining({
+ total: 20000,
+ cachedTotal: '$200.00',
+ lastMessageHtml: 'requested $200.00',
+ lastMessageText: 'requested $200.00',
+ }),
+ );
+ expect(updatedChatReport).toEqual(
+ expect.objectContaining({
+ lastMessageHtml: `${CARLOS_EMAIL} owes $200.00`,
+ lastMessageText: `${CARLOS_EMAIL} owes $200.00`,
+ }),
+ );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+ });
+ });
+
+ it('resets the IOU request and IOU report when api returns an error', () => {
+ let thread: OptimisticChatReport;
+ let iouReport: OnyxEntry;
+ let iouAction: OnyxEntry;
+ let transaction: OnyxEntry;
+
+ IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return waitForBatchedUpdates()
+ .then(() => {
+ Onyx.set(ONYXKEYS.SESSION, {email: RORY_EMAIL, accountID: RORY_ACCOUNT_ID});
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ [iouReport] = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+
+ [iouAction] = Object.values(reportActionsForIOUReport ?? {}).filter((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t)) ?? null;
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ thread = ReportUtils.buildTransactionThread(iouAction, iouReport);
+ Onyx.set(`report_${thread.reportID}`, thread);
+ return waitForBatchedUpdates();
+ })
+ .then(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.fail();
+
+ if (transaction) {
+ IOU.editMoneyRequest(
+ transaction,
+ thread.reportID,
+ {amount: 20000, comment: 'Double the amount!'},
+ {
+ id: '123',
+ role: 'user',
+ type: 'free',
+ name: '',
+ owner: '',
+ outputCurrency: '',
+ isPolicyExpenseChatEnabled: false,
+ },
+ {},
+ {},
+ );
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+
+ const updatedTransaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t));
+ expect(updatedTransaction?.modifiedAmount).toBe(undefined);
+ expect(updatedTransaction?.amount).toBe(10000);
+ expect(updatedTransaction?.comment).toMatchObject({comment});
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (allActions) => {
+ Onyx.disconnect(connectionID);
+ const updatedAction = Object.values(allActions ?? {}).find((reportAction) => !isEmptyObject(reportAction));
+ expect(updatedAction?.actionName).toEqual('MODIFIEDEXPENSE');
+ expect(Object.values(updatedAction?.errors ?? {})).toEqual(expect.arrayContaining([['iou.error.genericEditFailureMessage', {isTranslated: false}]]));
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ const updatedIOUReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ const updatedChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === iouReport?.chatReportID);
+ expect(updatedIOUReport).toEqual(
+ expect.objectContaining({
+ total: 10000,
+ cachedTotal: '$100.00',
+ lastMessageHtml: `requested $${amount / 100}.00 for ${comment}`,
+ lastMessageText: `requested $${amount / 100}.00 for ${comment}`,
+ }),
+ );
+ expect(updatedChatReport).toEqual(
+ expect.objectContaining({
+ lastMessageHtml: '',
+ }),
+ );
+ resolve();
+ },
+ });
+ }),
+ );
+ });
+ });
+
+ describe('pay expense report via ACH', () => {
+ const amount = 10000;
+ const comment = '💸💸💸💸';
+ const merchant = 'NASDAQ';
+
+ afterEach(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+ });
+
+ it('updates the expense request and expense report when paid while offline', () => {
+ let expenseReport: OnyxEntry;
+ let chatReport: OnyxEntry;
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(ONYXKEYS.SESSION, {email: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID});
+ return waitForBatchedUpdates()
+ .then(() => {
+ PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ if (chatReport) {
+ IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ if (chatReport && expenseReport) {
+ IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.VBBA, chatReport, expenseReport);
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (allActions) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(allActions ?? {})).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ message: expect.arrayContaining([
+ expect.objectContaining({
+ html: `paid $${amount / 100}.00 with Expensify`,
+ text: `paid $${amount / 100}.00 with Expensify`,
+ }),
+ ]),
+ originalMessage: expect.objectContaining({
+ amount,
+ paymentType: CONST.IOU.PAYMENT_TYPE.VBBA,
+ type: 'pay',
+ }),
+ }),
+ ]),
+ );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ const updatedIOUReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ const updatedChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === expenseReport?.chatReportID);
+ expect(updatedIOUReport).toEqual(
+ expect.objectContaining({
+ lastMessageHtml: `paid $${amount / 100}.00 with Expensify`,
+ lastMessageText: `paid $${amount / 100}.00 with Expensify`,
+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ }),
+ );
+ expect(updatedChatReport).toEqual(
+ expect.objectContaining({
+ lastMessageHtml: `paid $${amount / 100}.00 with Expensify`,
+ lastMessageText: `paid $${amount / 100}.00 with Expensify`,
+ }),
+ );
+ resolve();
+ },
+ });
+ }),
+ );
+ });
+
+ it('shows an error when paying results in an error', () => {
+ let expenseReport: OnyxEntry;
+ let chatReport: OnyxEntry;
+
+ Onyx.set(ONYXKEYS.SESSION, {email: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID});
+ return waitForBatchedUpdates()
+ .then(() => {
+ PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ if (chatReport) {
+ IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.fail();
+ if (chatReport && expenseReport) {
+ IOU.payMoneyRequest('ACH', chatReport, expenseReport);
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (allActions) => {
+ Onyx.disconnect(connectionID);
+ const erroredAction = Object.values(allActions ?? {}).find((action) => !isEmptyObject(action?.errors));
+ expect(Object.values(erroredAction?.errors ?? {})).toEqual(expect.arrayContaining([['iou.error.other', {isTranslated: false}]]));
+ resolve();
+ },
+ });
+ }),
+ );
+ });
+ });
+
+ describe('deleteMoneyRequest', () => {
+ const amount = 10000;
+ const comment = 'Send me money please';
+ let chatReport: OnyxEntry;
+ let iouReport: OnyxEntry;
+ let createIOUAction: OnyxEntry;
+ let transaction: OnyxEntry;
+ let thread: OptimisticChatReport;
+ const TEST_USER_ACCOUNT_ID = 1;
+ const TEST_USER_LOGIN = 'test@test.com';
+ let IOU_REPORT_ID: string;
+ let reportActionID;
+ const REPORT_ACTION: OnyxEntry = {
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ actorAccountID: TEST_USER_ACCOUNT_ID,
+ automatic: false,
+ avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
+ message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment', translationKey: ''}],
+ person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
+ shouldShow: true,
+ created: DateUtils.getDBTime(),
+ reportActionID: '1',
+ originalMessage: {
+ html: '',
+ whisperedTo: [],
+ },
+ };
+
+ let reportActions: OnyxCollection;
+
+ beforeEach(async () => {
+ // Given mocks are cleared and helpers are set up
+ jest.clearAllMocks();
+ PusherHelper.setup();
+
+ // Given a test user is signed in with Onyx setup and some initial data
+ await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN);
+ User.subscribeToUserEvents();
+ await waitForBatchedUpdates();
+ await TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID);
+
+ // When an IOU request for money is made
+ IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', '', TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID, {login: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}, comment, {});
+ await waitForBatchedUpdates();
+
+ // When fetching all reports from Onyx
+ const allReports = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (reports) => {
+ Onyx.disconnect(connectionID);
+ resolve(reports);
+ },
+ });
+ });
+
+ // Then we should have exactly 3 reports
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+
+ // Then one of them should be a chat report with relevant properties
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ expect(chatReport).toBeTruthy();
+ expect(chatReport).toHaveProperty('reportID');
+ expect(chatReport).toHaveProperty('iouReportID');
+
+ // Then one of them should be an IOU report with relevant properties
+ iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+
+ // Then their IDs should reference each other
+ expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
+ expect(iouReport?.chatReportID).toBe(chatReport?.reportID);
+
+ // Storing IOU Report ID for further reference
+ IOU_REPORT_ID = chatReport?.iouReportID ?? '';
+
+ await waitForBatchedUpdates();
+
+ // When fetching all report actions from Onyx
+ const allReportActions = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ Onyx.disconnect(connectionID);
+ resolve(actions);
+ },
+ });
+ });
+
+ // Then we should find an IOU action with specific properties
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+ createIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(createIOUAction).toBeTruthy();
+ expect(createIOUAction?.originalMessage.IOUReportID).toBe(iouReport?.reportID);
+
+ // When fetching all transactions from Onyx
+ const allTransactions = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (transactions) => {
+ Onyx.disconnect(connectionID);
+ resolve(transactions);
+ },
+ });
+ });
+
+ // Then we should find a specific transaction with relevant properties
+ transaction = Object.values(allTransactions ?? {}).find((t) => t) ?? null;
+ expect(transaction).toBeTruthy();
+ expect(transaction?.amount).toBe(amount);
+ expect(transaction?.reportID).toBe(iouReport?.reportID);
+ expect(createIOUAction?.originalMessage.IOUTransactionID).toBe(transaction?.transactionID);
+ });
+
+ afterEach(PusherHelper.teardown);
+
+ it('delete a money request (IOU Action and transaction) successfully', async () => {
+ // Given the fetch operations are paused and a money request is initiated
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+
+ if (transaction && createIOUAction) {
+ // When the money request is deleted
+ IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, true);
+ }
+ await waitForBatchedUpdates();
+
+ // Then we check if the IOU report action is removed from the report actions collection
+ let reportActionsForReport = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (actionsForReport) => {
+ Onyx.disconnect(connectionID);
+ resolve(actionsForReport);
+ },
+ });
+ });
+
+ createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ // Then the IOU Action should be truthy for offline support.
+ expect(createIOUAction).toBeTruthy();
+
+ // Then we check if the transaction is removed from the transactions collection
+ const t = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction?.transactionID}`,
+ waitForCollectionCallback: false,
+ callback: (transactionResult) => {
+ Onyx.disconnect(connectionID);
+ resolve(transactionResult);
+ },
+ });
+ });
+
+ expect(t).toBeFalsy();
+
+ // Given fetch operations are resumed
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+ await waitForBatchedUpdates();
+
+ // Then we recheck the IOU report action from the report actions collection
+ reportActionsForReport = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (actionsForReport) => {
+ Onyx.disconnect(connectionID);
+ resolve(actionsForReport);
+ },
+ });
+ });
+
+ createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction).toBeFalsy();
+
+ // Then we recheck the transaction from the transactions collection
+ const tr = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction?.transactionID}`,
+ waitForCollectionCallback: false,
+ callback: (transactionResult) => {
+ Onyx.disconnect(connectionID);
+ resolve(transactionResult);
+ },
+ });
+ });
+
+ expect(tr).toBeFalsy();
+ });
+
+ it('delete the IOU report when there are no visible comments left in the IOU report', async () => {
+ // Given an IOU report and a paused fetch state
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+
+ if (transaction && createIOUAction) {
+ // When the IOU money request is deleted
+ IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, true);
+ }
+ await waitForBatchedUpdates();
+
+ let report = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (res) => {
+ Onyx.disconnect(connectionID);
+ resolve(res);
+ },
+ });
+ });
+
+ // Then the report should be truthy for offline support
+ expect(report).toBeTruthy();
+
+ // Given the resumed fetch state
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+ await waitForBatchedUpdates();
+
+ report = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (res) => {
+ Onyx.disconnect(connectionID);
+ resolve(res);
+ },
+ });
+ });
+
+ // Then the report should be falsy so that there is no trace of the money request.
+ expect(report).toBeFalsy();
+ });
+
+ it('does not delete the IOU report when there are visible comments left in the IOU report', async () => {
+ // Given the initial setup is completed
+ await waitForBatchedUpdates();
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${IOU_REPORT_ID}`,
+ callback: (val) => (reportActions = val),
+ });
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+
+ // When a comment is added to the IOU report
+ Report.addComment(IOU_REPORT_ID, 'Testing a comment');
+ await waitForBatchedUpdates();
+
+ // Then verify that the comment is correctly added
+ const resultAction = Object.values(reportActions ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
+ reportActionID = resultAction?.reportActionID ?? '';
+
+ expect(resultAction?.message).toEqual(REPORT_ACTION.message);
+ expect(resultAction?.person).toEqual(REPORT_ACTION.person);
+ expect(resultAction?.pendingAction).toBeUndefined();
+
+ await waitForBatchedUpdates();
+
+ // Verify there are three actions (created + iou + addcomment) and our optimistic comment has been removed
+ expect(Object.values(reportActions ?? {}).length).toBe(3);
+
+ // Then check the loading state of our action
+ const resultActionAfterUpdate = reportActions?.[reportActionID];
+ expect(resultActionAfterUpdate?.pendingAction).toBeUndefined();
+
+ // When we attempt to delete a money request from the IOU report
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ if (transaction && createIOUAction) {
+ IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, false);
+ }
+ await waitForBatchedUpdates();
+
+ // Then expect that the IOU report still exists
+ let allReports = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (reports) => {
+ Onyx.disconnect(connectionID);
+ resolve(reports);
+ },
+ });
+ });
+
+ await waitForBatchedUpdates();
+
+ iouReport = Object.values(allReports ?? {}).find((report) => ReportUtils.isIOUReport(report)) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+
+ // Given the resumed fetch state
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+
+ allReports = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (reports) => {
+ Onyx.disconnect(connectionID);
+ resolve(reports);
+ },
+ });
+ });
+ // Then expect that the IOU report still exists
+ iouReport = Object.values(allReports ?? {}).find((report) => ReportUtils.isIOUReport(report)) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+ });
+
+ it('delete the transaction thread if there are no visible comments in the thread', async () => {
+ // Given all promises are resolved
+ await waitForBatchedUpdates();
+ jest.advanceTimersByTime(10);
+
+ // Given a transaction thread
+ thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport);
+
+ expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ callback: (val) => (reportActions = val),
+ });
+
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+
+ // Given User logins from the participant accounts
+ const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []);
+
+ // When Opening a thread report with the given details
+ Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID);
+ await waitForBatchedUpdates();
+
+ // Then The iou action has the transaction report id as a child report ID
+ const allReportActions = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ Onyx.disconnect(connectionID);
+ resolve(actions);
+ },
+ });
+ });
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+ createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction?.childReportID).toBe(thread.reportID);
+
+ await waitForBatchedUpdates();
+
+ // Given Fetch is paused and timers have advanced
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ jest.advanceTimersByTime(10);
+
+ if (transaction && createIOUAction) {
+ // When Deleting a money request
+ IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, false);
+ }
+ await waitForBatchedUpdates();
+
+ // Then The report for the given thread ID does not exist
+ let report = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportData) => {
+ Onyx.disconnect(connectionID);
+ resolve(reportData);
+ },
+ });
+ });
+
+ expect(report).toBeFalsy();
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+
+ // Then After resuming fetch, the report for the given thread ID still does not exist
+ report = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportData) => {
+ Onyx.disconnect(connectionID);
+ resolve(reportData);
+ },
+ });
+ });
+
+ expect(report).toBeFalsy();
+ });
+
+ it('delete the transaction thread if there are only changelogs (i.e. MODIFIEDEXPENSE actions) in the thread', async () => {
+ // Given all promises are resolved
+ await waitForBatchedUpdates();
+ jest.advanceTimersByTime(10);
+
+ // Given a transaction thread
+ thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport);
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ callback: (val) => (reportActions = val),
+ });
+
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+
+ // Given User logins from the participant accounts
+ const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []);
+
+ // When Opening a thread report with the given details
+ Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID);
+ await waitForBatchedUpdates();
+
+ // Then The iou action has the transaction report id as a child report ID
+ const allReportActions = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ Onyx.disconnect(connectionID);
+ resolve(actions);
+ },
+ });
+ });
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+ createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction?.childReportID).toBe(thread.reportID);
+
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+ if (transaction && createIOUAction) {
+ IOU.editMoneyRequest(
+ transaction,
+ thread.reportID,
+ {amount: 20000, comment: 'Double the amount!'},
+ {
+ id: '123',
+ role: 'user',
+ type: 'free',
+ name: '',
+ owner: '',
+ outputCurrency: '',
+ isPolicyExpenseChatEnabled: false,
+ },
+ {},
+ {},
+ );
+ }
+ await waitForBatchedUpdates();
+
+ // Verify there are two actions (created + changelog)
+ expect(Object.values(reportActions ?? {}).length).toBe(2);
+
+ // Fetch the updated IOU Action from Onyx
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForReport) => {
+ Onyx.disconnect(connectionID);
+ createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ resolve();
+ },
+ });
+ });
+
+ if (transaction && createIOUAction) {
+ // When Deleting a money request
+ IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, false);
+ }
+ await waitForBatchedUpdates();
+
+ // Then, the report for the given thread ID does not exist
+ const report = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportData) => {
+ Onyx.disconnect(connectionID);
+ resolve(reportData);
+ },
+ });
+ });
+
+ expect(report).toBeFalsy();
+ });
+
+ it('does not delete the transaction thread if there are visible comments in the thread', async () => {
+ // Given initial environment is set up
+ await waitForBatchedUpdates();
+
+ // Given a transaction thread
+ thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport);
+
+ expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []);
+ jest.advanceTimersByTime(10);
+ Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID);
+ await waitForBatchedUpdates();
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ callback: (val) => (reportActions = val),
+ });
+ await waitForBatchedUpdates();
+
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+ waitForCollectionCallback: true,
+ callback: (report) => {
+ Onyx.disconnect(connectionID);
+ expect(report).toBeTruthy();
+ resolve();
+ },
+ });
+ });
+
+ jest.advanceTimersByTime(10);
+
+ // When a comment is added
+ Report.addComment(thread.reportID, 'Testing a comment');
+ await waitForBatchedUpdates();
+
+ // Then comment details should match the expected report action
+ const resultAction = Object.values(reportActions ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
+ reportActionID = resultAction?.reportActionID ?? '';
+ expect(resultAction?.message).toEqual(REPORT_ACTION.message);
+ expect(resultAction?.person).toEqual(REPORT_ACTION.person);
+
+ await waitForBatchedUpdates();
+
+ // Then the report should have 2 actions
+ expect(Object.values(reportActions ?? {}).length).toBe(2);
+ const resultActionAfter = reportActions?.[reportActionID];
+ expect(resultActionAfter?.pendingAction).toBeUndefined();
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+
+ if (transaction && createIOUAction) {
+ // When deleting money request
+ IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, false);
+ }
+ await waitForBatchedUpdates();
+
+ // Then the transaction thread report should still exist
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (report) => {
+ Onyx.disconnect(connectionID);
+ expect(report).toBeTruthy();
+ resolve();
+ },
+ });
+ });
+
+ // When fetch resumes
+ // Then the transaction thread report should still exist
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+ waitForCollectionCallback: true,
+ callback: (report) => {
+ Onyx.disconnect(connectionID);
+ expect(report).toBeTruthy();
+ resolve();
+ },
+ });
+ });
+ });
+
+ it('update the moneyRequestPreview to show [Deleted request] when appropriate', async () => {
+ await waitForBatchedUpdates();
+
+ // Given a thread report
+
+ jest.advanceTimersByTime(10);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport);
+
+ expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ callback: (val) => (reportActions = val),
+ });
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+ const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []);
+ Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID);
+
+ await waitForBatchedUpdates();
+
+ const allReportActions = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ Onyx.disconnect(connectionID);
+ resolve(actions);
+ },
+ });
+ });
+
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+ createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction?.childReportID).toBe(thread.reportID);
+
+ await waitForBatchedUpdates();
+
+ // Given an added comment to the thread report
+
+ jest.advanceTimersByTime(10);
+
+ Report.addComment(thread.reportID, 'Testing a comment');
+ await waitForBatchedUpdates();
+
+ // Fetch the updated IOU Action from Onyx due to addition of comment to transaction thread.
+ // This needs to be fetched as `deleteMoneyRequest` depends on `childVisibleActionCount` in `createIOUAction`.
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForReport) => {
+ Onyx.disconnect(connectionID);
+ createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ resolve();
+ },
+ });
+ });
+
+ let resultAction = Object.values(reportActions ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
+ reportActionID = resultAction?.reportActionID ?? '';
+
+ expect(resultAction?.message).toEqual(REPORT_ACTION.message);
+ expect(resultAction?.person).toEqual(REPORT_ACTION.person);
+ expect(resultAction?.pendingAction).toBeUndefined();
+
+ await waitForBatchedUpdates();
+
+ // Verify there are three actions (created + addcomment) and our optimistic comment has been removed
+ expect(Object.values(reportActions ?? {}).length).toBe(2);
+
+ let resultActionAfterUpdate = reportActions?.[reportActionID];
+
+ // Verify that our action is no longer in the loading state
+ expect(resultActionAfterUpdate?.pendingAction).toBeUndefined();
+
+ await waitForBatchedUpdates();
+
+ // Given an added comment to the IOU report
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${IOU_REPORT_ID}`,
+ callback: (val) => (reportActions = val),
+ });
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+
+ Report.addComment(IOU_REPORT_ID, 'Testing a comment');
+ await waitForBatchedUpdates();
+
+ resultAction = Object.values(reportActions ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
+ reportActionID = resultAction?.reportActionID ?? '';
+
+ expect(resultAction?.message).toEqual(REPORT_ACTION.message);
+ expect(resultAction?.person).toEqual(REPORT_ACTION.person);
+ expect(resultAction?.pendingAction).toBeUndefined();
+
+ await waitForBatchedUpdates();
+
+ // Verify there are three actions (created + iou + addcomment) and our optimistic comment has been removed
+ expect(Object.values(reportActions ?? {}).length).toBe(3);
+
+ resultActionAfterUpdate = reportActions?.[reportActionID];
+
+ // Verify that our action is no longer in the loading state
+ expect(resultActionAfterUpdate?.pendingAction).toBeUndefined();
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ if (transaction && createIOUAction) {
+ // When we delete the money request
+ IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
+ }
+ await waitForBatchedUpdates();
+
+ // Then we expect the moneyRequestPreview to show [Deleted request]
+
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForReport) => {
+ Onyx.disconnect(connectionID);
+ createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction?.message?.[0].isDeletedParentAction).toBeTruthy();
+ resolve();
+ },
+ });
+ });
+
+ // When we resume fetch
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+
+ // Then we expect the moneyRequestPreview to show [Deleted request]
+
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForReport) => {
+ Onyx.disconnect(connectionID);
+ createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction?.message?.[0].isDeletedParentAction).toBeTruthy();
+ resolve();
+ },
+ });
+ });
+ });
+
+ it('update IOU report and reportPreview with new totals and messages if the IOU report is not deleted', async () => {
+ await waitForBatchedUpdates();
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport?.reportID}`,
+ callback: (val) => (iouReport = val),
+ });
+ await waitForBatchedUpdates();
+
+ // Given a second money request in addition to the first one
+
+ jest.advanceTimersByTime(10);
+ const amount2 = 20000;
+ const comment2 = 'Send me money please 2';
+ if (chatReport) {
+ IOU.requestMoney(chatReport, amount2, CONST.CURRENCY.USD, '', '', TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID, {login: RORY_EMAIL, accountID: RORY_ACCOUNT_ID}, comment2, {});
+ }
+
+ await waitForBatchedUpdates();
+
+ // Then we expect the IOU report and reportPreview to update with new totals
+
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+ expect(iouReport?.total).toBe(30000);
+
+ const ioupreview = ReportActionsUtils.getReportPreviewAction(chatReport?.reportID ?? '', iouReport?.reportID ?? '');
+ expect(ioupreview).toBeTruthy();
+ expect(ioupreview?.message?.[0].text).toBe('rory@expensifail.com owes $300.00');
+
+ // When we delete the first money request
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ jest.advanceTimersByTime(10);
+ if (transaction && createIOUAction) {
+ IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
+ }
+ await waitForBatchedUpdates();
+
+ // Then we expect the IOU report and reportPreview to update with new totals
+
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+ expect(iouReport?.total).toBe(20000);
+
+ // When we resume
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+
+ // Then we expect the IOU report and reportPreview to update with new totals
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+ expect(iouReport?.total).toBe(20000);
+ });
+
+ it('navigate the user correctly to the iou Report when appropriate', async () => {
+ await waitForBatchedUpdates();
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${IOU_REPORT_ID}`,
+ callback: (val) => (reportActions = val),
+ });
+ await waitForBatchedUpdates();
+
+ // Given an added comment to the iou report
+
+ jest.advanceTimersByTime(10);
+
+ Report.addComment(IOU_REPORT_ID, 'Testing a comment');
+ await waitForBatchedUpdates();
+
+ const resultAction = Object.values(reportActions ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
+ reportActionID = resultAction?.reportActionID;
+
+ expect(resultAction?.message).toEqual(REPORT_ACTION.message);
+ expect(resultAction?.person).toEqual(REPORT_ACTION.person);
+
+ await waitForBatchedUpdates();
+
+ // Verify there are three actions (created + iou + addcomment) and our optimistic comment has been removed
+ expect(Object.values(reportActions ?? {}).length).toBe(3);
+
+ await waitForBatchedUpdates();
+
+ // Given a thread report
+
+ jest.advanceTimersByTime(10);
+ thread = ReportUtils.buildTransactionThread(createIOUAction, iouReport);
+
+ expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+
+ Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+ callback: (val) => (reportActions = val),
+ });
+ await waitForBatchedUpdates();
+
+ jest.advanceTimersByTime(10);
+ const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []);
+ Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID);
+ await waitForBatchedUpdates();
+
+ const allReportActions = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (actions) => {
+ Onyx.disconnect(connectionID);
+ resolve(actions);
+ },
+ });
+ });
+
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+ createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction?.childReportID).toBe(thread.reportID);
+
+ await waitForBatchedUpdates();
+
+ // When we delete the money request in SingleTransactionView and we should not delete the IOU report
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+
+ if (transaction && createIOUAction) {
+ IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true);
+ }
+ await waitForBatchedUpdates();
+
+ let allReports = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (reports) => {
+ Onyx.disconnect(connectionID);
+ resolve(reports);
+ },
+ });
+ });
+
+ await waitForBatchedUpdates();
+
+ iouReport = Object.values(allReports ?? {}).find((report) => ReportUtils.isIOUReport(report)) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.resume();
+
+ allReports = await new Promise>((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (reports) => {
+ Onyx.disconnect(connectionID);
+ resolve(reports);
+ },
+ });
+ });
+
+ iouReport = Object.values(allReports ?? {}).find((report) => ReportUtils.isIOUReport(report)) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
+
+ // Then we expect to navigate to the iou report
+
+ expect(Navigation.goBack).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(IOU_REPORT_ID));
+ });
+
+ it('navigate the user correctly to the chat Report when appropriate', () => {
+ if (transaction && createIOUAction) {
+ // When we delete the money request and we should delete the IOU report
+ IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
+ }
+ // Then we expect to navigate to the chat report
+ expect(Navigation.goBack).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? ''));
+ });
+ });
+
+ describe('submitReport', () => {
+ it('correctly submits a report', () => {
+ const amount = 10000;
+ const comment = '💸💸💸💸';
+ const merchant = 'NASDAQ';
+ let expenseReport: OnyxEntry;
+ let chatReport: OnyxEntry;
+ return waitForBatchedUpdates()
+ .then(() => {
+ PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ if (chatReport) {
+ IOU.requestMoney(
+ chatReport,
+ amount,
+ CONST.CURRENCY.USD,
+ '',
+ merchant,
+ RORY_EMAIL,
+ RORY_ACCOUNT_ID,
+ {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID, isPolicyExpenseChat: true, reportID: chatReport.reportID},
+ comment,
+ {},
+ );
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null;
+ Onyx.merge(`report_${expenseReport?.reportID}`, {
+ statusNum: 0,
+ stateNum: 0,
+ });
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null;
+
+ // Verify report is a draft
+ expect(expenseReport?.stateNum).toBe(0);
+ expect(expenseReport?.statusNum).toBe(0);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ if (expenseReport) {
+ IOU.submitReport(expenseReport);
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null;
+
+ // Report was submitted correctly
+ expect(expenseReport?.stateNum).toBe(1);
+ expect(expenseReport?.statusNum).toBe(1);
+ resolve();
+ },
+ });
+ }),
+ );
+ });
+ it('correctly implements error handling', () => {
+ const amount = 10000;
+ const comment = '💸💸💸💸';
+ const merchant = 'NASDAQ';
+ let expenseReport: OnyxEntry;
+ let chatReport: OnyxEntry;
+ return waitForBatchedUpdates()
+ .then(() => {
+ PolicyActions.createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace");
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) ?? null;
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ if (chatReport) {
+ IOU.requestMoney(
+ chatReport,
+ amount,
+ CONST.CURRENCY.USD,
+ '',
+ merchant,
+ RORY_EMAIL,
+ RORY_ACCOUNT_ID,
+ {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID, isPolicyExpenseChat: true, reportID: chatReport.reportID},
+ comment,
+ {},
+ );
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null;
+ Onyx.merge(`report_${expenseReport?.reportID}`, {
+ statusNum: 0,
+ stateNum: 0,
+ });
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null;
+
+ // Verify report is a draft
+ expect(expenseReport?.stateNum).toBe(0);
+ expect(expenseReport?.statusNum).toBe(0);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.fail();
+ if (expenseReport) {
+ IOU.submitReport(expenseReport);
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE) ?? null;
+
+ // Report was submitted with some fail
+ expect(expenseReport?.stateNum).toBe(0);
+ expect(expenseReport?.statusNum).toBe(0);
+ resolve();
+ },
+ });
+ }),
+ );
+ });
+ });
+});
diff --git a/tests/unit/GithubUtilsTest.ts b/tests/unit/GithubUtilsTest.ts
index 794139286527..8267b26d3d0f 100644
--- a/tests/unit/GithubUtilsTest.ts
+++ b/tests/unit/GithubUtilsTest.ts
@@ -355,7 +355,7 @@ describe('GithubUtils', () => {
},
{
number: 6,
- title: '[Internal QA] Test Internal QA PR',
+ title: '[Internal QA] Another Test Internal QA PR',
html_url: 'https://github.com/Expensify/App/pull/6',
user: {login: 'testUser'},
labels: [
@@ -368,14 +368,7 @@ describe('GithubUtils', () => {
color: 'f29513',
},
],
- assignees: [
- {
- login: 'octocat',
- },
- {
- login: 'hubot',
- },
- ],
+ assignees: [],
},
{
number: 7,
@@ -392,16 +385,12 @@ describe('GithubUtils', () => {
color: 'f29513',
},
],
- assignees: [
- {
- login: 'octocat',
- },
- {
- login: 'hubot',
- },
- ],
+ assignees: [],
},
];
+ const mockInternalQaPR = {
+ merged_by: {login: 'octocat'},
+ };
const mockGithub = jest.fn(() => ({
getOctokit: () => ({
rest: {
@@ -410,6 +399,7 @@ describe('GithubUtils', () => {
},
pulls: {
list: jest.fn().mockResolvedValue({data: mockPRs}),
+ get: jest.fn().mockResolvedValue({data: mockInternalQaPR}),
},
},
paginate: jest.fn().mockImplementation((objectMethod: () => Promise>) => objectMethod().then(({data}) => data)),
@@ -446,7 +436,7 @@ describe('GithubUtils', () => {
const internalQAHeader = '\r\n\r\n**Internal QA:**';
const lineBreak = '\r\n';
const lineBreakDouble = '\r\n\r\n';
- const assignOctocatHubot = ' - @octocat @hubot';
+ const assignOctocat = ' - @octocat';
const deployerVerificationsHeader = '\r\n**Deployer verifications:**';
// eslint-disable-next-line max-len
const timingDashboardVerification =
@@ -468,8 +458,8 @@ describe('GithubUtils', () => {
`${lineBreak}`;
test('Test no verified PRs', () => {
- githubUtils.generateStagingDeployCashBody(tag, basePRList).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList).then((issue) => {
+ expect(issue.issueBody).toBe(
`${baseExpectedOutput}` +
`${openCheckbox}${basePRList[2]}` +
`${lineBreak}${openCheckbox}${basePRList[0]}` +
@@ -482,12 +472,13 @@ describe('GithubUtils', () => {
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual([]);
});
});
test('Test some verified PRs', () => {
- githubUtils.generateStagingDeployCashBody(tag, basePRList, [basePRList[0]]).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, [basePRList[0]]).then((issue) => {
+ expect(issue.issueBody).toBe(
`${baseExpectedOutput}` +
`${openCheckbox}${basePRList[2]}` +
`${lineBreak}${closedCheckbox}${basePRList[0]}` +
@@ -500,12 +491,13 @@ describe('GithubUtils', () => {
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual([]);
});
});
test('Test all verified PRs', () => {
- githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList).then((issue) => {
+ expect(issue.issueBody).toBe(
`${allVerifiedExpectedOutput}` +
`${lineBreak}${deployerVerificationsHeader}` +
`${lineBreak}${openCheckbox}${timingDashboardVerification}` +
@@ -513,12 +505,13 @@ describe('GithubUtils', () => {
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual([]);
});
});
test('Test no resolved deploy blockers', () => {
- githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList, baseDeployBlockerList).then((issue) => {
+ expect(issue.issueBody).toBe(
`${allVerifiedExpectedOutput}` +
`${lineBreak}${deployBlockerHeader}` +
`${lineBreak}${openCheckbox}${baseDeployBlockerList[0]}` +
@@ -529,12 +522,13 @@ describe('GithubUtils', () => {
`${lineBreak}${openCheckbox}${ghVerification}${lineBreak}` +
`${lineBreak}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual([]);
});
});
test('Test some resolved deploy blockers', () => {
- githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, [baseDeployBlockerList[0]]).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList, baseDeployBlockerList, [baseDeployBlockerList[0]]).then((issue) => {
+ expect(issue.issueBody).toBe(
`${allVerifiedExpectedOutput}` +
`${lineBreak}${deployBlockerHeader}` +
`${lineBreak}${closedCheckbox}${baseDeployBlockerList[0]}` +
@@ -545,12 +539,13 @@ describe('GithubUtils', () => {
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual([]);
});
});
test('Test all resolved deploy blockers', () => {
- githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, baseDeployBlockerList).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, basePRList, basePRList, baseDeployBlockerList, baseDeployBlockerList).then((issue) => {
+ expect(issue.issueBody).toBe(
`${baseExpectedOutput}` +
`${closedCheckbox}${basePRList[2]}` +
`${lineBreak}${closedCheckbox}${basePRList[0]}` +
@@ -566,12 +561,13 @@ describe('GithubUtils', () => {
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual([]);
});
});
test('Test internalQA PRs', () => {
- githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList]).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, [...basePRList, ...internalQAPRList]).then((issue) => {
+ expect(issue.issueBody).toBe(
`${baseExpectedOutput}` +
`${openCheckbox}${basePRList[2]}` +
`${lineBreak}${openCheckbox}${basePRList[0]}` +
@@ -579,20 +575,21 @@ describe('GithubUtils', () => {
`${lineBreak}${closedCheckbox}${basePRList[4]}` +
`${lineBreak}${closedCheckbox}${basePRList[5]}` +
`${lineBreak}${internalQAHeader}` +
- `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` +
- `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` +
+ `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocat}` +
+ `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocat}` +
`${lineBreakDouble}${deployerVerificationsHeader}` +
`${lineBreak}${openCheckbox}${timingDashboardVerification}` +
`${lineBreak}${openCheckbox}${firebaseVerification}` +
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual(['octocat']);
});
});
test('Test some verified internalQA PRs', () => {
- githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList], [], [], [], [internalQAPRList[0]]).then((issueBody: string) => {
- expect(issueBody).toBe(
+ githubUtils.generateStagingDeployCashBodyAndAssignees(tag, [...basePRList, ...internalQAPRList], [], [], [], [internalQAPRList[0]]).then((issue) => {
+ expect(issue.issueBody).toBe(
`${baseExpectedOutput}` +
`${openCheckbox}${basePRList[2]}` +
`${lineBreak}${openCheckbox}${basePRList[0]}` +
@@ -600,14 +597,15 @@ describe('GithubUtils', () => {
`${lineBreak}${closedCheckbox}${basePRList[4]}` +
`${lineBreak}${closedCheckbox}${basePRList[5]}` +
`${lineBreak}${internalQAHeader}` +
- `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` +
- `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` +
+ `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocat}` +
+ `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocat}` +
`${lineBreakDouble}${deployerVerificationsHeader}` +
`${lineBreak}${openCheckbox}${timingDashboardVerification}` +
`${lineBreak}${openCheckbox}${firebaseVerification}` +
`${lineBreak}${openCheckbox}${ghVerification}` +
`${lineBreakDouble}${ccApplauseLeads}`,
);
+ expect(issue.issueAssignees).toEqual(['octocat']);
});
});
});
diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts
index bf528eca3e81..5f0bb65cc3ab 100644
--- a/tests/unit/ReportActionsUtilsTest.ts
+++ b/tests/unit/ReportActionsUtilsTest.ts
@@ -1783,6 +1783,143 @@ describe('ReportActionsUtils', () => {
input.pop();
expect(result).toStrictEqual(expectedResult.reverse());
});
+
+ it('given an empty input ID and the report only contains pending actions, it will return all actions', () => {
+ const input: ReportAction[] = [
+ // Given these sortedReportActions
+ {
+ reportActionID: '1',
+ previousReportActionID: undefined,
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ {
+ reportActionID: '2',
+ previousReportActionID: '1',
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ {
+ reportActionID: '3',
+ previousReportActionID: '2',
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ {
+ reportActionID: '4',
+ previousReportActionID: '3',
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ {
+ reportActionID: '5',
+ previousReportActionID: '4',
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ {
+ reportActionID: '6',
+ previousReportActionID: '5',
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ {
+ reportActionID: '7',
+ previousReportActionID: '6',
+ created: '2022-11-13 22:27:01.825',
+ actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
+ originalMessage: {
+ html: 'Hello world',
+ whisperedTo: [],
+ },
+ message: [
+ {
+ html: 'Hello world',
+ type: 'Action type',
+ text: 'Action text',
+ },
+ ],
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ ];
+
+ const expectedResult = input;
+ // Reversing the input array to simulate descending order sorting as per our data structure
+ const result = ReportActionsUtils.getContinuousReportActionChain(input.reverse(), '');
+ expect(result).toStrictEqual(expectedResult.reverse());
+ });
});
describe('getLastVisibleAction', () => {
diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js
index 9fbea1df862e..7b563d46b7eb 100644
--- a/tests/unit/ReportUtilsTest.js
+++ b/tests/unit/ReportUtilsTest.js
@@ -1,6 +1,7 @@
import Onyx from 'react-native-onyx';
import _ from 'underscore';
import CONST from '../../src/CONST';
+import * as NumberUtils from '../../src/libs/NumberUtils';
import * as ReportUtils from '../../src/libs/ReportUtils';
import ONYXKEYS from '../../src/ONYXKEYS';
import * as LHNTestUtils from '../utils/LHNTestUtils';
@@ -552,6 +553,7 @@ describe('ReportUtils', () => {
const paidPolicy = {
id: 'ef72dfeb',
type: CONST.POLICY.TYPE.TEAM,
+ autoReporting: true,
autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT,
};
Promise.all([
@@ -638,6 +640,78 @@ describe('ReportUtils', () => {
});
});
+ describe('shouldDisableThread', () => {
+ const reportID = '1';
+
+ it('should disable on thread-disabled actions', () => {
+ const reportAction = ReportUtils.buildOptimisticCreatedReportAction('email1@test.com');
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy();
+ });
+
+ it('should disable thread on split bill actions', () => {
+ const reportAction = ReportUtils.buildOptimisticIOUReportAction(
+ CONST.IOU.REPORT_ACTION_TYPE.SPLIT,
+ 50000,
+ CONST.CURRENCY.USD,
+ '',
+ [{login: 'email1@test.com'}, {login: 'email2@test.com'}],
+ NumberUtils.rand64(),
+ );
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy();
+ });
+
+ it('should disable on deleted and not-thread actions', () => {
+ const reportAction = {
+ message: [
+ {
+ translationKey: '',
+ type: 'COMMENT',
+ html: '',
+ text: '',
+ isEdited: true,
+ },
+ ],
+ childVisibleActionCount: 1,
+ };
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeFalsy();
+
+ reportAction.childVisibleActionCount = 0;
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy();
+ });
+
+ it('should disable on archived reports and not-thread actions', () => {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
+ statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ })
+ .then(() => waitForBatchedUpdates())
+ .then(() => {
+ const reportAction = {
+ childVisibleActionCount: 1,
+ };
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeFalsy();
+
+ reportAction.childVisibleActionCount = 0;
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy();
+ });
+ });
+
+ it("should disable on a whisper action and it's neither a report preview nor IOU action", () => {
+ const reportAction = {
+ actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE,
+ whisperedToAccountIDs: [123456],
+ };
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy();
+ });
+
+ it('should disable on thread first chat', () => {
+ const reportAction = {
+ childReportID: reportID,
+ };
+ expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy();
+ });
+ });
+
describe('getAllAncestorReportActions', () => {
const reports = [
{reportID: '1', lastReadTime: '2024-02-01 04:56:47.233', reportName: 'Report'},
diff --git a/wdyr.js b/wdyr.ts
similarity index 76%
rename from wdyr.js
rename to wdyr.ts
index ad1edaa5a075..e5b40bc3b4c7 100644
--- a/wdyr.js
+++ b/wdyr.ts
@@ -1,12 +1,12 @@
// Implements Why Did You Render (WDYR) in Dev
-import lodashGet from 'lodash/get';
+import type WhyDidYouRender from '@welldone-software/why-did-you-render';
import React from 'react';
import Config from 'react-native-config';
-const useWDYR = lodashGet(Config, 'USE_WDYR') === 'true';
+const useWDYR = Config?.USE_WDYR === 'true';
if (useWDYR) {
- const whyDidYouRender = require('@welldone-software/why-did-you-render');
+ const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
// Enable tracking in all pure components by default
trackAllPureComponents: true,
diff --git a/workflow_tests/mocks/authorChecklistMocks.js b/workflow_tests/mocks/authorChecklistMocks.js
deleted file mode 100644
index b0fda86815db..000000000000
--- a/workflow_tests/mocks/authorChecklistMocks.js
+++ /dev/null
@@ -1,10 +0,0 @@
-const utils = require('../utils/utils');
-
-// checklist
-const AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'CHECKLIST');
-const AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK = utils.createMockStep('authorChecklist.js', 'Running authorChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []);
-const AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS = [AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK, AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK];
-
-module.exports = {
- AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/authorChecklistMocks.ts b/workflow_tests/mocks/authorChecklistMocks.ts
new file mode 100644
index 000000000000..b4d520eb7a1f
--- /dev/null
+++ b/workflow_tests/mocks/authorChecklistMocks.ts
@@ -0,0 +1,12 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// checklist
+const AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'CHECKLIST');
+const AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK = createMockStep('authorChecklist.js', 'Running authorChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []);
+const AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS = [AUTHORCHECKLIST__CHECKLIST__CHECKOUT__STEP_MOCK, AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK] as const;
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS,
+};
diff --git a/workflow_tests/mocks/cherryPickMocks.js b/workflow_tests/mocks/cherryPickMocks.ts
similarity index 51%
rename from workflow_tests/mocks/cherryPickMocks.js
rename to workflow_tests/mocks/cherryPickMocks.ts
index 5ce9b2ecccfb..f1508a3dc39d 100644
--- a/workflow_tests/mocks/cherryPickMocks.js
+++ b/workflow_tests/mocks/cherryPickMocks.ts
@@ -1,7 +1,8 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// validateactor
-const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK = createMockStep(
'Check if user is deployer',
'Checking if user is a deployer',
'VALIDATEACTOR',
@@ -9,7 +10,7 @@ const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK = uti
['GITHUB_TOKEN'],
{IS_DEPLOYER: true},
);
-const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK = createMockStep(
'Check if user is deployer',
'Checking if user is a deployer',
'VALIDATEACTOR',
@@ -17,11 +18,11 @@ const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK = ut
['GITHUB_TOKEN'],
{IS_DEPLOYER: false},
);
-const CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK];
-const CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK];
+const CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK] as const;
+const CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK] as const;
// createnewversion
-const CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK = createMockStep(
'Create new version',
'Creating new version',
'CREATENEWVERSION',
@@ -32,19 +33,19 @@ const CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK = utils.create
true,
'createNewVersion',
);
-const CHERRYPICK__CREATENEWVERSION__STEP_MOCKS = [CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK];
+const CHERRYPICK__CREATENEWVERSION__STEP_MOCKS = [CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK] as const;
// cherrypick
-const CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK = utils.createMockStep('Checkout staging branch', 'Checking out staging branch', 'CHERRYPICK', ['ref', 'token'], []);
-const CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep('Set up git for OSBotify', 'Setting up git for OSBotify', 'CHERRYPICK', ['GPG_PASSPHRASE'], [], {
+const CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK = createMockStep('Checkout staging branch', 'Checking out staging branch', 'CHERRYPICK', ['ref', 'token'], []);
+const CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK = createMockStep('Set up git for OSBotify', 'Setting up git for OSBotify', 'CHERRYPICK', ['GPG_PASSPHRASE'], [], {
OS_BOTIFY_API_TOKEN: 'os_botify_api_token',
});
-const CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK = utils.createMockStep('Get previous app version', 'Get previous app version', 'CHERRYPICK', ['SEMVER_LEVEL']);
-const CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK = utils.createMockStep('Fetch history of relevant refs', 'Fetch history of relevant refs', 'CHERRYPICK');
-const CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK = utils.createMockStep('Get version bump commit', 'Get version bump commit', 'CHERRYPICK', [], [], {
+const CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK = createMockStep('Get previous app version', 'Get previous app version', 'CHERRYPICK', ['SEMVER_LEVEL']);
+const CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK = createMockStep('Fetch history of relevant refs', 'Fetch history of relevant refs', 'CHERRYPICK');
+const CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK = createMockStep('Get version bump commit', 'Get version bump commit', 'CHERRYPICK', [], [], {
VERSION_BUMP_SHA: 'version_bump_sha',
});
-const CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK = createMockStep(
'Get merge commit for pull request to CP',
'Get merge commit for pull request to CP',
'CHERRYPICK',
@@ -52,14 +53,14 @@ const CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK
[],
{MERGE_ACTOR: '@dummyauthor'},
);
-const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK = createMockStep(
'Cherry-pick the version-bump to staging',
'Cherry-picking the version-bump to staging',
'CHERRYPICK',
[],
[],
);
-const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK = createMockStep(
'Cherry-pick the merge commit of target PR',
'Cherry-picking the merge commit of target PR',
'CHERRYPICK',
@@ -68,7 +69,7 @@ const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_B
{HAS_CONFLICTS: false},
);
// eslint-disable-next-line rulesdir/no-negated-variables
-const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK = createMockStep(
'Cherry-pick the merge commit of target PR',
'Cherry-picking the merge commit of target PR',
'CHERRYPICK',
@@ -76,15 +77,15 @@ const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_B
[],
{HAS_CONFLICTS: true},
);
-const CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK = utils.createMockStep('Push changes', 'Pushing changes', 'CHERRYPICK', [], []);
-const CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK = createMockStep('Push changes', 'Pushing changes', 'CHERRYPICK', [], []);
+const CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK = createMockStep(
'Create Pull Request to manually finish CP',
'Creating Pull Request to manually finish CP',
'CHERRYPICK',
[],
['GITHUB_TOKEN'],
);
-const CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK = utils.createMockStep(
+const CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK = createMockStep(
'Announces a CP failure in the #announce Slack room',
'Announcing a CP failure',
'CHERRYPICK',
@@ -92,25 +93,21 @@ const CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM_
['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'],
);
-const getCherryPickMockSteps = (upToDate, hasConflicts) => [
- CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK,
- hasConflicts
- ? CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK
- : CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK,
- CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK,
-];
+const getCherryPickMockSteps = (upToDate: boolean, hasConflicts: boolean) =>
+ [
+ CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK,
+ hasConflicts
+ ? CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK
+ : CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK,
+ CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK,
+ ] as const;
-module.exports = {
- CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS,
- CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS,
- CHERRYPICK__CREATENEWVERSION__STEP_MOCKS,
- getCherryPickMockSteps,
-};
+export {CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS, CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, getCherryPickMockSteps};
diff --git a/workflow_tests/mocks/claMocks.js b/workflow_tests/mocks/claMocks.js
deleted file mode 100644
index d0a6793b93e6..000000000000
--- a/workflow_tests/mocks/claMocks.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const utils = require('../utils/utils');
-
-// cla
-const CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK = utils.createMockStep('CLA comment check', 'CLA comment check', 'CLA', ['text', 'regex'], [], {match: ''});
-const CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK = utils.createMockStep('CLA comment check', 'CLA comment check', 'CLA', ['text', 'regex'], [], {
- match: 'I have read the CLA Document and I hereby sign the CLA',
-});
-const CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK = utils.createMockStep('CLA comment re-check', 'CLA comment re-check', 'CLA', ['text', 'regex'], [], {match: ''});
-const CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK = utils.createMockStep('CLA comment re-check', 'CLA comment re-check', 'CLA', ['text', 'regex'], [], {match: 'recheck'});
-const CLA__CLA__CLA_ASSISTANT__STEP_MOCK = utils.createMockStep(
- 'CLA Assistant',
- 'CLA Assistant',
- 'CLA',
- ['path-to-signatures', 'path-to-document', 'branch', 'remote-organization-name', 'remote-repository-name', 'lock-pullrequest-aftermerge', 'allowlist'],
- ['GITHUB_TOKEN', 'PERSONAL_ACCESS_TOKEN'],
-);
-const CLA__CLA__NO_MATCHES__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK];
-const CLA__CLA__CHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK];
-const CLA__CLA__RECHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK];
-
-module.exports = {
- CLA__CLA__NO_MATCHES__STEP_MOCKS,
- CLA__CLA__CHECK_MATCH__STEP_MOCKS,
- CLA__CLA__RECHECK_MATCH__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/claMocks.ts b/workflow_tests/mocks/claMocks.ts
new file mode 100644
index 000000000000..ab2d5f15a756
--- /dev/null
+++ b/workflow_tests/mocks/claMocks.ts
@@ -0,0 +1,22 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// cla
+const CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK = createMockStep('CLA comment check', 'CLA comment check', 'CLA', ['text', 'regex'], [], {match: ''});
+const CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK = createMockStep('CLA comment check', 'CLA comment check', 'CLA', ['text', 'regex'], [], {
+ match: 'I have read the CLA Document and I hereby sign the CLA',
+});
+const CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK = createMockStep('CLA comment re-check', 'CLA comment re-check', 'CLA', ['text', 'regex'], [], {match: ''});
+const CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK = createMockStep('CLA comment re-check', 'CLA comment re-check', 'CLA', ['text', 'regex'], [], {match: 'recheck'});
+const CLA__CLA__CLA_ASSISTANT__STEP_MOCK = createMockStep(
+ 'CLA Assistant',
+ 'CLA Assistant',
+ 'CLA',
+ ['path-to-signatures', 'path-to-document', 'branch', 'remote-organization-name', 'remote-repository-name', 'lock-pullrequest-aftermerge', 'allowlist'],
+ ['GITHUB_TOKEN', 'PERSONAL_ACCESS_TOKEN'],
+);
+const CLA__CLA__NO_MATCHES__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK] as const;
+const CLA__CLA__CHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK] as const;
+const CLA__CLA__RECHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK] as const;
+
+export {CLA__CLA__NO_MATCHES__STEP_MOCKS, CLA__CLA__CHECK_MATCH__STEP_MOCKS, CLA__CLA__RECHECK_MATCH__STEP_MOCKS};
diff --git a/workflow_tests/mocks/createNewVersionMocks.js b/workflow_tests/mocks/createNewVersionMocks.ts
similarity index 62%
rename from workflow_tests/mocks/createNewVersionMocks.js
rename to workflow_tests/mocks/createNewVersionMocks.ts
index a1f601aef47f..59aa6bc910a2 100644
--- a/workflow_tests/mocks/createNewVersionMocks.js
+++ b/workflow_tests/mocks/createNewVersionMocks.ts
@@ -1,39 +1,28 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// validateactor
-const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK = utils.createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
+const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK = createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
PERMISSION: 'admin',
});
-const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK = utils.createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
+const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK = createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
PERMISSION: 'write',
});
-const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK = utils.createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
+const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK = createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
PERMISSION: 'read',
});
-const CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK];
-const CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK];
-const CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK];
+const CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK] as const;
+const CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK] as const;
+const CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK] as const;
// createnewversion
-const CREATENEWVERSION__CREATENEWVERSION__RUN_TURNSTYLE__STEP_MOCK = utils.createMockStep('Run turnstyle', 'Run turnstyle', 'CREATENEWVERSION', ['poll-interval-seconds'], ['GITHUB_TOKEN']);
-const CREATENEWVERSION__CREATENEWVERSION__CHECK_OUT__STEP_MOCK = utils.createMockStep('Check out', 'Check out', 'CREATENEWVERSION', ['ref', 'token'], []);
-const CREATENEWVERSION__CREATENEWVERSION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep(
- 'Setup git for OSBotify',
- 'Setup git for OSBotify',
- 'CREATENEWVERSION',
- ['GPG_PASSPHRASE'],
- [],
-);
-const CREATENEWVERSION__CREATENEWVERSION__GENERATE_VERSION__STEP_MOCK = utils.createMockStep(
- 'Generate version',
- 'Generate version',
- 'CREATENEWVERSION',
- ['GITHUB_TOKEN', 'SEMVER_LEVEL'],
- [],
-);
-const CREATENEWVERSION__CREATENEWVERSION__COMMIT_NEW_VERSION__STEP_MOCK = utils.createMockStep('Commit new version', 'Commit new version', 'CREATENEWVERSION', [], []);
-const CREATENEWVERSION__CREATENEWVERSION__UPDATE_MAIN_BRANCH__STEP_MOCK = utils.createMockStep('Update main branch', 'Update main branch', 'CREATENEWVERSION', [], []);
-const CREATENEWVERSION__CREATENEWVERSION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep(
+const CREATENEWVERSION__CREATENEWVERSION__RUN_TURNSTYLE__STEP_MOCK = createMockStep('Run turnstyle', 'Run turnstyle', 'CREATENEWVERSION', ['poll-interval-seconds'], ['GITHUB_TOKEN']);
+const CREATENEWVERSION__CREATENEWVERSION__CHECK_OUT__STEP_MOCK = createMockStep('Check out', 'Check out', 'CREATENEWVERSION', ['ref', 'token'], []);
+const CREATENEWVERSION__CREATENEWVERSION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = createMockStep('Setup git for OSBotify', 'Setup git for OSBotify', 'CREATENEWVERSION', ['GPG_PASSPHRASE'], []);
+const CREATENEWVERSION__CREATENEWVERSION__GENERATE_VERSION__STEP_MOCK = createMockStep('Generate version', 'Generate version', 'CREATENEWVERSION', ['GITHUB_TOKEN', 'SEMVER_LEVEL'], []);
+const CREATENEWVERSION__CREATENEWVERSION__COMMIT_NEW_VERSION__STEP_MOCK = createMockStep('Commit new version', 'Commit new version', 'CREATENEWVERSION', [], []);
+const CREATENEWVERSION__CREATENEWVERSION__UPDATE_MAIN_BRANCH__STEP_MOCK = createMockStep('Update main branch', 'Update main branch', 'CREATENEWVERSION', [], []);
+const CREATENEWVERSION__CREATENEWVERSION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = createMockStep(
'Announce failed workflow in Slack',
'Announce failed workflow in Slack',
'CREATENEWVERSION',
@@ -48,9 +37,9 @@ const CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS = [
CREATENEWVERSION__CREATENEWVERSION__COMMIT_NEW_VERSION__STEP_MOCK,
CREATENEWVERSION__CREATENEWVERSION__UPDATE_MAIN_BRANCH__STEP_MOCK,
CREATENEWVERSION__CREATENEWVERSION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
-module.exports = {
+export {
CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS,
CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS,
CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS,
diff --git a/workflow_tests/mocks/deployBlockerMocks.js b/workflow_tests/mocks/deployBlockerMocks.ts
similarity index 60%
rename from workflow_tests/mocks/deployBlockerMocks.js
rename to workflow_tests/mocks/deployBlockerMocks.ts
index 4f74e0b91ebb..932d4626b67b 100644
--- a/workflow_tests/mocks/deployBlockerMocks.js
+++ b/workflow_tests/mocks/deployBlockerMocks.ts
@@ -1,33 +1,28 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// updateChecklist
-const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK = utils.createMockStep('updateChecklist', 'Run updateChecklist', 'UPDATECHECKLIST');
-const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS = [DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK];
+const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK = createMockStep('updateChecklist', 'Run updateChecklist', 'UPDATECHECKLIST');
+const DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS = [DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCK] as const;
// deployblocker
-const DEPLOYBLOCKER__DEPLOYBLOCKER__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'DEPLOYBLOCKER');
-const DEPLOYBLOCKER__DEPLOYBLOCKER__GIVE_LABELS__STEP_MOCK = utils.createMockStep(
+const DEPLOYBLOCKER__DEPLOYBLOCKER__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'DEPLOYBLOCKER');
+const DEPLOYBLOCKER__DEPLOYBLOCKER__GIVE_LABELS__STEP_MOCK = createMockStep(
'Give the issue/PR the Hourly, Engineering labels',
'Give the issue/PR the Hourly, Engineering labels',
'DEPLOYBLOCKER',
[],
['GITHUB_TOKEN'],
);
-const DEPLOYBLOCKER__DEPLOYBLOCKER__POST_THE_ISSUE_IN_THE_EXPENSIFY_OPEN_SOURCE_SLACK_ROOM__STEP_MOCK = utils.createMockStep(
+const DEPLOYBLOCKER__DEPLOYBLOCKER__POST_THE_ISSUE_IN_THE_EXPENSIFY_OPEN_SOURCE_SLACK_ROOM__STEP_MOCK = createMockStep(
'Post the issue in the #expensify-open-source slack room',
'Post the issue in the expensify-open-source slack room',
'DEPLOYBLOCKER',
['status'],
['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'],
);
-const DEPLOYBLOCKER__DEPLOYBLOCKER__COMMENT_ON_DEPLOY_BLOCKER__STEP_MOCK = utils.createMockStep(
- 'Comment on deploy blocker',
- 'Comment on deploy blocker',
- 'DEPLOYBLOCKER',
- [],
- ['GITHUB_TOKEN'],
-);
-const DEPLOYBLOCKER__DEPLOYBLOCKER__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep(
+const DEPLOYBLOCKER__DEPLOYBLOCKER__COMMENT_ON_DEPLOY_BLOCKER__STEP_MOCK = createMockStep('Comment on deploy blocker', 'Comment on deploy blocker', 'DEPLOYBLOCKER', [], ['GITHUB_TOKEN']);
+const DEPLOYBLOCKER__DEPLOYBLOCKER__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = createMockStep(
'Announce failed workflow in Slack',
'Announce failed workflow in Slack',
'DEPLOYBLOCKER',
@@ -40,9 +35,6 @@ const DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS = [
DEPLOYBLOCKER__DEPLOYBLOCKER__POST_THE_ISSUE_IN_THE_EXPENSIFY_OPEN_SOURCE_SLACK_ROOM__STEP_MOCK,
DEPLOYBLOCKER__DEPLOYBLOCKER__COMMENT_ON_DEPLOY_BLOCKER__STEP_MOCK,
DEPLOYBLOCKER__DEPLOYBLOCKER__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
-module.exports = {
- DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS,
- DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS,
-};
+export {DEPLOYBLOCKER__UPDATECHECKLIST__STEP_MOCKS, DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS};
diff --git a/workflow_tests/mocks/deployMocks.js b/workflow_tests/mocks/deployMocks.js
deleted file mode 100644
index 9e8b978f05ac..000000000000
--- a/workflow_tests/mocks/deployMocks.js
+++ /dev/null
@@ -1,55 +0,0 @@
-const utils = require('../utils/utils');
-
-const DEPLOY_STAGING__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout staging branch', 'Checking out staging branch', 'DEPLOY_STAGING', ['ref', 'token']);
-const DEPLOY_STAGING__SETUP_GIT__STEP_MOCK = utils.createMockStep('Setup git for OSBotify', 'Setting up git for OSBotify', 'DEPLOY_STAGING', [
- 'GPG_PASSPHRASE',
- 'OS_BOTIFY_APP_ID',
- 'OS_BOTIFY_PRIVATE_KEY',
-]);
-const DEPLOY_STAGING__TAG_VERSION__STEP_MOCK = utils.createMockStep('Tag version', 'Tagging new version', 'DEPLOY_STAGING');
-const DEPLOY_STAGING__PUSH_TAG__STEP_MOCK = utils.createMockStep('🚀 Push tags to trigger staging deploy 🚀', 'Pushing tag to trigger staging deploy', 'DEPLOY_STAGING');
-const DEPLOY_STAGING_STEP_MOCKS = [DEPLOY_STAGING__CHECKOUT__STEP_MOCK, DEPLOY_STAGING__SETUP_GIT__STEP_MOCK, DEPLOY_STAGING__TAG_VERSION__STEP_MOCK, DEPLOY_STAGING__PUSH_TAG__STEP_MOCK];
-
-const DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'DEPLOY_PRODUCTION', ['ref', 'token']);
-const DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK = utils.createMockStep(
- 'Setup git for OSBotify',
- 'Setting up git for OSBotify',
- 'DEPLOY_PRODUCTION',
- ['GPG_PASSPHRASE', 'OS_BOTIFY_APP_ID', 'OS_BOTIFY_PRIVATE_KEY'],
- null,
- {OS_BOTIFY_API_TOKEN: 'os_botify_api_token'},
-);
-const DEPLOY_PRODUCTION__CURRENT_APP_VERSION__STEP_MOCK = utils.createMockStep('Get current app version', 'Getting current app version', 'DEPLOY_PRODUCTION', null, null, null, {
- PRODUCTION_VERSION: '1.2.3',
-});
-const DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK = utils.createMockStep(
- 'Get Release Pull Request List',
- 'Getting release PR list',
- 'DEPLOY_PRODUCTION',
- ['TAG', 'GITHUB_TOKEN', 'IS_PRODUCTION_DEPLOY'],
- null,
- {PR_LIST: '["1.2.1", "1.2.2"]'},
-);
-const DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK = utils.createMockStep('Generate Release Body', 'Generating release body', 'DEPLOY_PRODUCTION', ['PR_LIST'], null, {
- RELEASE_BODY: 'Release body',
-});
-const DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK = utils.createMockStep(
- '🚀 Create release to trigger production deploy 🚀',
- 'Creating release to trigger production deploy',
- 'DEPLOY_PRODUCTION',
- ['tag_name', 'body'],
- ['GITHUB_TOKEN'],
-);
-const DEPLOY_PRODUCTION_STEP_MOCKS = [
- DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK,
- DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK,
- DEPLOY_PRODUCTION__CURRENT_APP_VERSION__STEP_MOCK,
- DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK,
- DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK,
- DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK,
-];
-
-module.exports = {
- DEPLOY_STAGING_STEP_MOCKS,
- DEPLOY_PRODUCTION_STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/deployMocks.ts b/workflow_tests/mocks/deployMocks.ts
new file mode 100644
index 000000000000..d0795477cfca
--- /dev/null
+++ b/workflow_tests/mocks/deployMocks.ts
@@ -0,0 +1,58 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+const DEPLOY_STAGING__CHECKOUT__STEP_MOCK = createMockStep('Checkout staging branch', 'Checking out staging branch', 'DEPLOY_STAGING', ['ref', 'token']);
+const DEPLOY_STAGING__SETUP_GIT__STEP_MOCK = createMockStep('Setup git for OSBotify', 'Setting up git for OSBotify', 'DEPLOY_STAGING', [
+ 'GPG_PASSPHRASE',
+ 'OS_BOTIFY_APP_ID',
+ 'OS_BOTIFY_PRIVATE_KEY',
+]);
+const DEPLOY_STAGING__TAG_VERSION__STEP_MOCK = createMockStep('Tag version', 'Tagging new version', 'DEPLOY_STAGING');
+const DEPLOY_STAGING__PUSH_TAG__STEP_MOCK = createMockStep('🚀 Push tags to trigger staging deploy 🚀', 'Pushing tag to trigger staging deploy', 'DEPLOY_STAGING');
+const DEPLOY_STAGING_STEP_MOCKS = [
+ DEPLOY_STAGING__CHECKOUT__STEP_MOCK,
+ DEPLOY_STAGING__SETUP_GIT__STEP_MOCK,
+ DEPLOY_STAGING__TAG_VERSION__STEP_MOCK,
+ DEPLOY_STAGING__PUSH_TAG__STEP_MOCK,
+] as const;
+
+const DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'DEPLOY_PRODUCTION', ['ref', 'token']);
+const DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK = createMockStep(
+ 'Setup git for OSBotify',
+ 'Setting up git for OSBotify',
+ 'DEPLOY_PRODUCTION',
+ ['GPG_PASSPHRASE', 'OS_BOTIFY_APP_ID', 'OS_BOTIFY_PRIVATE_KEY'],
+ null,
+ {OS_BOTIFY_API_TOKEN: 'os_botify_api_token'},
+);
+const DEPLOY_PRODUCTION__CURRENT_APP_VERSION__STEP_MOCK = createMockStep('Get current app version', 'Getting current app version', 'DEPLOY_PRODUCTION', null, null, null, {
+ PRODUCTION_VERSION: '1.2.3',
+});
+const DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK = createMockStep(
+ 'Get Release Pull Request List',
+ 'Getting release PR list',
+ 'DEPLOY_PRODUCTION',
+ ['TAG', 'GITHUB_TOKEN', 'IS_PRODUCTION_DEPLOY'],
+ null,
+ {PR_LIST: '["1.2.1", "1.2.2"]'},
+);
+const DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK = createMockStep('Generate Release Body', 'Generating release body', 'DEPLOY_PRODUCTION', ['PR_LIST'], null, {
+ RELEASE_BODY: 'Release body',
+});
+const DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK = createMockStep(
+ '🚀 Create release to trigger production deploy 🚀',
+ 'Creating release to trigger production deploy',
+ 'DEPLOY_PRODUCTION',
+ ['tag_name', 'body'],
+ ['GITHUB_TOKEN'],
+);
+const DEPLOY_PRODUCTION_STEP_MOCKS = [
+ DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK,
+ DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK,
+ DEPLOY_PRODUCTION__CURRENT_APP_VERSION__STEP_MOCK,
+ DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK,
+ DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK,
+ DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK,
+] as const;
+
+export {DEPLOY_STAGING_STEP_MOCKS, DEPLOY_PRODUCTION_STEP_MOCKS};
diff --git a/workflow_tests/mocks/failureNotifierMocks.js b/workflow_tests/mocks/failureNotifierMocks.js
deleted file mode 100644
index 6d37f08ff7ac..000000000000
--- a/workflow_tests/mocks/failureNotifierMocks.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/* eslint-disable rulesdir/no-negated-variables */
-const utils = require('../utils/utils');
-
-// notifyfailure
-const FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK = utils.createMockStep('Fetch Workflow Run Jobs', 'Fetch Workflow Run Jobs', 'NOTIFYFAILURE', [], []);
-const FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK = utils.createMockStep('Process Each Failed Job', 'Process Each Failed Job', 'NOTIFYFAILURE', [], []);
-const FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS = [FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK, FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK];
-
-module.exports = {
- FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/failureNotifierMocks.ts b/workflow_tests/mocks/failureNotifierMocks.ts
new file mode 100644
index 000000000000..5c869adae21b
--- /dev/null
+++ b/workflow_tests/mocks/failureNotifierMocks.ts
@@ -0,0 +1,17 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+
+/* eslint-disable rulesdir/no-negated-variables */
+import {createMockStep} from '../utils/utils';
+
+// notifyfailure
+const FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK = createMockStep('Fetch Workflow Run Jobs', 'Fetch Workflow Run Jobs', 'NOTIFYFAILURE', [], []);
+const FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK = createMockStep('Process Each Failed Job', 'Process Each Failed Job', 'NOTIFYFAILURE', [], []);
+const FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS = [
+ FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK,
+ FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK,
+] as const;
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS,
+};
diff --git a/workflow_tests/mocks/finishReleaseCycleMocks.js b/workflow_tests/mocks/finishReleaseCycleMocks.ts
similarity index 84%
rename from workflow_tests/mocks/finishReleaseCycleMocks.js
rename to workflow_tests/mocks/finishReleaseCycleMocks.ts
index 29e0842852dc..360bb017da88 100644
--- a/workflow_tests/mocks/finishReleaseCycleMocks.js
+++ b/workflow_tests/mocks/finishReleaseCycleMocks.ts
@@ -1,8 +1,9 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// validate
-const FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'VALIDATE', ['ref', 'token']);
-const FINISHRELEASECYCLE__VALIDATE__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'VALIDATE', ['ref', 'token']);
+const FINISHRELEASECYCLE__VALIDATE__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = createMockStep(
'Setup git for OSBotify',
'Setup git for OSBotify',
'VALIDATE',
@@ -10,7 +11,7 @@ const FINISHRELEASECYCLE__VALIDATE__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.cr
[],
{OS_BOTIFY_API_TOKEN: 'os_botify_api_token'},
);
-const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_TRUE__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_TRUE__STEP_MOCK = createMockStep(
'Validate actor is deployer',
'Validating if actor is deployer',
'VALIDATE',
@@ -18,7 +19,7 @@ const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_TRUE__STEP_MOCK =
['GITHUB_TOKEN'],
{IS_DEPLOYER: true},
);
-const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_FALSE__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_FALSE__STEP_MOCK = createMockStep(
'Validate actor is deployer',
'Validating if actor is deployer',
'VALIDATE',
@@ -27,14 +28,14 @@ const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_FALSE__STEP_MOCK
{IS_DEPLOYER: false},
);
// eslint-disable-next-line rulesdir/no-negated-variables
-const FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK = createMockStep(
'Reopen and comment on issue (not a team member)',
'Reopening issue - not a team member',
'VALIDATE',
['GITHUB_TOKEN', 'ISSUE_NUMBER', 'COMMENT'],
[],
);
-const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK = createMockStep(
'Check for any deploy blockers',
'Checking for deploy blockers',
'VALIDATE',
@@ -42,7 +43,7 @@ const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MO
[],
{HAS_DEPLOY_BLOCKERS: false},
);
-const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK = createMockStep(
'Check for any deploy blockers',
'Checking for deploy blockers',
'VALIDATE',
@@ -50,14 +51,14 @@ const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOC
[],
{HAS_DEPLOY_BLOCKERS: true},
);
-const FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK = createMockStep(
'Reopen and comment on issue (has blockers)',
'Reopening issue - blockers',
'VALIDATE',
['GITHUB_TOKEN', 'ISSUE_NUMBER'],
[],
);
-const FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = createMockStep(
'Announce failed workflow in Slack',
'Announce failed workflow in Slack',
'VALIDATE',
@@ -72,7 +73,7 @@ const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK,
@@ -81,7 +82,7 @@ const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
// eslint-disable-next-line rulesdir/no-negated-variables
const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK,
@@ -91,7 +92,7 @@ const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
// eslint-disable-next-line rulesdir/no-negated-variables
const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECKOUT__STEP_MOCK,
@@ -101,19 +102,19 @@ const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [
FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK,
FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
// updateproduction
-const FINISHRELEASECYCLE__UPDATEPRODUCTION__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'UPDATEPRODUCTION', ['ref', 'token'], []);
-const FINISHRELEASECYCLE__UPDATEPRODUCTION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__UPDATEPRODUCTION__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'UPDATEPRODUCTION', ['ref', 'token'], []);
+const FINISHRELEASECYCLE__UPDATEPRODUCTION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = createMockStep(
'Setup git for OSBotify',
'Setup git for OSBotify',
'UPDATEPRODUCTION',
['GPG_PASSPHRASE'],
[],
);
-const FINISHRELEASECYCLE__UPDATEPRODUCTION__UPDATE_PRODUCTION_BRANCH__STEP_MOCK = utils.createMockStep('Update production branch', 'Updating production branch', 'UPDATEPRODUCTION', [], []);
-const FINISHRELEASECYCLE__UPDATEPRODUCTION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__UPDATEPRODUCTION__UPDATE_PRODUCTION_BRANCH__STEP_MOCK = createMockStep('Update production branch', 'Updating production branch', 'UPDATEPRODUCTION', [], []);
+const FINISHRELEASECYCLE__UPDATEPRODUCTION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = createMockStep(
'Announce failed workflow in Slack',
'Announce failed workflow in Slack',
'UPDATEPRODUCTION',
@@ -125,10 +126,10 @@ const FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS = [
FINISHRELEASECYCLE__UPDATEPRODUCTION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK,
FINISHRELEASECYCLE__UPDATEPRODUCTION__UPDATE_PRODUCTION_BRANCH__STEP_MOCK,
FINISHRELEASECYCLE__UPDATEPRODUCTION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
// createnewpatchversion
-const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK = createMockStep(
'Create new version',
'Creating new version',
'CREATENEWPATCHVERSION',
@@ -139,25 +140,19 @@ const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK =
true,
'createNewVersion',
);
-const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS = [FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK];
+const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS = [FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK] as const;
// updatestaging
-const FINISHRELEASECYCLE__UPDATESTAGING__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'UPDATESTAGING', ['ref', 'token'], []);
-const FINISHRELEASECYCLE__UPDATESTAGING__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep(
- 'Setup git for OSBotify',
- 'Setup git for OSBotify',
- 'UPDATESTAGING',
- ['GPG_PASSPHRASE'],
- [],
-);
-const FINISHRELEASECYCLE__UPDATESTAGING__UPDATE_STAGING_BRANCH_TO_TRIGGER_STAGING_DEPLOY__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__UPDATESTAGING__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'UPDATESTAGING', ['ref', 'token'], []);
+const FINISHRELEASECYCLE__UPDATESTAGING__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = createMockStep('Setup git for OSBotify', 'Setup git for OSBotify', 'UPDATESTAGING', ['GPG_PASSPHRASE'], []);
+const FINISHRELEASECYCLE__UPDATESTAGING__UPDATE_STAGING_BRANCH_TO_TRIGGER_STAGING_DEPLOY__STEP_MOCK = createMockStep(
'Update staging branch to trigger staging deploy',
'Updating staging branch',
'UPDATESTAGING',
[],
[],
);
-const FINISHRELEASECYCLE__UPDATESTAGING__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep(
+const FINISHRELEASECYCLE__UPDATESTAGING__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = createMockStep(
'Announce failed workflow in Slack',
'Announce failed workflow in Slack',
'UPDATESTAGING',
@@ -169,9 +164,9 @@ const FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS = [
FINISHRELEASECYCLE__UPDATESTAGING__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK,
FINISHRELEASECYCLE__UPDATESTAGING__UPDATE_STAGING_BRANCH_TO_TRIGGER_STAGING_DEPLOY__STEP_MOCK,
FINISHRELEASECYCLE__UPDATESTAGING__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK,
-];
+] as const;
-module.exports = {
+export {
FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS,
FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS,
FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS,
diff --git a/workflow_tests/mocks/lintMocks.js b/workflow_tests/mocks/lintMocks.js
deleted file mode 100644
index 1fc5dbfd526f..000000000000
--- a/workflow_tests/mocks/lintMocks.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const utils = require('../utils/utils');
-
-// lint
-const LINT__LINT__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'LINT', [], []);
-const LINT__LINT__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'LINT', [], []);
-const LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK = utils.createMockStep('Lint JavaScript and Typescript with ESLint', 'Lint JavaScript with ESLint', 'LINT', [], ['CI']);
-const LINT__LINT__VERIFY_NO_PRETTIER__STEP_MOCK = utils.createMockStep("Verify there's no Prettier diff", 'Verify theres no Prettier diff', 'LINT');
-const LINT__LINT__RUN_UNUSED_SEARCHER__STEP_MOCK = utils.createMockStep('Run unused style searcher', 'Run unused style searcher', 'LINT');
-const LINT__LINT__STEP_MOCKS = [
- LINT__LINT__CHECKOUT__STEP_MOCK,
- LINT__LINT__SETUP_NODE__STEP_MOCK,
- LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK,
- LINT__LINT__VERIFY_NO_PRETTIER__STEP_MOCK,
- LINT__LINT__RUN_UNUSED_SEARCHER__STEP_MOCK,
-];
-
-module.exports = {
- LINT__LINT__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/lintMocks.ts b/workflow_tests/mocks/lintMocks.ts
new file mode 100644
index 000000000000..93a3fd190a41
--- /dev/null
+++ b/workflow_tests/mocks/lintMocks.ts
@@ -0,0 +1,21 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// lint
+const LINT__LINT__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'LINT', [], []);
+const LINT__LINT__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'LINT', [], []);
+const LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK = createMockStep('Lint JavaScript and Typescript with ESLint', 'Lint JavaScript with ESLint', 'LINT', [], ['CI']);
+const LINT__LINT__VERIFY_NO_PRETTIER__STEP_MOCK = createMockStep("Verify there's no Prettier diff", 'Verify theres no Prettier diff', 'LINT');
+const LINT__LINT__RUN_UNUSED_SEARCHER__STEP_MOCK = createMockStep('Run unused style searcher', 'Run unused style searcher', 'LINT');
+const LINT__LINT__STEP_MOCKS = [
+ LINT__LINT__CHECKOUT__STEP_MOCK,
+ LINT__LINT__SETUP_NODE__STEP_MOCK,
+ LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK,
+ LINT__LINT__VERIFY_NO_PRETTIER__STEP_MOCK,
+ LINT__LINT__RUN_UNUSED_SEARCHER__STEP_MOCK,
+] as const;
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ LINT__LINT__STEP_MOCKS,
+};
diff --git a/workflow_tests/mocks/lockDeploysMocks.js b/workflow_tests/mocks/lockDeploysMocks.ts
similarity index 69%
rename from workflow_tests/mocks/lockDeploysMocks.js
rename to workflow_tests/mocks/lockDeploysMocks.ts
index bb6246a2e1d9..2c9aa1fd6d11 100644
--- a/workflow_tests/mocks/lockDeploysMocks.js
+++ b/workflow_tests/mocks/lockDeploysMocks.ts
@@ -1,22 +1,23 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// lockstagingdeploys
-const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'LOCKSTAGINGDEPLOYS', ['ref', 'token'], []);
-const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__WAIT_FOR_STAGING_DEPLOYS_TO_FINISH__STEP_MOCK = utils.createMockStep(
+const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'LOCKSTAGINGDEPLOYS', ['ref', 'token'], []);
+const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__WAIT_FOR_STAGING_DEPLOYS_TO_FINISH__STEP_MOCK = createMockStep(
'Wait for staging deploys to finish',
'Waiting for staging deploys to finish',
'LOCKSTAGINGDEPLOYS',
['GITHUB_TOKEN'],
[],
);
-const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__COMMENT_IN_STAGINGDEPLOYCASH_TO_GIVE_APPLAUSE_THE_GREEN_LIGHT_TO_BEGIN_QA__STEP_MOCK = utils.createMockStep(
+const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__COMMENT_IN_STAGINGDEPLOYCASH_TO_GIVE_APPLAUSE_THE_GREEN_LIGHT_TO_BEGIN_QA__STEP_MOCK = createMockStep(
'Comment in StagingDeployCash to give Applause the 🟢 to begin QA',
'Commenting in StagingDeployCash',
'LOCKSTAGINGDEPLOYS',
[],
['GITHUB_TOKEN'],
);
-const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__ANNOUNCE_FAILED_WORKFLOW__STEP_MOCK = utils.createMockStep(
+const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__ANNOUNCE_FAILED_WORKFLOW__STEP_MOCK = createMockStep(
'Announce failed workflow',
'Announcing failed workflow in Slack',
'LOCKSTAGINGDEPLOYS',
@@ -28,8 +29,9 @@ const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS = [
LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__WAIT_FOR_STAGING_DEPLOYS_TO_FINISH__STEP_MOCK,
LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__COMMENT_IN_STAGINGDEPLOYCASH_TO_GIVE_APPLAUSE_THE_GREEN_LIGHT_TO_BEGIN_QA__STEP_MOCK,
LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__ANNOUNCE_FAILED_WORKFLOW__STEP_MOCK,
-];
+] as const;
-module.exports = {
+export {
+ // eslint-disable-next-line import/prefer-default-export
LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS,
};
diff --git a/workflow_tests/mocks/platformDeployMocks.js b/workflow_tests/mocks/platformDeployMocks.ts
similarity index 54%
rename from workflow_tests/mocks/platformDeployMocks.js
rename to workflow_tests/mocks/platformDeployMocks.ts
index 3fe8de8cca3d..72176e34d1fa 100644
--- a/workflow_tests/mocks/platformDeployMocks.js
+++ b/workflow_tests/mocks/platformDeployMocks.ts
@@ -1,7 +1,8 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// validateActor
-const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK = createMockStep(
'Check if user is deployer',
'Checking if the user is a deployer',
'VALIDATE_ACTOR',
@@ -9,7 +10,7 @@ const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MO
['GITHUB_TOKEN'],
{IS_DEPLOYER: true},
);
-const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK = createMockStep(
'Check if user is deployer',
'Checking if the user is a deployer',
'VALIDATE_ACTOR',
@@ -17,42 +18,42 @@ const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK
['GITHUB_TOKEN'],
{IS_DEPLOYER: false},
);
-const PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK];
-const PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK];
+const PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK] as const;
+const PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK] as const;
// deployChecklist
-const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK = utils.createMockStep('deployChecklist', 'Run deployChecklist', 'DEPLOY_CHECKLIST');
-const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS = [PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK];
+const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK = createMockStep('deployChecklist', 'Run deployChecklist', 'DEPLOY_CHECKLIST');
+const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS = [PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCK] as const;
// android
-const PLATFORM_DEPLOY__ANDROID__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'ANDROID');
-const PLATFORM_DEPLOY__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'ANDROID');
-const PLATFORM_DEPLOY__ANDROID__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'ANDROID');
-const PLATFORM_DEPLOY__ANDROID__SETUP_JAVA__STEP_MOCK = utils.createMockStep('Setup Java', 'Setup Java', 'ANDROID', ['distribution', 'java-version'], []);
-const PLATFORM_DEPLOY__ANDROID__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setting up Ruby', 'ANDROID', ['ruby-version', 'bundler-cache']);
-const PLATFORM_DEPLOY__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK = utils.createMockStep('Decrypt keystore', 'Decrypting keystore', 'ANDROID', null, ['LARGE_SECRET_PASSPHRASE']);
-const PLATFORM_DEPLOY__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK = utils.createMockStep('Decrypt json key', 'Decrypting JSON key', 'ANDROID', null, ['LARGE_SECRET_PASSPHRASE']);
-const PLATFORM_DEPLOY__ANDROID__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version in ENV', 'Setting version in ENV', 'ANDROID', null, null, null, {VERSION_CODE: '1.2.3'});
-const PLATFORM_DEPLOY__ANDROID__FASTLANE_BETA__STEP_MOCK = utils.createMockStep('Run Fastlane beta', 'Running Fastlane beta', 'ANDROID', null, [
+const PLATFORM_DEPLOY__ANDROID__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'ANDROID');
+const PLATFORM_DEPLOY__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK = createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'ANDROID');
+const PLATFORM_DEPLOY__ANDROID__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setting up Node', 'ANDROID');
+const PLATFORM_DEPLOY__ANDROID__SETUP_JAVA__STEP_MOCK = createMockStep('Setup Java', 'Setup Java', 'ANDROID', ['distribution', 'java-version'], []);
+const PLATFORM_DEPLOY__ANDROID__SETUP_RUBY__STEP_MOCK = createMockStep('Setup Ruby', 'Setting up Ruby', 'ANDROID', ['ruby-version', 'bundler-cache']);
+const PLATFORM_DEPLOY__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK = createMockStep('Decrypt keystore', 'Decrypting keystore', 'ANDROID', null, ['LARGE_SECRET_PASSPHRASE']);
+const PLATFORM_DEPLOY__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK = createMockStep('Decrypt json key', 'Decrypting JSON key', 'ANDROID', null, ['LARGE_SECRET_PASSPHRASE']);
+const PLATFORM_DEPLOY__ANDROID__SET_VERSION__STEP_MOCK = createMockStep('Set version in ENV', 'Setting version in ENV', 'ANDROID', null, null, null, {VERSION_CODE: '1.2.3'});
+const PLATFORM_DEPLOY__ANDROID__FASTLANE_BETA__STEP_MOCK = createMockStep('Run Fastlane beta', 'Running Fastlane beta', 'ANDROID', null, [
'MYAPP_UPLOAD_STORE_PASSWORD',
'MYAPP_UPLOAD_KEY_PASSWORD',
]);
-const PLATFORM_DEPLOY__ANDROID__FASTLANE_PRODUCTION__STEP_MOCK = utils.createMockStep('Run Fastlane production', 'Running Fastlane production', 'ANDROID', null, ['VERSION']);
-const PLATFORM_DEPLOY__ANDROID__ARCHIVE_SOURCEMAPS__STEP_MOCK = utils.createMockStep('Archive Android sourcemaps', 'Archiving Android sourcemaps', 'ANDROID', ['name', 'path']);
-const PLATFORM_DEPLOY__ANDROID__UPLOAD_ANDROID_VERSION_TO_GITHUB_ARTIFACTS__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__ANDROID__FASTLANE_PRODUCTION__STEP_MOCK = createMockStep('Run Fastlane production', 'Running Fastlane production', 'ANDROID', null, ['VERSION']);
+const PLATFORM_DEPLOY__ANDROID__ARCHIVE_SOURCEMAPS__STEP_MOCK = createMockStep('Archive Android sourcemaps', 'Archiving Android sourcemaps', 'ANDROID', ['name', 'path']);
+const PLATFORM_DEPLOY__ANDROID__UPLOAD_ANDROID_VERSION_TO_GITHUB_ARTIFACTS__STEP_MOCK = createMockStep(
'Upload Android version to GitHub artifacts',
'Upload Android version to GitHub artifacts',
'ANDROID',
['name', 'path'],
);
-const PLATFORM_DEPLOY__ANDROID__UPLOAD_TO_BROWSER_STACK__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__ANDROID__UPLOAD_TO_BROWSER_STACK__STEP_MOCK = createMockStep(
'Upload Android version to Browser Stack',
'Uploading Android version to Browser Stack',
'ANDROID',
null,
['BROWSERSTACK'],
);
-const PLATFORM_DEPLOY__ANDROID__WARN_DEPLOYERS__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__ANDROID__WARN_DEPLOYERS__STEP_MOCK = createMockStep(
'Warn deployers if Android production deploy failed',
'Warning deployers of failed production deploy',
'ANDROID',
@@ -74,15 +75,15 @@ const PLATFORM_DEPLOY__ANDROID__STEP_MOCKS = [
PLATFORM_DEPLOY__ANDROID__UPLOAD_ANDROID_VERSION_TO_GITHUB_ARTIFACTS__STEP_MOCK,
PLATFORM_DEPLOY__ANDROID__UPLOAD_TO_BROWSER_STACK__STEP_MOCK,
PLATFORM_DEPLOY__ANDROID__WARN_DEPLOYERS__STEP_MOCK,
-];
+] as const;
// desktop
-const PLATFORM_DEPLOY__DESKTOP__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'DESKTOP');
-const PLATFORM_DEPLOY__DESKTOP__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'DESKTOP');
-const PLATFORM_DEPLOY__DESKTOP__DECRYPT_ID__STEP_MOCK = utils.createMockStep('Decrypt Developer ID Certificate', 'Decrypting developer id certificate', 'DESKTOP', null, [
+const PLATFORM_DEPLOY__DESKTOP__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'DESKTOP');
+const PLATFORM_DEPLOY__DESKTOP__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setting up Node', 'DESKTOP');
+const PLATFORM_DEPLOY__DESKTOP__DECRYPT_ID__STEP_MOCK = createMockStep('Decrypt Developer ID Certificate', 'Decrypting developer id certificate', 'DESKTOP', null, [
'DEVELOPER_ID_SECRET_PASSPHRASE',
]);
-const PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK = utils.createMockStep('Build production desktop app', 'Building production desktop app', 'DESKTOP', null, [
+const PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK = createMockStep('Build production desktop app', 'Building production desktop app', 'DESKTOP', null, [
'CSC_LINK',
'CSC_KEY_PASSWORD',
'APPLE_ID',
@@ -90,7 +91,7 @@ const PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK = utils.createMockSt
'AWS_ACCESS_KEY_ID',
'AWS_SECRET_ACCESS_KEY',
]);
-const PLATFORM_DEPLOY__DESKTOP__BUILD_STAGING__STEP_MOCK = utils.createMockStep('Build staging desktop app', 'Building staging desktop app', 'DESKTOP', null, [
+const PLATFORM_DEPLOY__DESKTOP__BUILD_STAGING__STEP_MOCK = createMockStep('Build staging desktop app', 'Building staging desktop app', 'DESKTOP', null, [
'CSC_LINK',
'CSC_KEY_PASSWORD',
'APPLE_ID',
@@ -104,50 +105,43 @@ const PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS = [
PLATFORM_DEPLOY__DESKTOP__DECRYPT_ID__STEP_MOCK,
PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK,
PLATFORM_DEPLOY__DESKTOP__BUILD_STAGING__STEP_MOCK,
-];
+] as const;
// ios
-const PLATFORM_DEPLOY__IOS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'IOS');
-const PLATFORM_DEPLOY__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'IOS');
-const PLATFORM_DEPLOY__IOS__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'IOS');
-const PLATFORM_DEPLOY__IOS__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setting up Ruby', 'IOS', ['ruby-version', 'bundler-cache']);
-const PLATFORM_DEPLOY__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK = utils.createMockStep('Cache Pod dependencies', 'Cache Pod dependencies', 'IOS', ['path', 'key', 'restore-keys'], [], {
+const PLATFORM_DEPLOY__IOS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'IOS');
+const PLATFORM_DEPLOY__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK = createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'IOS');
+const PLATFORM_DEPLOY__IOS__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setting up Node', 'IOS');
+const PLATFORM_DEPLOY__IOS__SETUP_RUBY__STEP_MOCK = createMockStep('Setup Ruby', 'Setting up Ruby', 'IOS', ['ruby-version', 'bundler-cache']);
+const PLATFORM_DEPLOY__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK = createMockStep('Cache Pod dependencies', 'Cache Pod dependencies', 'IOS', ['path', 'key', 'restore-keys'], [], {
'cache-hit': false,
});
-const PLATFORM_DEPLOY__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK = utils.createMockStep(
- 'Compare Podfile.lock and Manifest.lock',
- 'Compare Podfile.lock and Manifest.lock',
- 'IOS',
- [],
- [],
- {IS_PODFILE_SAME_AS_MANIFEST: false},
-);
-const PLATFORM_DEPLOY__IOS__COCOAPODS__STEP_MOCK = utils.createMockStep('Install cocoapods', 'Installing cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command']);
-const PLATFORM_DEPLOY__IOS__DECRYPT_APPSTORE_PROFILE__STEP_MOCK = utils.createMockStep('Decrypt AppStore profile', 'Decrypting profile', 'IOS', null, ['LARGE_SECRET_PASSPHRASE']);
-const PLATFORM_DEPLOY__IOS__DECRYPT_APPSTORE_NSE_PROFILE__STEP_MOCK = utils.createMockStep('Decrypt AppStore Notification Service profile', 'Decrypting profile', 'IOS', null, [
+const PLATFORM_DEPLOY__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK = createMockStep('Compare Podfile.lock and Manifest.lock', 'Compare Podfile.lock and Manifest.lock', 'IOS', [], [], {
+ IS_PODFILE_SAME_AS_MANIFEST: false,
+});
+const PLATFORM_DEPLOY__IOS__COCOAPODS__STEP_MOCK = createMockStep('Install cocoapods', 'Installing cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command']);
+const PLATFORM_DEPLOY__IOS__DECRYPT_APPSTORE_PROFILE__STEP_MOCK = createMockStep('Decrypt AppStore profile', 'Decrypting profile', 'IOS', null, ['LARGE_SECRET_PASSPHRASE']);
+const PLATFORM_DEPLOY__IOS__DECRYPT_APPSTORE_NSE_PROFILE__STEP_MOCK = createMockStep('Decrypt AppStore Notification Service profile', 'Decrypting profile', 'IOS', null, [
'LARGE_SECRET_PASSPHRASE',
]);
-const PLATFORM_DEPLOY__IOS__DECRYPT_CERTIFICATE__STEP_MOCK = utils.createMockStep('Decrypt certificate', 'Decrypting certificate', 'IOS', null, ['LARGE_SECRET_PASSPHRASE']);
-const PLATFORM_DEPLOY__IOS__DECRYPT_APP_STORE_API_KEY__STEP_MOCK = utils.createMockStep('Decrypt App Store Connect API key', 'Decrypting App Store API key', 'IOS', null, [
+const PLATFORM_DEPLOY__IOS__DECRYPT_CERTIFICATE__STEP_MOCK = createMockStep('Decrypt certificate', 'Decrypting certificate', 'IOS', null, ['LARGE_SECRET_PASSPHRASE']);
+const PLATFORM_DEPLOY__IOS__DECRYPT_APP_STORE_API_KEY__STEP_MOCK = createMockStep('Decrypt App Store Connect API key', 'Decrypting App Store API key', 'IOS', null, [
'LARGE_SECRET_PASSPHRASE',
]);
-const PLATFORM_DEPLOY__IOS__FASTLANE__STEP_MOCK = utils.createMockStep('Run Fastlane', 'Running Fastlane', 'IOS', null, [
+const PLATFORM_DEPLOY__IOS__FASTLANE__STEP_MOCK = createMockStep('Run Fastlane', 'Running Fastlane', 'IOS', null, [
'APPLE_CONTACT_EMAIL',
'APPLE_CONTACT_PHONE',
'APPLE_DEMO_EMAIL',
'APPLE_DEMO_PASSWORD',
]);
-const PLATFORM_DEPLOY__IOS__ARCHIVE_SOURCEMAPS__STEP_MOCK = utils.createMockStep('Archive iOS sourcemaps', 'Archiving sourcemaps', 'IOS', ['name', 'path']);
-const PLATFORM_DEPLOY__IOS__UPLOAD_IOS_VERSION_TO_GITHUB_ARTIFACTS__STEP_MOCK = utils.createMockStep(
- 'Upload iOS version to GitHub artifacts',
- 'Upload iOS version to GitHub artifacts',
- 'IOS',
- ['name', 'path'],
-);
-const PLATFORM_DEPLOY__IOS__UPLOAD_BROWSERSTACK__STEP_MOCK = utils.createMockStep('Upload iOS version to Browser Stack', 'Uploading version to Browser Stack', 'IOS', null, ['BROWSERSTACK']);
-const PLATFORM_DEPLOY__IOS__SET_VERSION__STEP_MOCK = utils.createMockStep('Set iOS version in ENV', 'Setting iOS version', 'IOS', null, null, null, {IOS_VERSION: '1.2.3'});
-const PLATFORM_DEPLOY__IOS__RELEASE_FASTLANE__STEP_MOCK = utils.createMockStep('Run Fastlane for App Store release', 'Running Fastlane for release', 'IOS', null, ['VERSION']);
-const PLATFORM_DEPLOY__IOS__WARN_FAIL__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__IOS__ARCHIVE_SOURCEMAPS__STEP_MOCK = createMockStep('Archive iOS sourcemaps', 'Archiving sourcemaps', 'IOS', ['name', 'path']);
+const PLATFORM_DEPLOY__IOS__UPLOAD_IOS_VERSION_TO_GITHUB_ARTIFACTS__STEP_MOCK = createMockStep('Upload iOS version to GitHub artifacts', 'Upload iOS version to GitHub artifacts', 'IOS', [
+ 'name',
+ 'path',
+]);
+const PLATFORM_DEPLOY__IOS__UPLOAD_BROWSERSTACK__STEP_MOCK = createMockStep('Upload iOS version to Browser Stack', 'Uploading version to Browser Stack', 'IOS', null, ['BROWSERSTACK']);
+const PLATFORM_DEPLOY__IOS__SET_VERSION__STEP_MOCK = createMockStep('Set iOS version in ENV', 'Setting iOS version', 'IOS', null, null, null, {IOS_VERSION: '1.2.3'});
+const PLATFORM_DEPLOY__IOS__RELEASE_FASTLANE__STEP_MOCK = createMockStep('Run Fastlane for App Store release', 'Running Fastlane for release', 'IOS', null, ['VERSION']);
+const PLATFORM_DEPLOY__IOS__WARN_FAIL__STEP_MOCK = createMockStep(
'Warn deployers if iOS production deploy failed',
'Warning developers of failed deploy',
'IOS',
@@ -173,25 +167,25 @@ const PLATFORM_DEPLOY__IOS__STEP_MOCKS = [
PLATFORM_DEPLOY__IOS__SET_VERSION__STEP_MOCK,
PLATFORM_DEPLOY__IOS__RELEASE_FASTLANE__STEP_MOCK,
PLATFORM_DEPLOY__IOS__WARN_FAIL__STEP_MOCK,
-];
+] as const;
// web
-const PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'WEB');
-const PLATFORM_DEPLOY__WEB__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'WEB');
-const PLATFORM_DEPLOY__WEB__CLOUDFLARE__STEP_MOCK = utils.createMockStep('Setup Cloudflare CLI', 'Setting up Cloudflare CLI', 'WEB');
-const PLATFORM_DEPLOY__WEB__AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep('Configure AWS Credentials', 'Configuring AWS credentials', 'WEB', [
+const PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'WEB');
+const PLATFORM_DEPLOY__WEB__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setting up Node', 'WEB');
+const PLATFORM_DEPLOY__WEB__CLOUDFLARE__STEP_MOCK = createMockStep('Setup Cloudflare CLI', 'Setting up Cloudflare CLI', 'WEB');
+const PLATFORM_DEPLOY__WEB__AWS_CREDENTIALS__STEP_MOCK = createMockStep('Configure AWS Credentials', 'Configuring AWS credentials', 'WEB', [
'aws-access-key-id',
'aws-secret-access-key',
'aws-region',
]);
-const PLATFORM_DEPLOY__WEB__BUILD_PRODUCTION__STEP_MOCK = utils.createMockStep('Build web for production', 'Building web for production', 'WEB');
-const PLATFORM_DEPLOY__WEB__BUILD_STAGING__STEP_MOCK = utils.createMockStep('Build web for staging', 'Building web for staging', 'WEB');
-const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_PRODUCTION__STEP_MOCK = utils.createMockStep('Build storybook docs for production', 'Build storybook docs for production', 'WEB');
-const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_STAGING__STEP_MOCK = utils.createMockStep('Build storybook docs for staging', 'Build storybook docs for staging', 'WEB');
-const PLATFORM_DEPLOY__WEB__DEPLOY_PRODUCTION_S3__STEP_MOCK = utils.createMockStep('Deploy production to S3', 'Deploying production to S3', 'WEB');
-const PLATFORM_DEPLOY__WEB__DEPLOY_STAGING_S3__STEP_MOCK = utils.createMockStep('Deploy staging to S3', 'Deploying staging to S3', 'WEB');
-const PLATFORM_DEPLOY__WEB__PURGE_PRODUCTION_CACHE__STEP_MOCK = utils.createMockStep('Purge production Cloudflare cache', 'Purging production Cloudflare cache', 'WEB', null, ['CF_API_KEY']);
-const PLATFORM_DEPLOY__WEB__PURGE_STAGING_CACHE__STEP_MOCK = utils.createMockStep('Purge staging Cloudflare cache', 'Purging staging Cloudflare cache', 'WEB', null, ['CF_API_KEY']);
+const PLATFORM_DEPLOY__WEB__BUILD_PRODUCTION__STEP_MOCK = createMockStep('Build web for production', 'Building web for production', 'WEB');
+const PLATFORM_DEPLOY__WEB__BUILD_STAGING__STEP_MOCK = createMockStep('Build web for staging', 'Building web for staging', 'WEB');
+const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_PRODUCTION__STEP_MOCK = createMockStep('Build storybook docs for production', 'Build storybook docs for production', 'WEB');
+const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_STAGING__STEP_MOCK = createMockStep('Build storybook docs for staging', 'Build storybook docs for staging', 'WEB');
+const PLATFORM_DEPLOY__WEB__DEPLOY_PRODUCTION_S3__STEP_MOCK = createMockStep('Deploy production to S3', 'Deploying production to S3', 'WEB');
+const PLATFORM_DEPLOY__WEB__DEPLOY_STAGING_S3__STEP_MOCK = createMockStep('Deploy staging to S3', 'Deploying staging to S3', 'WEB');
+const PLATFORM_DEPLOY__WEB__PURGE_PRODUCTION_CACHE__STEP_MOCK = createMockStep('Purge production Cloudflare cache', 'Purging production Cloudflare cache', 'WEB', null, ['CF_API_KEY']);
+const PLATFORM_DEPLOY__WEB__PURGE_STAGING_CACHE__STEP_MOCK = createMockStep('Purge staging Cloudflare cache', 'Purging staging Cloudflare cache', 'WEB', null, ['CF_API_KEY']);
const PLATFORM_DEPLOY__WEB__STEP_MOCKS = [
PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK,
PLATFORM_DEPLOY__WEB__SETUP_NODE__STEP_MOCK,
@@ -205,32 +199,32 @@ const PLATFORM_DEPLOY__WEB__STEP_MOCKS = [
PLATFORM_DEPLOY__WEB__DEPLOY_STAGING_S3__STEP_MOCK,
PLATFORM_DEPLOY__WEB__PURGE_PRODUCTION_CACHE__STEP_MOCK,
PLATFORM_DEPLOY__WEB__PURGE_STAGING_CACHE__STEP_MOCK,
-];
+] as const;
// post slack message on failure
-const PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK = utils.createMockStep('Post Slack message on failure', 'Posting Slack message on platform deploy failure', 'POST_SLACK_FAIL', [
+const PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK = createMockStep('Post Slack message on failure', 'Posting Slack message on platform deploy failure', 'POST_SLACK_FAIL', [
'SLACK_WEBHOOK',
]);
-const PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS = [PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK];
+const PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS = [PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK] as const;
// post slack message on success
-const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'POST_SLACK_SUCCESS');
-const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version', 'Setting version', 'POST_SLACK_SUCCESS', null, null, null, {VERSION: '1.2.3'});
-const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__ANNOUNCE_CHANNEL__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'POST_SLACK_SUCCESS');
+const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__SET_VERSION__STEP_MOCK = createMockStep('Set version', 'Setting version', 'POST_SLACK_SUCCESS', null, null, null, {VERSION: '1.2.3'});
+const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__ANNOUNCE_CHANNEL__STEP_MOCK = createMockStep(
'Announces the deploy in the #announce Slack room',
'Posting message to \\#announce channel',
'POST_SLACK_SUCCESS',
['status'],
['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'],
);
-const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__DEPLOYER_CHANNEL__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__DEPLOYER_CHANNEL__STEP_MOCK = createMockStep(
'Announces the deploy in the #deployer Slack room',
'Posting message to \\#deployer channel',
'POST_SLACK_SUCCESS',
['status'],
['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'],
);
-const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__EXPENSIFY_CHANNEL__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__EXPENSIFY_CHANNEL__STEP_MOCK = createMockStep(
'Announces a production deploy in the #expensify-open-source Slack room',
'Posting message to \\#expensify-open-source channel',
'POST_SLACK_SUCCESS',
@@ -243,13 +237,13 @@ const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS = [
PLATFORM_DEPLOY__POST_SLACK_SUCCESS__ANNOUNCE_CHANNEL__STEP_MOCK,
PLATFORM_DEPLOY__POST_SLACK_SUCCESS__DEPLOYER_CHANNEL__STEP_MOCK,
PLATFORM_DEPLOY__POST_SLACK_SUCCESS__EXPENSIFY_CHANNEL__STEP_MOCK,
-];
+] as const;
// post github comment
-const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'POST_GITHUB_COMMENT');
-const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'POST_GITHUB_COMMENT');
-const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version', 'Setting version', 'POST_GITHUB_COMMENT', null, null, null, {VERSION: '1.2.3'});
-const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK = utils.createMockStep(
+const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checking out', 'POST_GITHUB_COMMENT');
+const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setting up Node', 'POST_GITHUB_COMMENT');
+const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SET_VERSION__STEP_MOCK = createMockStep('Set version', 'Setting version', 'POST_GITHUB_COMMENT', null, null, null, {VERSION: '1.2.3'});
+const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK = createMockStep(
'Get Release Pull Request List',
'Getting release pull request list',
'POST_GITHUB_COMMENT',
@@ -257,7 +251,7 @@ const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK = utils.create
null,
{PR_LIST: '[1.2.1, 1.2.2]'},
);
-const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__COMMENT__STEP_MOCK = utils.createMockStep('Comment on issues', 'Commenting on issues', 'POST_GITHUB_COMMENT', [
+const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__COMMENT__STEP_MOCK = createMockStep('Comment on issues', 'Commenting on issues', 'POST_GITHUB_COMMENT', [
'PR_LIST',
'IS_PRODUCTION_DEPLOY',
'DEPLOY_VERSION',
@@ -273,9 +267,9 @@ const PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS = [
PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SET_VERSION__STEP_MOCK,
PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK,
PLATFORM_DEPLOY__POST_GIHUB_COMMENT__COMMENT__STEP_MOCK,
-];
+] as const;
-module.exports = {
+export {
PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS,
PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS,
PLATFORM_DEPLOY__ANDROID__STEP_MOCKS,
diff --git a/workflow_tests/mocks/preDeployMocks.js b/workflow_tests/mocks/preDeployMocks.js
deleted file mode 100644
index daadf3d0c743..000000000000
--- a/workflow_tests/mocks/preDeployMocks.js
+++ /dev/null
@@ -1,103 +0,0 @@
-const utils = require('../utils/utils');
-
-// typecheck
-const TYPECHECK_WORKFLOW_MOCK_STEP = utils.createMockStep('Run typecheck workflow', 'Running typecheck workflow', 'TYPECHECK');
-const TYPECHECK_JOB_MOCK_STEPS = [TYPECHECK_WORKFLOW_MOCK_STEP];
-
-// lint
-const LINT_WORKFLOW_MOCK_STEP = utils.createMockStep('Run lint workflow', 'Running lint workflow', 'LINT');
-const LINT_JOB_MOCK_STEPS = [LINT_WORKFLOW_MOCK_STEP];
-
-// test
-const TEST_WORKFLOW_MOCK_STEP = utils.createMockStep('Run test workflow', 'Running test workflow', 'TEST');
-const TEST_JOB_MOCK_STEPS = [TEST_WORKFLOW_MOCK_STEP];
-
-// confirm_passing_build
-const ANNOUNCE_IN_SLACK_MOCK_STEP = utils.createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in slack', 'CONFIRM_PASSING_BUILD', ['SLACK_WEBHOOK']);
-const CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS = [
- ANNOUNCE_IN_SLACK_MOCK_STEP,
-
- // 2nd step runs normally
-];
-
-// choose_deploy_actions
-const GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY = utils.createMockStep('Get merged pull request', 'Getting merged pull request', 'CHOOSE_DEPLOY_ACTIONS', ['github_token'], null, {
- number: '123',
- labels: '[]',
-});
-const CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED = utils.createMockStep(
- 'Check if StagingDeployCash is locked',
- 'Checking StagingDeployCash',
- 'CHOOSE_DEPLOY_ACTIONS',
- ['GITHUB_TOKEN'],
- null,
- {IS_LOCKED: true},
-);
-const CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED = utils.createMockStep(
- 'Check if StagingDeployCash is locked',
- 'Checking StagingDeployCash',
- 'CHOOSE_DEPLOY_ACTIONS',
- ['GITHUB_TOKEN'],
- null,
- {IS_LOCKED: false},
-);
-const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED = [
- GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY,
- CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED,
-
- // step 3 runs normally
-];
-const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED = [
- GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY,
- CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED,
-
- // step 3 runs normally
-];
-
-// skip_deploy
-const COMMENT_ON_DEFERRED_PR_MOCK_STEP = utils.createMockStep('Comment on deferred PR', 'Skipping deploy', 'SKIP_DEPLOY', ['github_token', 'number', 'body']);
-const SKIP_DEPLOY_JOB_MOCK_STEPS = [COMMENT_ON_DEFERRED_PR_MOCK_STEP];
-
-// create_new_version
-const CREATE_NEW_VERSION_MOCK_STEP = utils.createMockStep(
- 'Create new version',
- 'Creating new version',
- 'CREATE_NEW_VERSION',
- null,
- null,
- {NEW_VERSION: '1.2.3'},
- null,
- true,
- 'createNewVersion',
-);
-const CREATE_NEW_VERSION_JOB_MOCK_STEPS = [CREATE_NEW_VERSION_MOCK_STEP];
-
-// update_staging
-const RUN_TURNSTYLE_MOCK_STEP = utils.createMockStep('Run turnstyle', 'Running turnstyle', 'UPDATE_STAGING', ['poll-interval-seconds'], ['GITHUB_TOKEN']);
-const CHECKOUT_MAIN_MOCK_STEP = utils.createMockStep('Checkout main', 'Checkout main', 'UPDATE_STAGING', ['ref', 'token']);
-const SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP = utils.createMockStep('Setup Git for OSBotify', 'Setup Git for OSBotify', 'UPDATE_STAGING', ['GPG_PASSPHRASE']);
-const UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP = utils.createMockStep('Update staging branch from main', 'Update staging branch from main', 'UPDATE_STAGING');
-const ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP = utils.createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in Slack', 'UPDATE_STAGING', ['SLACK_WEBHOOK']);
-const UPDATE_STAGING_JOB_MOCK_STEPS = [
- RUN_TURNSTYLE_MOCK_STEP,
- CHECKOUT_MAIN_MOCK_STEP,
- SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP,
- UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP,
- ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP,
-];
-
-const PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP = utils.createMockStep('Perform E2E tests', 'Perform E2E tests', 'E2EPERFORMANCETESTS');
-const PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS = [PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP];
-
-module.exports = {
- TYPECHECK_JOB_MOCK_STEPS,
- LINT_JOB_MOCK_STEPS,
- TEST_JOB_MOCK_STEPS,
- CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS,
- CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED,
- CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED,
- SKIP_DEPLOY_JOB_MOCK_STEPS,
- CREATE_NEW_VERSION_JOB_MOCK_STEPS,
- UPDATE_STAGING_JOB_MOCK_STEPS,
- PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS,
-};
diff --git a/workflow_tests/mocks/preDeployMocks.ts b/workflow_tests/mocks/preDeployMocks.ts
new file mode 100644
index 000000000000..df6bc63e7dd9
--- /dev/null
+++ b/workflow_tests/mocks/preDeployMocks.ts
@@ -0,0 +1,94 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// typecheck
+const TYPECHECK_WORKFLOW_MOCK_STEP = createMockStep('Run typecheck workflow', 'Running typecheck workflow', 'TYPECHECK');
+const TYPECHECK_JOB_MOCK_STEPS = [TYPECHECK_WORKFLOW_MOCK_STEP] as const;
+
+// lint
+const LINT_WORKFLOW_MOCK_STEP = createMockStep('Run lint workflow', 'Running lint workflow', 'LINT');
+const LINT_JOB_MOCK_STEPS = [LINT_WORKFLOW_MOCK_STEP] as const;
+
+// test
+const TEST_WORKFLOW_MOCK_STEP = createMockStep('Run test workflow', 'Running test workflow', 'TEST');
+const TEST_JOB_MOCK_STEPS = [TEST_WORKFLOW_MOCK_STEP] as const;
+
+// confirm_passing_build
+const ANNOUNCE_IN_SLACK_MOCK_STEP = createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in slack', 'CONFIRM_PASSING_BUILD', ['SLACK_WEBHOOK']);
+const CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS = [
+ ANNOUNCE_IN_SLACK_MOCK_STEP,
+
+ // 2nd step runs normally
+] as const;
+
+// choose_deploy_actions
+const GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY = createMockStep('Get merged pull request', 'Getting merged pull request', 'CHOOSE_DEPLOY_ACTIONS', ['github_token'], null, {
+ number: '123',
+ labels: '[]',
+});
+const CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED = createMockStep(
+ 'Check if StagingDeployCash is locked',
+ 'Checking StagingDeployCash',
+ 'CHOOSE_DEPLOY_ACTIONS',
+ ['GITHUB_TOKEN'],
+ null,
+ {IS_LOCKED: true},
+);
+const CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED = createMockStep(
+ 'Check if StagingDeployCash is locked',
+ 'Checking StagingDeployCash',
+ 'CHOOSE_DEPLOY_ACTIONS',
+ ['GITHUB_TOKEN'],
+ null,
+ {IS_LOCKED: false},
+);
+const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED = [
+ GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY,
+ CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED,
+
+ // step 3 runs normally
+] as const;
+const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED = [
+ GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY,
+ CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED,
+
+ // step 3 runs normally
+] as const;
+
+// skip_deploy
+const COMMENT_ON_DEFERRED_PR_MOCK_STEP = createMockStep('Comment on deferred PR', 'Skipping deploy', 'SKIP_DEPLOY', ['github_token', 'number', 'body']);
+const SKIP_DEPLOY_JOB_MOCK_STEPS = [COMMENT_ON_DEFERRED_PR_MOCK_STEP] as const;
+
+// create_new_version
+const CREATE_NEW_VERSION_MOCK_STEP = createMockStep('Create new version', 'Creating new version', 'CREATE_NEW_VERSION', null, null, {NEW_VERSION: '1.2.3'}, null, true, 'createNewVersion');
+const CREATE_NEW_VERSION_JOB_MOCK_STEPS = [CREATE_NEW_VERSION_MOCK_STEP] as const;
+
+// update_staging
+const RUN_TURNSTYLE_MOCK_STEP = createMockStep('Run turnstyle', 'Running turnstyle', 'UPDATE_STAGING', ['poll-interval-seconds'], ['GITHUB_TOKEN']);
+const CHECKOUT_MAIN_MOCK_STEP = createMockStep('Checkout main', 'Checkout main', 'UPDATE_STAGING', ['ref', 'token']);
+const SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP = createMockStep('Setup Git for OSBotify', 'Setup Git for OSBotify', 'UPDATE_STAGING', ['GPG_PASSPHRASE']);
+const UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP = createMockStep('Update staging branch from main', 'Update staging branch from main', 'UPDATE_STAGING');
+const ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP = createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in Slack', 'UPDATE_STAGING', ['SLACK_WEBHOOK']);
+const UPDATE_STAGING_JOB_MOCK_STEPS = [
+ RUN_TURNSTYLE_MOCK_STEP,
+ CHECKOUT_MAIN_MOCK_STEP,
+ SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP,
+ UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP,
+ ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP,
+] as const;
+
+const PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP = createMockStep('Perform E2E tests', 'Perform E2E tests', 'E2EPERFORMANCETESTS');
+const PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS = [PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP] as const;
+
+export {
+ TYPECHECK_JOB_MOCK_STEPS,
+ LINT_JOB_MOCK_STEPS,
+ TEST_JOB_MOCK_STEPS,
+ CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS,
+ CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED,
+ CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED,
+ SKIP_DEPLOY_JOB_MOCK_STEPS,
+ CREATE_NEW_VERSION_JOB_MOCK_STEPS,
+ UPDATE_STAGING_JOB_MOCK_STEPS,
+ PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS,
+};
diff --git a/workflow_tests/mocks/reviewerChecklistMocks.js b/workflow_tests/mocks/reviewerChecklistMocks.js
deleted file mode 100644
index 5f9ef67198a8..000000000000
--- a/workflow_tests/mocks/reviewerChecklistMocks.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const utils = require('../utils/utils');
-
-// checklist
-const REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK = utils.createMockStep('reviewerChecklist.js', 'reviewerChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []);
-const REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS = [REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK];
-
-module.exports = {
- REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/reviewerChecklistMocks.ts b/workflow_tests/mocks/reviewerChecklistMocks.ts
new file mode 100644
index 000000000000..20c87994f957
--- /dev/null
+++ b/workflow_tests/mocks/reviewerChecklistMocks.ts
@@ -0,0 +1,11 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// checklist
+const REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK = createMockStep('reviewerChecklist.js', 'reviewerChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []);
+const REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS = [REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK] as const;
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS,
+};
diff --git a/workflow_tests/mocks/testBuildMocks.js b/workflow_tests/mocks/testBuildMocks.ts
similarity index 59%
rename from workflow_tests/mocks/testBuildMocks.js
rename to workflow_tests/mocks/testBuildMocks.ts
index 37bcc5fb6fac..12109d0a875b 100644
--- a/workflow_tests/mocks/testBuildMocks.js
+++ b/workflow_tests/mocks/testBuildMocks.ts
@@ -1,13 +1,14 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// validateactor
-const TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK = utils.createMockStep('Is Expensify employee', 'Is Expensify employee', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
+const TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK = createMockStep('Is Expensify employee', 'Is Expensify employee', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
IS_EXPENSIFY_EMPLOYEE: true,
});
-const TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK = utils.createMockStep('Is Expensify employee', 'Is Expensify employee', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
+const TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK = createMockStep('Is Expensify employee', 'Is Expensify employee', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], {
IS_EXPENSIFY_EMPLOYEE: false,
});
-const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK = createMockStep(
'Set HAS_READY_TO_BUILD_LABEL flag',
'Set HAS_READY_TO_BUILD_LABEL flag',
'VALIDATEACTOR',
@@ -15,7 +16,7 @@ const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MO
['PULL_REQUEST_NUMBER', 'GITHUB_TOKEN'],
{HAS_READY_TO_BUILD_LABEL: true},
);
-const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK = createMockStep(
'Set HAS_READY_TO_BUILD_LABEL flag',
'Set HAS_READY_TO_BUILD_LABEL flag',
'VALIDATEACTOR',
@@ -26,23 +27,23 @@ const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_M
const TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS = [
TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK,
TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK,
-];
+] as const;
const TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS = [
TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK,
TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK,
-];
+] as const;
const TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS = [
TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK,
TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK,
-];
+] as const;
const TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS = [
TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK,
TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK,
-];
+] as const;
// getbranchref
-const TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'GETBRANCHREF', [], []);
-const TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'GETBRANCHREF', [], []);
+const TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK = createMockStep(
'Check if pull request number is correct',
'Check if pull request number is correct',
'GETBRANCHREF',
@@ -50,38 +51,38 @@ const TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOC
['GITHUB_TOKEN'],
{REF: 'test-ref'},
);
-const TESTBUILD__GETBRANCHREF__STEP_MOCKS = [TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK, TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK];
+const TESTBUILD__GETBRANCHREF__STEP_MOCKS = [TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK, TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK] as const;
// android
-const TESTBUILD__ANDROID__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'ANDROID', ['ref'], []);
-const TESTBUILD__ANDROID__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__ANDROID__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'ANDROID', ['ref'], []);
+const TESTBUILD__ANDROID__CREATE_ENV_ADHOC__STEP_MOCK = createMockStep(
'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it',
'Creating .env.adhoc file based on staging',
'ANDROID',
[],
[],
);
-const TESTBUILD__ANDROID__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'ANDROID', [], []);
-const TESTBUILD__ANDROID__SETUP_JAVA__STEP_MOCK = utils.createMockStep('Setup Java', 'Setup Java', 'ANDROID', ['distribution', 'java-version'], []);
-const TESTBUILD__ANDROID__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setup Ruby', 'ANDROID', ['ruby-version', 'bundler-cache'], []);
-const TESTBUILD__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK = utils.createMockStep('Decrypt keystore', 'Decrypt keystore', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE']);
-const TESTBUILD__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK = utils.createMockStep('Decrypt json key', 'Decrypt json key', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE']);
-const TESTBUILD__ANDROID__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__ANDROID__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'ANDROID', [], []);
+const TESTBUILD__ANDROID__SETUP_JAVA__STEP_MOCK = createMockStep('Setup Java', 'Setup Java', 'ANDROID', ['distribution', 'java-version'], []);
+const TESTBUILD__ANDROID__SETUP_RUBY__STEP_MOCK = createMockStep('Setup Ruby', 'Setup Ruby', 'ANDROID', ['ruby-version', 'bundler-cache'], []);
+const TESTBUILD__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK = createMockStep('Decrypt keystore', 'Decrypt keystore', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE']);
+const TESTBUILD__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK = createMockStep('Decrypt json key', 'Decrypt json key', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE']);
+const TESTBUILD__ANDROID__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = createMockStep(
'Configure AWS Credentials',
'Configure AWS Credentials',
'ANDROID',
['aws-access-key-id', 'aws-secret-access-key', 'aws-region'],
[],
);
-const TESTBUILD__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'ANDROID');
-const TESTBUILD__ANDROID__RUN_FASTLANE_BETA_TEST__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK = createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'ANDROID');
+const TESTBUILD__ANDROID__RUN_FASTLANE_BETA_TEST__STEP_MOCK = createMockStep(
'Run Fastlane beta test',
'Run Fastlane beta test',
'ANDROID',
[],
['S3_ACCESS_KEY', 'S3_SECRET_ACCESS_KEY', 'S3_BUCKET', 'S3_REGION', 'MYAPP_UPLOAD_STORE_PASSWORD', 'MYAPP_UPLOAD_KEY_PASSWORD'],
);
-const TESTBUILD__ANDROID__UPLOAD_ARTIFACT__STEP_MOCK = utils.createMockStep('Upload Artifact', 'Upload Artifact', 'ANDROID', ['name', 'path'], []);
+const TESTBUILD__ANDROID__UPLOAD_ARTIFACT__STEP_MOCK = createMockStep('Upload Artifact', 'Upload Artifact', 'ANDROID', ['name', 'path'], []);
const TESTBUILD__ANDROID__STEP_MOCKS = [
TESTBUILD__ANDROID__CHECKOUT__STEP_MOCK,
TESTBUILD__ANDROID__CREATE_ENV_ADHOC__STEP_MOCK,
@@ -94,46 +95,46 @@ const TESTBUILD__ANDROID__STEP_MOCKS = [
TESTBUILD__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK,
TESTBUILD__ANDROID__RUN_FASTLANE_BETA_TEST__STEP_MOCK,
TESTBUILD__ANDROID__UPLOAD_ARTIFACT__STEP_MOCK,
-];
+] as const;
// ios
-const TESTBUILD__IOS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'IOS', ['ref'], []);
-const TESTBUILD__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'IOS');
-const TESTBUILD__IOS__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__IOS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'IOS', ['ref'], []);
+const TESTBUILD__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK = createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'IOS');
+const TESTBUILD__IOS__CREATE_ENV_ADHOC__STEP_MOCK = createMockStep(
'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it',
'Creating .env.adhoc file based on staging',
'IOS',
[],
[],
);
-const TESTBUILD__IOS__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'IOS', [], []);
-const TESTBUILD__IOS__SETUP_XCODE__STEP_MOCK = utils.createMockStep('Setup XCode', 'Setup XCode', 'IOS', [], []);
-const TESTBUILD__IOS__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setup Ruby', 'IOS', ['ruby-version', 'bundler-cache'], []);
-const TESTBUILD__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK = utils.createMockStep('Cache Pod dependencies', 'Cache Pod dependencies', 'IOS', ['path', 'key', 'restore-keys'], [], {
+const TESTBUILD__IOS__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'IOS', [], []);
+const TESTBUILD__IOS__SETUP_XCODE__STEP_MOCK = createMockStep('Setup XCode', 'Setup XCode', 'IOS', [], []);
+const TESTBUILD__IOS__SETUP_RUBY__STEP_MOCK = createMockStep('Setup Ruby', 'Setup Ruby', 'IOS', ['ruby-version', 'bundler-cache'], []);
+const TESTBUILD__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK = createMockStep('Cache Pod dependencies', 'Cache Pod dependencies', 'IOS', ['path', 'key', 'restore-keys'], [], {
'cache-hit': false,
});
-const TESTBUILD__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK = utils.createMockStep('Compare Podfile.lock and Manifest.lock', 'Compare Podfile.lock and Manifest.lock', 'IOS', [], [], {
+const TESTBUILD__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK = createMockStep('Compare Podfile.lock and Manifest.lock', 'Compare Podfile.lock and Manifest.lock', 'IOS', [], [], {
IS_PODFILE_SAME_AS_MANIFEST: false,
});
-const TESTBUILD__IOS__INSTALL_COCOAPODS__STEP_MOCK = utils.createMockStep('Install cocoapods', 'Install cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command'], []);
-const TESTBUILD__IOS__DECRYPT_ADHOC_PROFILE__STEP_MOCK = utils.createMockStep('Decrypt AdHoc profile', 'Decrypt AdHoc profile', 'IOS', [], ['LARGE_SECRET_PASSPHRASE']);
-const TESTBUILD__IOS__DECRYPT_ADHOC_NSE_PROFILE__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__IOS__INSTALL_COCOAPODS__STEP_MOCK = createMockStep('Install cocoapods', 'Install cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command'], []);
+const TESTBUILD__IOS__DECRYPT_ADHOC_PROFILE__STEP_MOCK = createMockStep('Decrypt AdHoc profile', 'Decrypt AdHoc profile', 'IOS', [], ['LARGE_SECRET_PASSPHRASE']);
+const TESTBUILD__IOS__DECRYPT_ADHOC_NSE_PROFILE__STEP_MOCK = createMockStep(
'Decrypt AdHoc Notification Service profile',
'Decrypt AdHoc Notification Service profile',
'IOS',
[],
['LARGE_SECRET_PASSPHRASE'],
);
-const TESTBUILD__IOS__DECRYPT_CERTIFICATE__STEP_MOCK = utils.createMockStep('Decrypt certificate', 'Decrypt certificate', 'IOS', [], ['LARGE_SECRET_PASSPHRASE']);
-const TESTBUILD__IOS__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__IOS__DECRYPT_CERTIFICATE__STEP_MOCK = createMockStep('Decrypt certificate', 'Decrypt certificate', 'IOS', [], ['LARGE_SECRET_PASSPHRASE']);
+const TESTBUILD__IOS__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = createMockStep(
'Configure AWS Credentials',
'Configure AWS Credentials',
'IOS',
['aws-access-key-id', 'aws-secret-access-key', 'aws-region'],
[],
);
-const TESTBUILD__IOS__RUN_FASTLANE__STEP_MOCK = utils.createMockStep('Run Fastlane', 'Run Fastlane', 'IOS', [], ['S3_ACCESS_KEY', 'S3_SECRET_ACCESS_KEY', 'S3_BUCKET', 'S3_REGION']);
-const TESTBUILD__IOS__UPLOAD_ARTIFACT__STEP_MOCK = utils.createMockStep('Upload Artifact', 'Upload Artifact', 'IOS', ['name', 'path'], []);
+const TESTBUILD__IOS__RUN_FASTLANE__STEP_MOCK = createMockStep('Run Fastlane', 'Run Fastlane', 'IOS', [], ['S3_ACCESS_KEY', 'S3_SECRET_ACCESS_KEY', 'S3_BUCKET', 'S3_REGION']);
+const TESTBUILD__IOS__UPLOAD_ARTIFACT__STEP_MOCK = createMockStep('Upload Artifact', 'Upload Artifact', 'IOS', ['name', 'path'], []);
const TESTBUILD__IOS__STEP_MOCKS = [
TESTBUILD__IOS__CHECKOUT__STEP_MOCK,
TESTBUILD__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK,
@@ -150,33 +151,33 @@ const TESTBUILD__IOS__STEP_MOCKS = [
TESTBUILD__IOS__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK,
TESTBUILD__IOS__RUN_FASTLANE__STEP_MOCK,
TESTBUILD__IOS__UPLOAD_ARTIFACT__STEP_MOCK,
-];
+] as const;
// desktop
-const TESTBUILD__DESKTOP__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'DESKTOP', ['ref'], []);
-const TESTBUILD__DESKTOP__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__DESKTOP__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'DESKTOP', ['ref'], []);
+const TESTBUILD__DESKTOP__CREATE_ENV_ADHOC__STEP_MOCK = createMockStep(
'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it',
'Creating .env.adhoc file based on staging',
'DESKTOP',
[],
[],
);
-const TESTBUILD__DESKTOP__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'DESKTOP', [], []);
-const TESTBUILD__DESKTOP__DECRYPT_DEVELOPER_ID_CERTIFICATE__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__DESKTOP__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'DESKTOP', [], []);
+const TESTBUILD__DESKTOP__DECRYPT_DEVELOPER_ID_CERTIFICATE__STEP_MOCK = createMockStep(
'Decrypt Developer ID Certificate',
'Decrypt Developer ID Certificate',
'DESKTOP',
[],
['DEVELOPER_ID_SECRET_PASSPHRASE'],
);
-const TESTBUILD__DESKTOP__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__DESKTOP__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = createMockStep(
'Configure AWS Credentials',
'Configure AWS Credentials',
'DESKTOP',
['aws-access-key-id', 'aws-secret-access-key', 'aws-region'],
[],
);
-const TESTBUILD__DESKTOP__BUILD_DESKTOP_APP_FOR_TESTING__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__DESKTOP__BUILD_DESKTOP_APP_FOR_TESTING__STEP_MOCK = createMockStep(
'Build desktop app for testing',
'Build desktop app for testing',
'DESKTOP',
@@ -190,28 +191,28 @@ const TESTBUILD__DESKTOP__STEP_MOCKS = [
TESTBUILD__DESKTOP__DECRYPT_DEVELOPER_ID_CERTIFICATE__STEP_MOCK,
TESTBUILD__DESKTOP__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK,
TESTBUILD__DESKTOP__BUILD_DESKTOP_APP_FOR_TESTING__STEP_MOCK,
-];
+] as const;
// web
-const TESTBUILD__WEB__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'WEB', ['ref'], []);
-const TESTBUILD__WEB__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__WEB__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'WEB', ['ref'], []);
+const TESTBUILD__WEB__CREATE_ENV_ADHOC__STEP_MOCK = createMockStep(
'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it',
'Creating .env.adhoc file based on staging',
'WEB',
[],
[],
);
-const TESTBUILD__WEB__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'WEB', [], []);
-const TESTBUILD__WEB__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__WEB__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'WEB', [], []);
+const TESTBUILD__WEB__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = createMockStep(
'Configure AWS Credentials',
'Configure AWS Credentials',
'WEB',
['aws-access-key-id', 'aws-secret-access-key', 'aws-region'],
[],
);
-const TESTBUILD__WEB__BUILD_WEB_FOR_TESTING__STEP_MOCK = utils.createMockStep('Build web for testing', 'Build web for testing', 'WEB', [], []);
-const TESTBUILD__WEB__BUILD_DOCS__STEP_MOCK = utils.createMockStep('Build docs', 'Build docs', 'WEB', [], []);
-const TESTBUILD__WEB__DEPLOY_TO_S3_FOR_INTERNAL_TESTING__STEP_MOCK = utils.createMockStep('Deploy to S3 for internal testing', 'Deploy to S3 for internal testing', 'WEB', [], []);
+const TESTBUILD__WEB__BUILD_WEB_FOR_TESTING__STEP_MOCK = createMockStep('Build web for testing', 'Build web for testing', 'WEB', [], []);
+const TESTBUILD__WEB__BUILD_DOCS__STEP_MOCK = createMockStep('Build docs', 'Build docs', 'WEB', [], []);
+const TESTBUILD__WEB__DEPLOY_TO_S3_FOR_INTERNAL_TESTING__STEP_MOCK = createMockStep('Deploy to S3 for internal testing', 'Deploy to S3 for internal testing', 'WEB', [], []);
const TESTBUILD__WEB__STEP_MOCKS = [
TESTBUILD__WEB__CHECKOUT__STEP_MOCK,
TESTBUILD__WEB__CREATE_ENV_ADHOC__STEP_MOCK,
@@ -220,23 +221,18 @@ const TESTBUILD__WEB__STEP_MOCKS = [
TESTBUILD__WEB__BUILD_WEB_FOR_TESTING__STEP_MOCK,
TESTBUILD__WEB__BUILD_DOCS__STEP_MOCK,
TESTBUILD__WEB__DEPLOY_TO_S3_FOR_INTERNAL_TESTING__STEP_MOCK,
-];
+] as const;
// postgithubcomment
-const TESTBUILD__POSTGITHUBCOMMENT__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'POSTGITHUBCOMMENT', ['ref'], []);
-const TESTBUILD__POSTGITHUBCOMMENT__DOWNLOAD_ARTIFACT__STEP_MOCK = utils.createMockStep('Download Artifact', 'Download Artifact', 'POSTGITHUBCOMMENT', [], []);
-const TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_ANDROID_PATHS__STEP_MOCK = utils.createMockStep(
- 'Read JSONs with android paths',
- 'Read JSONs with android paths',
- 'POSTGITHUBCOMMENT',
- [],
- [],
- {android_path: 'http://dummy.android.link'},
-);
-const TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_IOS_PATHS__STEP_MOCK = utils.createMockStep('Read JSONs with iOS paths', 'Read JSONs with iOS paths', 'POSTGITHUBCOMMENT', [], [], {
+const TESTBUILD__POSTGITHUBCOMMENT__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'POSTGITHUBCOMMENT', ['ref'], []);
+const TESTBUILD__POSTGITHUBCOMMENT__DOWNLOAD_ARTIFACT__STEP_MOCK = createMockStep('Download Artifact', 'Download Artifact', 'POSTGITHUBCOMMENT', [], []);
+const TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_ANDROID_PATHS__STEP_MOCK = createMockStep('Read JSONs with android paths', 'Read JSONs with android paths', 'POSTGITHUBCOMMENT', [], [], {
+ android_path: 'http://dummy.android.link',
+});
+const TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_IOS_PATHS__STEP_MOCK = createMockStep('Read JSONs with iOS paths', 'Read JSONs with iOS paths', 'POSTGITHUBCOMMENT', [], [], {
ios_path: 'http://dummy.ios.link',
});
-const TESTBUILD__POSTGITHUBCOMMENT__PUBLISH_LINKS_TO_APPS_FOR_DOWNLOAD__STEP_MOCK = utils.createMockStep(
+const TESTBUILD__POSTGITHUBCOMMENT__PUBLISH_LINKS_TO_APPS_FOR_DOWNLOAD__STEP_MOCK = createMockStep(
'Publish links to apps for download',
'Publish links to apps for download',
'POSTGITHUBCOMMENT',
@@ -249,9 +245,9 @@ const TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS = [
TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_ANDROID_PATHS__STEP_MOCK,
TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_IOS_PATHS__STEP_MOCK,
TESTBUILD__POSTGITHUBCOMMENT__PUBLISH_LINKS_TO_APPS_FOR_DOWNLOAD__STEP_MOCK,
-];
+] as const;
-module.exports = {
+export {
TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS,
TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS,
TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS,
diff --git a/workflow_tests/mocks/testMocks.js b/workflow_tests/mocks/testMocks.js
deleted file mode 100644
index 19011271bb47..000000000000
--- a/workflow_tests/mocks/testMocks.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const utils = require('../utils/utils');
-
-// jest
-const TEST__JEST__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'JEST', [], []);
-const TEST__JEST__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'JEST', [], []);
-const TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK = utils.createMockStep('Get number of CPU cores', 'Get number of CPU cores', 'JEST', [], [], {count: 8});
-const TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK = utils.createMockStep('Cache Jest cache', 'Cache Jest cache', 'JEST', ['path', 'key'], []);
-const TEST__JEST__JEST_TESTS__STEP_MOCK = utils.createMockStep('Jest tests', 'Jest tests', 'JEST', [], []);
-const TEST__JEST__STEP_MOCKS = [
- TEST__JEST__CHECKOUT__STEP_MOCK,
- TEST__JEST__SETUP_NODE__STEP_MOCK,
- TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK,
- TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK,
- TEST__JEST__JEST_TESTS__STEP_MOCK,
-];
-
-// shelltests
-const TEST__SHELLTESTS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'SHELLTESTS', [], []);
-const TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'SHELLTESTS', [], []);
-const TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK = utils.createMockStep('Test CI git logic', 'Test CI git logic', 'SHELLTESTS', [], []);
-const TEST__SHELLTESTS__STEP_MOCKS = [TEST__SHELLTESTS__CHECKOUT__STEP_MOCK, TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK, TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK];
-
-module.exports = {
- TEST__JEST__STEP_MOCKS,
- TEST__SHELLTESTS__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/testMocks.ts b/workflow_tests/mocks/testMocks.ts
new file mode 100644
index 000000000000..96f47d4204fb
--- /dev/null
+++ b/workflow_tests/mocks/testMocks.ts
@@ -0,0 +1,24 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// jest
+const TEST__JEST__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'JEST', [], []);
+const TEST__JEST__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'JEST', [], []);
+const TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK = createMockStep('Get number of CPU cores', 'Get number of CPU cores', 'JEST', [], [], {count: 8});
+const TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK = createMockStep('Cache Jest cache', 'Cache Jest cache', 'JEST', ['path', 'key'], []);
+const TEST__JEST__JEST_TESTS__STEP_MOCK = createMockStep('Jest tests', 'Jest tests', 'JEST', [], []);
+const TEST__JEST__STEP_MOCKS = [
+ TEST__JEST__CHECKOUT__STEP_MOCK,
+ TEST__JEST__SETUP_NODE__STEP_MOCK,
+ TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK,
+ TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK,
+ TEST__JEST__JEST_TESTS__STEP_MOCK,
+] as const;
+
+// shelltests
+const TEST__SHELLTESTS__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'SHELLTESTS', [], []);
+const TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'SHELLTESTS', [], []);
+const TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK = createMockStep('Test CI git logic', 'Test CI git logic', 'SHELLTESTS', [], []);
+const TEST__SHELLTESTS__STEP_MOCKS = [TEST__SHELLTESTS__CHECKOUT__STEP_MOCK, TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK, TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK] as const;
+
+export {TEST__JEST__STEP_MOCKS, TEST__SHELLTESTS__STEP_MOCKS};
diff --git a/workflow_tests/mocks/validateGithubActionsMocks.js b/workflow_tests/mocks/validateGithubActionsMocks.js
deleted file mode 100644
index e2d48932acf6..000000000000
--- a/workflow_tests/mocks/validateGithubActionsMocks.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const utils = require('../utils/utils');
-
-// verify
-const VALIDATEGITHUBACTIONS__VERIFY__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'VERIFY');
-const VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []);
-const VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK = utils.createMockStep(
- 'Verify Javascript Action Builds',
- 'Verify Javascript Action Builds',
- 'VERIFY',
- [],
- [],
-);
-const VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK = utils.createMockStep('Validate actions and workflows', 'Validate actions and workflows', 'VERIFY', [], []);
-const VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS = [
- VALIDATEGITHUBACTIONS__VERIFY__CHECKOUT__STEP_MOCK,
- VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK,
- VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK,
- VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK,
-];
-
-module.exports = {
- VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/validateGithubActionsMocks.ts b/workflow_tests/mocks/validateGithubActionsMocks.ts
new file mode 100644
index 000000000000..8249945dbfc7
--- /dev/null
+++ b/workflow_tests/mocks/validateGithubActionsMocks.ts
@@ -0,0 +1,19 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// verify
+const VALIDATEGITHUBACTIONS__VERIFY__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'VERIFY');
+const VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []);
+const VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK = createMockStep('Verify Javascript Action Builds', 'Verify Javascript Action Builds', 'VERIFY', [], []);
+const VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK = createMockStep('Validate actions and workflows', 'Validate actions and workflows', 'VERIFY', [], []);
+const VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS = [
+ VALIDATEGITHUBACTIONS__VERIFY__CHECKOUT__STEP_MOCK,
+ VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK,
+ VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK,
+ VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK,
+] as const;
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS,
+};
diff --git a/workflow_tests/mocks/verifyPodfileMocks.js b/workflow_tests/mocks/verifyPodfileMocks.js
deleted file mode 100644
index 0a82eebcc748..000000000000
--- a/workflow_tests/mocks/verifyPodfileMocks.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const utils = require('../utils/utils');
-
-// verify
-const VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'VERIFY');
-const VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []);
-const VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK = utils.createMockStep('Verify podfile', 'Verify podfile', 'VERIFY', [], []);
-const VERIFYPODFILE__VERIFY__STEP_MOCKS = [VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK, VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK, VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK];
-
-module.exports = {
- VERIFYPODFILE__VERIFY__STEP_MOCKS,
-};
diff --git a/workflow_tests/mocks/verifyPodfileMocks.ts b/workflow_tests/mocks/verifyPodfileMocks.ts
new file mode 100644
index 000000000000..b1f0669b8b00
--- /dev/null
+++ b/workflow_tests/mocks/verifyPodfileMocks.ts
@@ -0,0 +1,17 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
+
+// verify
+const VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK = createMockStep('Checkout', 'Checkout', 'VERIFY');
+const VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK = createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []);
+const VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK = createMockStep('Verify podfile', 'Verify podfile', 'VERIFY', [], []);
+const VERIFYPODFILE__VERIFY__STEP_MOCKS = [
+ VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK,
+ VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK,
+ VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK,
+] as const;
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ VERIFYPODFILE__VERIFY__STEP_MOCKS,
+};
diff --git a/workflow_tests/mocks/verifySignedCommitsMocks.js b/workflow_tests/mocks/verifySignedCommitsMocks.ts
similarity index 63%
rename from workflow_tests/mocks/verifySignedCommitsMocks.js
rename to workflow_tests/mocks/verifySignedCommitsMocks.ts
index a19fac809e55..953d09d1255f 100644
--- a/workflow_tests/mocks/verifySignedCommitsMocks.js
+++ b/workflow_tests/mocks/verifySignedCommitsMocks.ts
@@ -1,15 +1,17 @@
-const utils = require('../utils/utils');
+/* eslint-disable @typescript-eslint/naming-convention */
+import {createMockStep} from '../utils/utils';
// verifysignedcommits
-const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK = utils.createMockStep(
+const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK = createMockStep(
'Verify signed commits',
'Verify signed commits',
'VERIFYSIGNEDCOMMITS',
['GITHUB_TOKEN'],
[],
);
-const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS = [VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK];
+const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS = [VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK] as const;
-module.exports = {
+export {
+ // eslint-disable-next-line import/prefer-default-export
VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS,
};
diff --git a/workflow_tests/utils/utils.ts b/workflow_tests/utils/utils.ts
index dcc04aee02de..df4cc0468963 100644
--- a/workflow_tests/utils/utils.ts
+++ b/workflow_tests/utils/utils.ts
@@ -69,7 +69,7 @@ function createMockStep(
jobId: string | null = null,
inputs: string[] | null = null,
inEnvs: string[] | null = null,
- outputs: Record | null = null,
+ outputs: Record | null = null,
outEnvs: Record | null = null,
isSuccessful = true,
id: string | null = null,