From f0a9e603dcc42c5da9a4de26660fb7e463654bd5 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 6 Nov 2020 08:46:05 -1000 Subject: [PATCH 01/24] change name --- package-lock.json | 6 ++ package.json | 1 + src/CONFIG.js | 1 + src/libs/API.js | 5 ++ src/libs/actions/Report.js | 9 ++- tests/unit/reportActionTest.js | 128 +++++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/unit/reportActionTest.js diff --git a/package-lock.json b/package-lock.json index e666fafc2dfc..72a5992611bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18793,6 +18793,12 @@ "tweetnacl": "^1.0.3" } }, + "pusher-js-mock": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/pusher-js-mock/-/pusher-js-mock-0.3.3.tgz", + "integrity": "sha512-Qn8u167Qm+pU+ZhE/vTLL7msh3Zk6C4im4fXC/Vxsjv2anXRYhhBwt+eqFVTfGvhb0ZcwPtL8w5dkwKAFSiMGQ==", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", diff --git a/package.json b/package.json index b4adcb3d15c5..7218af47b27a 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "jest-circus": "^26.5.2", "jest-cli": "^26.5.2", "metro-react-native-babel-preset": "^0.61.0", + "pusher-js-mock": "^0.3.3", "react-hot-loader": "^4.12.21", "react-native-version": "^4.0.0", "react-test-renderer": "16.13.1", diff --git a/src/CONFIG.js b/src/CONFIG.js index 7dcc2f4a01ae..219690f868ed 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -11,6 +11,7 @@ export default { }, // eslint-disable-next-line no-undef IS_IN_PRODUCTION: Platform.OS === 'web' ? process.env.NODE_ENV === 'production' : !__DEV__, + IS_JEST_RUNNING: typeof jest !== 'undefined', PUSHER: { APP_KEY: Config.PUSHER_APP_KEY, CLUSTER: 'mt1', diff --git a/src/libs/API.js b/src/libs/API.js index dd75538b501c..dd1d18a8bc75 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -71,6 +71,11 @@ function isAuthTokenRequired(command) { * @returns {Promise} */ function queueRequest(command, data) { + // Mock all requests for now + if (CONFIG.IS_JEST_RUNNING) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { // Add the write request to a queue of actions to perform networkRequestQueue.push({ diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index b581d38a5a6b..c248965bed29 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -15,6 +15,7 @@ import Visibility from '../Visibility'; import ROUTES from '../../ROUTES'; import NetworkConnection from '../NetworkConnection'; import {hide as hideSidebar} from './Sidebar'; +import CONFIG from '../../CONFIG'; let currentUserEmail; let currentUserAccountID; @@ -281,7 +282,13 @@ function subscribeToReportCommentEvents() { } const pusherChannelName = `private-user-accountID-${currentUserAccountID}`; - if (Pusher.isSubscribed(pusherChannelName) || Pusher.isAlreadySubscribing(pusherChannelName)) { + + // This early return is to prevent duplicate subscriptions. The mocks for jest + // are always subscribed so we skip this check when testing. + if (!CONFIG.IS_JEST_RUNNING + && Pusher.isSubscribed(pusherChannelName) + || Pusher.isAlreadySubscribing(pusherChannelName) + ) { return; } diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js new file mode 100644 index 000000000000..9f60fbe1c2de --- /dev/null +++ b/tests/unit/reportActionTest.js @@ -0,0 +1,128 @@ +import Ion from '../../src/libs/Ion'; +import IONKEYS from '../../src/IONKEYS'; +import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; +import * as Pusher from '../../src/libs/Pusher/pusher'; +import moment from 'moment'; +const resolveAllPromises = () => new Promise(setImmediate); + +Ion.registerLogger(() => {}); +Ion.init({ + keys: IONKEYS, +}); + +jest.mock('../../node_modules/urbanairship-react-native', () => { + const airshipMock = require('./mocks/urbanairship-react-native'); + airshipMock.EventType = { + PushReceived: 'pushReceived', + }; + return airshipMock; +}); + +jest.mock('../../node_modules/@react-native-community/async-storage', () => + require('./mocks/@react-native-community/async-storage') +); + +jest.mock('../../node_modules/@react-native-community/push-notification-ios', () => + require('./mocks/@react-native-community/push-notification-ios') +); + +jest.mock('pusher-js/react-native', () => + require("pusher-js-mock").PusherMock +); + +jest.mock('../../node_modules/@react-native-community/netinfo', () => + require('./mocks/@react-native-community/netinfo') +); + +describe('Report Action', () => { + it('should subscribe to the private-user-accountID channel', async (done) => { + const reportID = 1; + const CREATED_ACTION = { + actionName: 'CREATED', + automatic: false, + created: 'Nov 6 2020 9:14am PST', + message: [ + {type: "TEXT", style: "strong", text: "__fake__"}, + {type: "TEXT", style: "normal", text: " created this report"}, + ], + person: [{type: "TEXT", style: "strong", text: "__fake__"}], + sequenceNumber: 0, + shouldShow: true, + timestamp: 1604682894, + }; + + const mockIonCallback = jest.fn(); + + Pusher.init(); + + // Set up fake accountID, email, and reportActions key + await Ion.set(IONKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); + await Ion.set(IONKEYS.MY_PERSONAL_DETAILS, {}); + + // Simulate fetching a brand new report and actions + await Ion.set(`${IONKEYS.COLLECTION.REPORT}${reportID}`, { + reportID, + reportName: 'Test User', + }); + + await Ion.set(`${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { + [CREATED_ACTION.sequenceNumber]: CREATED_ACTION, + }); + + Ion.connect({ + key: `${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + initWithStoredValues: false, + callback: (val) => { + try { + mockIonCallback(val); + + // Verify that the optimistic comment has a loading state + if (mockIonCallback.mock.calls.length === 1) { + expect(val[0]).toStrictEqual(CREATED_ACTION); + expect(val[1]).toBeTruthy(); + expect(val[1].loading).toBe(true); + return; + } + + // Verify the loading state is now false + expect(val[1].loading).toBe(false); + done(); + } catch (err) { + done(err); + } + }, + }); + + subscribeToReportCommentEvents(); + const PRIVATE_USER_CHANNEL = 'private-user-accountID-1'; + const isSubscribedToPrivateChannel = Pusher.isSubscribed(PRIVATE_USER_CHANNEL); + expect(isSubscribedToPrivateChannel).toBe(true); + + // Add an action + addAction(reportID, 'Testing a comment'); + + // We have to resolve all the promises here otherwise we will end up + // handling the mock pusher payload before the optimistic comment set in + // addAction() + await resolveAllPromises(); + + // Simulate handling the comment push event from the server + const testReportCommentPusherPayload = { + reportID, + reportAction: { + actionName: 'ADDCOMMENT', + automatic: false, + created: 'Oct 28 2020 4:29pm PDT', + message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + shouldShow: true, + timestamp: moment().unix(), + actorAccountID: 1, + sequenceNumber: 1, + }, + }; + + const channel = Pusher.getChannel(PRIVATE_USER_CHANNEL); + channel.emit('reportComment', testReportCommentPusherPayload); + }); +}); From 1c37dd53f30aa66dd3db03dd7be320e974d1b6b9 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 6 Nov 2020 09:04:34 -1000 Subject: [PATCH 02/24] fix created being in the wrong order --- tests/unit/reportActionTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js index 9f60fbe1c2de..2eb0968ef277 100644 --- a/tests/unit/reportActionTest.js +++ b/tests/unit/reportActionTest.js @@ -40,7 +40,7 @@ describe('Report Action', () => { const CREATED_ACTION = { actionName: 'CREATED', automatic: false, - created: 'Nov 6 2020 9:14am PST', + created: 'Oct 28 2020 4:29pm PDT', message: [ {type: "TEXT", style: "strong", text: "__fake__"}, {type: "TEXT", style: "normal", text: " created this report"}, @@ -112,7 +112,7 @@ describe('Report Action', () => { reportAction: { actionName: 'ADDCOMMENT', automatic: false, - created: 'Oct 28 2020 4:29pm PDT', + created: 'Nov 6 2020 9:14am PST', message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], shouldShow: true, From d4e9ef1faad36a8643608a87d5f0f68af5ef7cfb Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 10 Nov 2020 07:18:43 -1000 Subject: [PATCH 03/24] fix style --- tests/unit/reportActionTest.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js index 2eb0968ef277..43ee6c3e4f8f 100644 --- a/tests/unit/reportActionTest.js +++ b/tests/unit/reportActionTest.js @@ -1,8 +1,8 @@ +import moment from 'moment'; import Ion from '../../src/libs/Ion'; import IONKEYS from '../../src/IONKEYS'; import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import * as Pusher from '../../src/libs/Pusher/pusher'; -import moment from 'moment'; const resolveAllPromises = () => new Promise(setImmediate); Ion.registerLogger(() => {}); @@ -18,21 +18,21 @@ jest.mock('../../node_modules/urbanairship-react-native', () => { return airshipMock; }); -jest.mock('../../node_modules/@react-native-community/async-storage', () => +jest.mock('../../node_modules/@react-native-community/async-storage', () => ( require('./mocks/@react-native-community/async-storage') -); +)); -jest.mock('../../node_modules/@react-native-community/push-notification-ios', () => +jest.mock('../../node_modules/@react-native-community/push-notification-ios', () => ( require('./mocks/@react-native-community/push-notification-ios') -); +)); -jest.mock('pusher-js/react-native', () => - require("pusher-js-mock").PusherMock -); +jest.mock('pusher-js/react-native', () => ( + require('pusher-js-mock').PusherMock +)); -jest.mock('../../node_modules/@react-native-community/netinfo', () => +jest.mock('../../node_modules/@react-native-community/netinfo', () => ( require('./mocks/@react-native-community/netinfo') -); +)); describe('Report Action', () => { it('should subscribe to the private-user-accountID channel', async (done) => { @@ -42,10 +42,10 @@ describe('Report Action', () => { automatic: false, created: 'Oct 28 2020 4:29pm PDT', message: [ - {type: "TEXT", style: "strong", text: "__fake__"}, - {type: "TEXT", style: "normal", text: " created this report"}, + {type: 'TEXT', style: 'strong', text: '__fake__'}, + {type: 'TEXT', style: 'normal', text: ' created this report'}, ], - person: [{type: "TEXT", style: "strong", text: "__fake__"}], + person: [{type: 'TEXT', style: 'strong', text: '__fake__'}], sequenceNumber: 0, shouldShow: true, timestamp: 1604682894, From 29d02a1544eaac83d09fb99f7c8210a48379dd60 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 10 Nov 2020 07:21:16 -1000 Subject: [PATCH 04/24] fix style then get rid of async --- tests/unit/reportActionTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js index 43ee6c3e4f8f..a0cf115392e3 100644 --- a/tests/unit/reportActionTest.js +++ b/tests/unit/reportActionTest.js @@ -3,6 +3,7 @@ import Ion from '../../src/libs/Ion'; import IONKEYS from '../../src/IONKEYS'; import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import * as Pusher from '../../src/libs/Pusher/pusher'; + const resolveAllPromises = () => new Promise(setImmediate); Ion.registerLogger(() => {}); From 597090957ed710690ea627174d028905c2b13502 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 10 Nov 2020 07:47:37 -1000 Subject: [PATCH 05/24] fix style --- package.json | 1 + src/libs/actions/Report.js | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7218af47b27a..9f680cfcdb7c 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "android-build": "fastlane android build", "test": "jest", "lint": "eslint .", + "lint-tests": "eslint tests/**", "postversion": "react-native-version", "print-version": "echo $npm_package_version", "detox-build": "detox build --configuration ios.sim.debug", diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index c248965bed29..fec451db950b 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -283,12 +283,13 @@ function subscribeToReportCommentEvents() { const pusherChannelName = `private-user-accountID-${currentUserAccountID}`; - // This early return is to prevent duplicate subscriptions. The mocks for jest - // are always subscribed so we skip this check when testing. - if (!CONFIG.IS_JEST_RUNNING - && Pusher.isSubscribed(pusherChannelName) - || Pusher.isAlreadySubscribing(pusherChannelName) - ) { + const pusherSubscribedOrSubscribing = Pusher.isSubscribed(pusherChannelName) + || Pusher.isAlreadySubscribing(pusherChannelName); + + // This check is to prevent duplicate subscriptions. The mock channels for jest + // are always subscribed when created so we skip this check when testing or else it + // will prevent us from binding an even to the channel. + if (!CONFIG.IS_JEST_RUNNING && pusherSubscribedOrSubscribing) { return; } From ccc37786cb249a84c30b4437296152195772d949 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 11 Nov 2020 17:03:37 -1000 Subject: [PATCH 06/24] fix tests to use Onyx --- tests/unit/reportActionTest.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js index a0cf115392e3..933390078d04 100644 --- a/tests/unit/reportActionTest.js +++ b/tests/unit/reportActionTest.js @@ -1,14 +1,15 @@ import moment from 'moment'; -import Ion from '../../src/libs/Ion'; -import IONKEYS from '../../src/IONKEYS'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '../../src/ONYXKEYS'; import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import * as Pusher from '../../src/libs/Pusher/pusher'; const resolveAllPromises = () => new Promise(setImmediate); -Ion.registerLogger(() => {}); -Ion.init({ - keys: IONKEYS, +Onyx.registerLogger(() => {}); +Onyx.init({ + keys: ONYXKEYS, + registerStorageEventListener: () => {}, }); jest.mock('../../node_modules/urbanairship-react-native', () => { @@ -52,33 +53,33 @@ describe('Report Action', () => { timestamp: 1604682894, }; - const mockIonCallback = jest.fn(); + const mockOnyxCallback = jest.fn(); Pusher.init(); // Set up fake accountID, email, and reportActions key - await Ion.set(IONKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); - await Ion.set(IONKEYS.MY_PERSONAL_DETAILS, {}); + await Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); + await Onyx.set(ONYXKEYS.MY_PERSONAL_DETAILS, {}); // Simulate fetching a brand new report and actions - await Ion.set(`${IONKEYS.COLLECTION.REPORT}${reportID}`, { + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { reportID, reportName: 'Test User', }); - await Ion.set(`${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { [CREATED_ACTION.sequenceNumber]: CREATED_ACTION, }); - Ion.connect({ - key: `${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, initWithStoredValues: false, callback: (val) => { try { - mockIonCallback(val); + mockOnyxCallback(val); // Verify that the optimistic comment has a loading state - if (mockIonCallback.mock.calls.length === 1) { + if (mockOnyxCallback.mock.calls.length === 1) { expect(val[0]).toStrictEqual(CREATED_ACTION); expect(val[1]).toBeTruthy(); expect(val[1].loading).toBe(true); From 478f09436898da4a5011e34106028e5f59dac30b Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 2 Dec 2020 08:07:01 -1000 Subject: [PATCH 07/24] fix API.js --- src/libs/API.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/libs/API.js b/src/libs/API.js index 9d3c4822cee6..29e50a1d5842 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -36,22 +36,6 @@ function isAuthTokenRequired(command) { * @param {Object} parameters * @returns {Object} */ -<<<<<<< HEAD -function queueRequest(command, data) { - // Mock all requests for now - if (CONFIG.IS_JEST_RUNNING) { - return Promise.resolve(); - } - - return new Promise((resolve, reject) => { - // Add the write request to a queue of actions to perform - networkRequestQueue.push({ - command, - data, - resolve, - reject, - }); -======= function addAuthTokenToParameters(command, parameters) { const finalParameters = {...parameters}; @@ -65,7 +49,6 @@ function addAuthTokenToParameters(command, parameters) { redirectToSignIn(); return; } ->>>>>>> origin finalParameters.authToken = authToken; } From 918fa0a0bd5c6ab35711bc383ab6cb8469bd76aa Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 2 Dec 2020 09:20:44 -1000 Subject: [PATCH 08/24] tests broke somehow --- src/libs/Network.js | 2 +- tests/unit/reportActionTest.js | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libs/Network.js b/src/libs/Network.js index 114dc7bfdc84..7906062c633c 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -75,7 +75,7 @@ setInterval(processNetworkRequestQueue, 1000); * @returns {Promise} */ function post(command, data, type) { - // Mock all post requests + // // Mock all post requests if (CONFIG.IS_JEST_RUNNING) { return Promise.resolve(); } diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js index 933390078d04..62479930c74d 100644 --- a/tests/unit/reportActionTest.js +++ b/tests/unit/reportActionTest.js @@ -3,6 +3,8 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../src/ONYXKEYS'; import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import * as Pusher from '../../src/libs/Pusher/pusher'; +import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; +import CONFIG from '../../src/CONFIG'; const resolveAllPromises = () => new Promise(setImmediate); @@ -24,6 +26,15 @@ jest.mock('../../node_modules/@react-native-community/async-storage', () => ( require('./mocks/@react-native-community/async-storage') )); +// jest.mock('../../src/libs/Network', () => { +// return { +// post: () => {}, +// registerParameterEnhancer: (params) => { +// return params; +// }, +// }; +// }); + jest.mock('../../node_modules/@react-native-community/push-notification-ios', () => ( require('./mocks/@react-native-community/push-notification-ios') )); @@ -32,6 +43,10 @@ jest.mock('pusher-js/react-native', () => ( require('pusher-js-mock').PusherMock )); +jest.mock('../../node_modules/react-native-config', () => ( + require('./mocks/react-native-config') +)); + jest.mock('../../node_modules/@react-native-community/netinfo', () => ( require('./mocks/@react-native-community/netinfo') )); @@ -55,7 +70,12 @@ describe('Report Action', () => { const mockOnyxCallback = jest.fn(); - Pusher.init(); + PusherConnectionManager.init(); + Pusher.init({ + appKey: CONFIG.PUSHER.APP_KEY, + cluster: CONFIG.PUSHER.CLUSTER, + authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Push_Authenticate`, + }); // Set up fake accountID, email, and reportActions key await Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); From da7746ea01bd5a78df7aad316382460a83ae0140 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 1 Jan 2021 08:37:56 -1000 Subject: [PATCH 09/24] Fix this test up --- .../@react-native-community/async-storage.js | 0 .../@react-native-community/netinfo.js | 0 .../push-notification-ios.js | 0 __mocks__/pusher-js/react-native.js | 3 + .../react-native-config.js | 0 .../react-native-image-picker.js | 0 .../mocks => __mocks__}/react-native-pdf.js | 0 __mocks__/urbanairship-react-native.js | 8 + package.json | 4 +- src/libs/actions/Report.js | 2 +- tests/actions/ReportTest.js | 80 ++++++++++ tests/unit/loginTest.js | 14 -- tests/unit/mocks/urbanairship-react-native.js | 3 - tests/unit/reportActionTest.js | 150 ------------------ tests/utils/waitForPromisesToResolve.js | 1 + 15 files changed, 95 insertions(+), 170 deletions(-) rename {tests/unit/mocks => __mocks__}/@react-native-community/async-storage.js (100%) rename {tests/unit/mocks => __mocks__}/@react-native-community/netinfo.js (100%) rename {tests/unit/mocks => __mocks__}/@react-native-community/push-notification-ios.js (100%) create mode 100644 __mocks__/pusher-js/react-native.js rename {tests/unit/mocks => __mocks__}/react-native-config.js (100%) rename {tests/unit/mocks => __mocks__}/react-native-image-picker.js (100%) rename {tests/unit/mocks => __mocks__}/react-native-pdf.js (100%) create mode 100644 __mocks__/urbanairship-react-native.js create mode 100644 tests/actions/ReportTest.js delete mode 100644 tests/unit/mocks/urbanairship-react-native.js delete mode 100644 tests/unit/reportActionTest.js create mode 100644 tests/utils/waitForPromisesToResolve.js diff --git a/tests/unit/mocks/@react-native-community/async-storage.js b/__mocks__/@react-native-community/async-storage.js similarity index 100% rename from tests/unit/mocks/@react-native-community/async-storage.js rename to __mocks__/@react-native-community/async-storage.js diff --git a/tests/unit/mocks/@react-native-community/netinfo.js b/__mocks__/@react-native-community/netinfo.js similarity index 100% rename from tests/unit/mocks/@react-native-community/netinfo.js rename to __mocks__/@react-native-community/netinfo.js diff --git a/tests/unit/mocks/@react-native-community/push-notification-ios.js b/__mocks__/@react-native-community/push-notification-ios.js similarity index 100% rename from tests/unit/mocks/@react-native-community/push-notification-ios.js rename to __mocks__/@react-native-community/push-notification-ios.js diff --git a/__mocks__/pusher-js/react-native.js b/__mocks__/pusher-js/react-native.js new file mode 100644 index 000000000000..0cb6afb4bb1d --- /dev/null +++ b/__mocks__/pusher-js/react-native.js @@ -0,0 +1,3 @@ +import {PusherMock} from 'pusher-js-mock'; + +export default PusherMock; diff --git a/tests/unit/mocks/react-native-config.js b/__mocks__/react-native-config.js similarity index 100% rename from tests/unit/mocks/react-native-config.js rename to __mocks__/react-native-config.js diff --git a/tests/unit/mocks/react-native-image-picker.js b/__mocks__/react-native-image-picker.js similarity index 100% rename from tests/unit/mocks/react-native-image-picker.js rename to __mocks__/react-native-image-picker.js diff --git a/tests/unit/mocks/react-native-pdf.js b/__mocks__/react-native-pdf.js similarity index 100% rename from tests/unit/mocks/react-native-pdf.js rename to __mocks__/react-native-pdf.js diff --git a/__mocks__/urbanairship-react-native.js b/__mocks__/urbanairship-react-native.js new file mode 100644 index 000000000000..5893b5798ec0 --- /dev/null +++ b/__mocks__/urbanairship-react-native.js @@ -0,0 +1,8 @@ +export default { + setUserNotificationsEnabled: jest.fn(), +}; + +export const EventType = { + NotificationResponse: 'notificationResponse', + PushReceived: 'pushReceived', +}; diff --git a/package.json b/package.json index b3bc65bee2b5..cae3b3bb09e0 100644 --- a/package.json +++ b/package.json @@ -127,10 +127,10 @@ "node_modules/(?!react-native)/" ], "testPathIgnorePatterns": [ - "/node_modules/", - "/tests/unit/mocks/" + "/node_modules/" ], "testMatch": [ + "**/tests/actions/**/*.[jt]s?(x)", "**/tests/unit/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)" ], diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index d2daf92bd243..ebe2dd7e9a75 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -294,7 +294,7 @@ function subscribeToReportCommentEvents() { // This check is to prevent duplicate subscriptions. The mock channels for jest // are always subscribed when created so we skip this check when testing or else it - // will prevent us from binding an even to the channel. + // will prevent us from binding an event to the channel. if (!CONFIG.IS_JEST_RUNNING && pusherSubscribedOrSubscribing) { return; } diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js new file mode 100644 index 000000000000..1424a0a0e6ed --- /dev/null +++ b/tests/actions/ReportTest.js @@ -0,0 +1,80 @@ +import moment from 'moment'; +import Onyx from 'react-native-onyx'; +import AsyncStorage from '@react-native-community/async-storage'; +import ONYXKEYS from '../../src/ONYXKEYS'; +import * as Pusher from '../../src/libs/Pusher/pusher'; +import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; +import CONFIG from '../../src/CONFIG'; +import {subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; +import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; + +// This step just sets up Onyx for testing. +Onyx.registerLogger(() => {}); +Onyx.init({ + keys: ONYXKEYS, + registerStorageEventListener: () => {}, +}); + +describe('actions/Report', () => { + it('should subscribe to the private-user-accountID channel and handle reportComment events', () => { + const REPORT_ID = 1; + const ACTION_ID = 1; + const REPORT_ACTION = { + actionName: 'ADDCOMMENT', + automatic: false, + created: 'Nov 6 2020 9:14am PST', + message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + shouldShow: true, + timestamp: moment().unix(), + actorAccountID: 1, + sequenceNumber: ACTION_ID, + isAttachment: false, + loading: false, + }; + + // Connect to Pusher + PusherConnectionManager.init(); + Pusher.init({ + appKey: CONFIG.PUSHER.APP_KEY, + cluster: CONFIG.PUSHER.CLUSTER, + authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Push_Authenticate`, + }); + + // Set up Onyx with some test user data + Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); + return waitForPromisesToResolve() + .then(() => { + subscribeToReportCommentEvents(); + return waitForPromisesToResolve(); + }) + .then(() => { + const PRIVATE_USER_CHANNEL = 'private-user-accountID-1'; + const isSubscribedToPrivateChannel = Pusher.isSubscribed(PRIVATE_USER_CHANNEL); + expect(isSubscribedToPrivateChannel).toBe(true); + + // Now that we are subscribed we need to simulate a reportComment action Pusher event. + // Then verify that action was handled correctly and merged into the reportActions. + const channel = Pusher.getChannel(PRIVATE_USER_CHANNEL); + channel.emit('reportComment', { + reportID: REPORT_ID, + reportAction: REPORT_ACTION, + }); + + // Once this happens we should see the comment get processed by the callback so we must wait for + // for promises to resolve again/ + return waitForPromisesToResolve(); + }) + .then(() => ( + + // The only thing left to do is check the report actions and verify the comment has been merged into + // the store. Onyx does not have a getter method so we will ask AsyncStorage directly. + AsyncStorage.getItem(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`) + )) + .then((reportActionData) => { + const data = JSON.parse(reportActionData); + const resultAction = data[ACTION_ID]; + expect(resultAction).toEqual(REPORT_ACTION); + }); + }); +}); diff --git a/tests/unit/loginTest.js b/tests/unit/loginTest.js index 6a04996f81c0..8bc0ec19d6a1 100644 --- a/tests/unit/loginTest.js +++ b/tests/unit/loginTest.js @@ -10,20 +10,6 @@ import renderer from 'react-test-renderer'; import App from '../../src/App'; import Expensify from '../../src/Expensify'; -jest.mock('../../node_modules/@react-native-community/async-storage', - () => require('./mocks/@react-native-community/async-storage')); -jest.mock('../../node_modules/@react-native-community/netinfo', - () => require('./mocks/@react-native-community/netinfo')); -jest.mock('../../node_modules/react-native-config', - () => require('./mocks/react-native-config')); -jest.mock('../../node_modules/react-native-image-picker', - () => require('./mocks/react-native-image-picker')); -jest.mock('../../node_modules/urbanairship-react-native', - () => require('./mocks/urbanairship-react-native')); -jest.mock('../../node_modules/react-native-pdf', - () => require('./mocks/react-native-pdf')); - - describe('AppComponent', () => { it('renders correctly', () => { renderer.create(); diff --git a/tests/unit/mocks/urbanairship-react-native.js b/tests/unit/mocks/urbanairship-react-native.js deleted file mode 100644 index fe99d594f483..000000000000 --- a/tests/unit/mocks/urbanairship-react-native.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - setUserNotificationsEnabled: jest.fn(), -}; diff --git a/tests/unit/reportActionTest.js b/tests/unit/reportActionTest.js deleted file mode 100644 index 62479930c74d..000000000000 --- a/tests/unit/reportActionTest.js +++ /dev/null @@ -1,150 +0,0 @@ -import moment from 'moment'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../../src/ONYXKEYS'; -import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; -import * as Pusher from '../../src/libs/Pusher/pusher'; -import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; -import CONFIG from '../../src/CONFIG'; - -const resolveAllPromises = () => new Promise(setImmediate); - -Onyx.registerLogger(() => {}); -Onyx.init({ - keys: ONYXKEYS, - registerStorageEventListener: () => {}, -}); - -jest.mock('../../node_modules/urbanairship-react-native', () => { - const airshipMock = require('./mocks/urbanairship-react-native'); - airshipMock.EventType = { - PushReceived: 'pushReceived', - }; - return airshipMock; -}); - -jest.mock('../../node_modules/@react-native-community/async-storage', () => ( - require('./mocks/@react-native-community/async-storage') -)); - -// jest.mock('../../src/libs/Network', () => { -// return { -// post: () => {}, -// registerParameterEnhancer: (params) => { -// return params; -// }, -// }; -// }); - -jest.mock('../../node_modules/@react-native-community/push-notification-ios', () => ( - require('./mocks/@react-native-community/push-notification-ios') -)); - -jest.mock('pusher-js/react-native', () => ( - require('pusher-js-mock').PusherMock -)); - -jest.mock('../../node_modules/react-native-config', () => ( - require('./mocks/react-native-config') -)); - -jest.mock('../../node_modules/@react-native-community/netinfo', () => ( - require('./mocks/@react-native-community/netinfo') -)); - -describe('Report Action', () => { - it('should subscribe to the private-user-accountID channel', async (done) => { - const reportID = 1; - const CREATED_ACTION = { - actionName: 'CREATED', - automatic: false, - created: 'Oct 28 2020 4:29pm PDT', - message: [ - {type: 'TEXT', style: 'strong', text: '__fake__'}, - {type: 'TEXT', style: 'normal', text: ' created this report'}, - ], - person: [{type: 'TEXT', style: 'strong', text: '__fake__'}], - sequenceNumber: 0, - shouldShow: true, - timestamp: 1604682894, - }; - - const mockOnyxCallback = jest.fn(); - - PusherConnectionManager.init(); - Pusher.init({ - appKey: CONFIG.PUSHER.APP_KEY, - cluster: CONFIG.PUSHER.CLUSTER, - authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Push_Authenticate`, - }); - - // Set up fake accountID, email, and reportActions key - await Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); - await Onyx.set(ONYXKEYS.MY_PERSONAL_DETAILS, {}); - - // Simulate fetching a brand new report and actions - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - reportID, - reportName: 'Test User', - }); - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { - [CREATED_ACTION.sequenceNumber]: CREATED_ACTION, - }); - - Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, - initWithStoredValues: false, - callback: (val) => { - try { - mockOnyxCallback(val); - - // Verify that the optimistic comment has a loading state - if (mockOnyxCallback.mock.calls.length === 1) { - expect(val[0]).toStrictEqual(CREATED_ACTION); - expect(val[1]).toBeTruthy(); - expect(val[1].loading).toBe(true); - return; - } - - // Verify the loading state is now false - expect(val[1].loading).toBe(false); - done(); - } catch (err) { - done(err); - } - }, - }); - - subscribeToReportCommentEvents(); - const PRIVATE_USER_CHANNEL = 'private-user-accountID-1'; - const isSubscribedToPrivateChannel = Pusher.isSubscribed(PRIVATE_USER_CHANNEL); - expect(isSubscribedToPrivateChannel).toBe(true); - - // Add an action - addAction(reportID, 'Testing a comment'); - - // We have to resolve all the promises here otherwise we will end up - // handling the mock pusher payload before the optimistic comment set in - // addAction() - await resolveAllPromises(); - - // Simulate handling the comment push event from the server - const testReportCommentPusherPayload = { - reportID, - reportAction: { - actionName: 'ADDCOMMENT', - automatic: false, - created: 'Nov 6 2020 9:14am PST', - message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], - person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], - shouldShow: true, - timestamp: moment().unix(), - actorAccountID: 1, - sequenceNumber: 1, - }, - }; - - const channel = Pusher.getChannel(PRIVATE_USER_CHANNEL); - channel.emit('reportComment', testReportCommentPusherPayload); - }); -}); diff --git a/tests/utils/waitForPromisesToResolve.js b/tests/utils/waitForPromisesToResolve.js new file mode 100644 index 000000000000..dde9189dba1c --- /dev/null +++ b/tests/utils/waitForPromisesToResolve.js @@ -0,0 +1 @@ +export default () => new Promise(setImmediate); From f65b2020dd935593caad55a016d31e3a6cda4582 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 1 Jan 2021 08:40:43 -1000 Subject: [PATCH 10/24] remove some things --- package.json | 1 - src/libs/Network.js | 6 ------ 2 files changed, 7 deletions(-) diff --git a/package.json b/package.json index cae3b3bb09e0..7b85c669697a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "ios-build": "fastlane ios build", "android-build": "fastlane android build", "test": "jest", - "lint-tests": "eslint tests/**", "lint": "eslint .", "postversion": "react-native-version", "print-version": "echo $npm_package_version", diff --git a/src/libs/Network.js b/src/libs/Network.js index 069f253cae25..0be50b56407c 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -3,7 +3,6 @@ import Onyx from 'react-native-onyx'; import HttpUtils from './HttpUtils'; import NetworkConnection from './NetworkConnection'; import ONYXKEYS from '../ONYXKEYS'; -import CONFIG from '../CONFIG'; let isQueuePaused = false; @@ -83,11 +82,6 @@ setInterval(processNetworkRequestQueue, 1000); * @returns {Promise} */ function post(command, data, type) { - // // Mock all post requests - if (CONFIG.IS_JEST_RUNNING) { - return Promise.resolve(); - } - return new Promise((resolve, reject) => { // Add the write request to a queue of actions to perform networkRequestQueue.push({ From fa536527be4af8899bb926447fc4de40938fa779 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 1 Jan 2021 08:49:38 -1000 Subject: [PATCH 11/24] add comment --- src/libs/actions/Report.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index ebe2dd7e9a75..931530f457c4 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -292,9 +292,10 @@ function subscribeToReportCommentEvents() { const pusherSubscribedOrSubscribing = Pusher.isSubscribed(pusherChannelName) || Pusher.isAlreadySubscribing(pusherChannelName); - // This check is to prevent duplicate subscriptions. The mock channels for jest - // are always subscribed when created so we skip this check when testing or else it - // will prevent us from binding an event to the channel. + // This check is to prevent duplicate subscriptions. + // Note: When running automated tests the act of calling Pusher.isSubscribed will create a mock + // channel already in a subscribed state. So, we must skip this check when tests are running or else it + // will prevent us from binding an event to the channel below. if (!CONFIG.IS_JEST_RUNNING && pusherSubscribedOrSubscribing) { return; } From 2f8dae63ce457142d8ea979f53f66eb7a8cab2fa Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 7 Jan 2021 14:55:45 -1000 Subject: [PATCH 12/24] fix ua mocks --- __mocks__/urbanairship-react-native.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/__mocks__/urbanairship-react-native.js b/__mocks__/urbanairship-react-native.js index fe99d594f483..481cec4e40c9 100644 --- a/__mocks__/urbanairship-react-native.js +++ b/__mocks__/urbanairship-react-native.js @@ -1,3 +1,12 @@ export default { setUserNotificationsEnabled: jest.fn(), }; + +const EventType = { + NotificationResponse: 'notificationResponse', + PushReceived: 'pushReceived', +}; + +export { + EventType, +}; From 39684fe113c1d0bfd4c696420ed750b349d2ad1e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 7 Jan 2021 14:56:40 -1000 Subject: [PATCH 13/24] remove uneeded --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8b9c207b7663..f2e075537738 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,6 @@ "/node_modules/" ], "testMatch": [ - "**/tests/actions/**/*.[jt]s?(x)", "**/tests/unit/**/*.[jt]s?(x)", "**/tests/actions/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)" From 9704aa99b6013756f1df9adf5fccfd744b26ddc7 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jan 2021 15:02:08 -1000 Subject: [PATCH 14/24] Mock Pusher methods instead of adding the IS_JEST_RUNNING --- src/CONFIG.js | 1 - src/libs/actions/Report.js | 10 +--------- tests/actions/ReportTest.js | 37 +++++++++++++++---------------------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/CONFIG.js b/src/CONFIG.js index f68e496d201c..d1a0f6b0ae91 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -40,7 +40,6 @@ export default { }, // eslint-disable-next-line no-undef IS_IN_PRODUCTION: Platform.OS === 'web' ? process.env.NODE_ENV === 'production' : !__DEV__, - IS_JEST_RUNNING: typeof jest !== 'undefined', PUSHER: { APP_KEY: lodashGet(Config, 'PUSHER_APP_KEY', '268df511a204fbb60884'), CLUSTER: 'mt1', diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f6fc267bae5e..3daa2fbb84bf 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -288,15 +288,7 @@ function subscribeToReportCommentEvents() { } const pusherChannelName = `private-user-accountID-${currentUserAccountID}`; - - const pusherSubscribedOrSubscribing = Pusher.isSubscribed(pusherChannelName) - || Pusher.isAlreadySubscribing(pusherChannelName); - - // This check is to prevent duplicate subscriptions. - // Note: When running automated tests the act of calling Pusher.isSubscribed will create a mock - // channel already in a subscribed state. So, we must skip this check when tests are running or else it - // will prevent us from binding an event to the channel below. - if (!CONFIG.IS_JEST_RUNNING && pusherSubscribedOrSubscribing) { + if (Pusher.isSubscribed(pusherChannelName) || Pusher.isAlreadySubscribing(pusherChannelName)) { return; } diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 1424a0a0e6ed..1b72e503f4f4 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -1,6 +1,5 @@ import moment from 'moment'; import Onyx from 'react-native-onyx'; -import AsyncStorage from '@react-native-community/async-storage'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as Pusher from '../../src/libs/Pusher/pusher'; import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; @@ -8,13 +7,6 @@ import CONFIG from '../../src/CONFIG'; import {subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; -// This step just sets up Onyx for testing. -Onyx.registerLogger(() => {}); -Onyx.init({ - keys: ONYXKEYS, - registerStorageEventListener: () => {}, -}); - describe('actions/Report', () => { it('should subscribe to the private-user-accountID channel and handle reportComment events', () => { const REPORT_ID = 1; @@ -33,6 +25,12 @@ describe('actions/Report', () => { loading: false, }; + // When using the Pusher mock the act of calling Pusher.isSubscribed will create a + // channel already in a subscribed state. These methods are normally used to prevent + // duplicated subscriptions, but we don't need them for this test. + Pusher.isSubscribed = jest.fn().mockReturnValue(false); + Pusher.isAlreadySubscribing = jest.fn().mockReturnValue(false); + // Connect to Pusher PusherConnectionManager.init(); Pusher.init({ @@ -41,6 +39,12 @@ describe('actions/Report', () => { authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Push_Authenticate`, }); + let reportActions; + Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, + callback: val => reportActions = val, + }); + // Set up Onyx with some test user data Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); return waitForPromisesToResolve() @@ -49,13 +53,9 @@ describe('actions/Report', () => { return waitForPromisesToResolve(); }) .then(() => { - const PRIVATE_USER_CHANNEL = 'private-user-accountID-1'; - const isSubscribedToPrivateChannel = Pusher.isSubscribed(PRIVATE_USER_CHANNEL); - expect(isSubscribedToPrivateChannel).toBe(true); - // Now that we are subscribed we need to simulate a reportComment action Pusher event. // Then verify that action was handled correctly and merged into the reportActions. - const channel = Pusher.getChannel(PRIVATE_USER_CHANNEL); + const channel = Pusher.getChannel('private-user-accountID-1'); channel.emit('reportComment', { reportID: REPORT_ID, reportAction: REPORT_ACTION, @@ -65,15 +65,8 @@ describe('actions/Report', () => { // for promises to resolve again/ return waitForPromisesToResolve(); }) - .then(() => ( - - // The only thing left to do is check the report actions and verify the comment has been merged into - // the store. Onyx does not have a getter method so we will ask AsyncStorage directly. - AsyncStorage.getItem(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`) - )) - .then((reportActionData) => { - const data = JSON.parse(reportActionData); - const resultAction = data[ACTION_ID]; + .then(() => { + const resultAction = reportActions[ACTION_ID]; expect(resultAction).toEqual(REPORT_ACTION); }); }); From c6a235a7df2d3bd7f241cfd9ea915863ebc860bc Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jan 2021 15:04:44 -1000 Subject: [PATCH 15/24] remove CONFIG dependency --- src/libs/actions/Report.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 3daa2fbb84bf..f02b56df3a29 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -14,7 +14,6 @@ import Visibility from '../Visibility'; import ROUTES from '../../ROUTES'; import NetworkConnection from '../NetworkConnection'; import {hide as hideSidebar} from './Sidebar'; -import CONFIG from '../../CONFIG'; import Timing from './Timing'; import * as API from '../API'; import CONST from '../../CONST'; From 8e55d77c6c4adf5199ab1f1073a762ccdd79399e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jan 2021 15:08:33 -1000 Subject: [PATCH 16/24] fix comment --- tests/actions/ReportTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 1b72e503f4f4..fc11fbcf1fc3 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -61,8 +61,8 @@ describe('actions/Report', () => { reportAction: REPORT_ACTION, }); - // Once this happens we should see the comment get processed by the callback so we must wait for - // for promises to resolve again/ + // Once this happens we should see the comment get processed by the callback and added to the + // storage so we must wait for promises to resolve again and then verify the data is in Onyx. return waitForPromisesToResolve(); }) .then(() => { From cd18cdb3f60a3610eb05c847b05d209dcc8e6f79 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 11 Jan 2021 17:32:21 -1000 Subject: [PATCH 17/24] Fix up comment. Use Onyx.set instead of waitForPromisesToResolve() --- tests/actions/ReportTest.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index fc11fbcf1fc3..8475f9e42142 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -8,7 +8,7 @@ import {subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; describe('actions/Report', () => { - it('should subscribe to the private-user-accountID channel and handle reportComment events', () => { + it('should store a new report action in Onyx when one is handled via Pusher', () => { const REPORT_ID = 1; const ACTION_ID = 1; const REPORT_ACTION = { @@ -46,8 +46,7 @@ describe('actions/Report', () => { }); // Set up Onyx with some test user data - Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}); - return waitForPromisesToResolve() + return Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}) .then(() => { subscribeToReportCommentEvents(); return waitForPromisesToResolve(); From 2ae40c9ac7e638495a8f39e0b03ea71b91357e71 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 11 Jan 2021 20:19:25 -1000 Subject: [PATCH 18/24] Improve tests. Add TestHelper. --- package-lock.json | 4 +- package.json | 2 +- src/libs/actions/Report.js | 2 + tests/actions/ReportTest.js | 44 ++++++++++++++++--- tests/actions/SessionTest.js | 43 ++----------------- tests/utils/TestHelper.js | 82 ++++++++++++++++++++++++++++++++++++ 6 files changed, 127 insertions(+), 50 deletions(-) create mode 100644 tests/utils/TestHelper.js diff --git a/package-lock.json b/package-lock.json index 7c31d1188489..f0247a2aa55d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21038,8 +21038,8 @@ } }, "react-native-onyx": { - "version": "git+https://github.com/Expensify/react-native-onyx.git#783806e7a3d82d78025bb9025767f42c3964eff9", - "from": "git+https://github.com/Expensify/react-native-onyx.git#783806e7a3d82d78025bb9025767f42c3964eff9", + "version": "git+https://github.com/Expensify/react-native-onyx.git#c769776165baa6ab8b57b122f49a12225a7bd19a", + "from": "git+https://github.com/Expensify/react-native-onyx.git#c769776165baa6ab8b57b122f49a12225a7bd19a", "requires": { "@react-native-community/async-storage": "^1.12.1", "expensify-common": "git+https://github.com/Expensify/expensify-common.git#cd9f195ed1fd340e7e890c41672a97af4f2956ca", diff --git a/package.json b/package.json index 908e381ce527..98323e4076af 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "react-native-image-picker": "^2.3.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.5.6", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#783806e7a3d82d78025bb9025767f42c3964eff9", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#c769776165baa6ab8b57b122f49a12225a7bd19a", "react-native-pdf": "^6.2.2", "react-native-render-html": "^6.0.0-alpha.10", "react-native-safe-area-context": "^3.1.4", diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f02b56df3a29..7fc2e9a8afdf 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -534,6 +534,7 @@ function addAction(reportID, text, file) { [newSequenceNumber]: { actionName: 'ADDCOMMENT', actorEmail: currentUserEmail, + actorAccountID: currentUserAccountID, person: [ { style: 'strong', @@ -558,6 +559,7 @@ function addAction(reportID, text, file) { isFirstItem: false, isAttachment, loading: true, + shouldShow: true, }, }); diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 8475f9e42142..b3534211a4a2 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -4,25 +4,34 @@ import ONYXKEYS from '../../src/ONYXKEYS'; import * as Pusher from '../../src/libs/Pusher/pusher'; import PusherConnectionManager from '../../src/libs/PusherConnectionManager'; import CONFIG from '../../src/CONFIG'; -import {subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; +import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import PushNotification from '../../src/libs/Notification/PushNotification'; +import {signInWithTestUser, fetchPersonalDetailsForTestUser} from '../utils/TestHelper'; + +PushNotification.register = () => {}; +PushNotification.deregister = () => {}; describe('actions/Report', () => { it('should store a new report action in Onyx when one is handled via Pusher', () => { + const TEST_USER_ACCOUNT_ID = 1; + const TEST_USER_LOGIN = 'test@test.com'; const REPORT_ID = 1; const ACTION_ID = 1; const REPORT_ACTION = { actionName: 'ADDCOMMENT', + actorAccountID: TEST_USER_ACCOUNT_ID, + actorEmail: TEST_USER_LOGIN, automatic: false, - created: 'Nov 6 2020 9:14am PST', + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + isAttachment: false, + isFirstItem: false, + loading: true, message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + sequenceNumber: ACTION_ID, shouldShow: true, timestamp: moment().unix(), - actorAccountID: 1, - sequenceNumber: ACTION_ID, - isAttachment: false, - loading: false, }; // When using the Pusher mock the act of calling Pusher.isSubscribed will create a @@ -46,11 +55,29 @@ describe('actions/Report', () => { }); // Set up Onyx with some test user data - return Onyx.set(ONYXKEYS.SESSION, {accountID: 1, email: 'test@test.com'}) + return signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { subscribeToReportCommentEvents(); return waitForPromisesToResolve(); }) + .then(() => fetchPersonalDetailsForTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, { + [TEST_USER_LOGIN]: { + accountID: TEST_USER_ACCOUNT_ID, + email: TEST_USER_LOGIN, + firstName: 'Test', + lastName: 'User', + }, + })) + .then(() => { + // This is a fire and forget response, but one it completes we should be able to verify that we + // have an "optimistic" report action in Onyx. + addAction(REPORT_ID, 'Testing a comment'); + return waitForPromisesToResolve(); + }) + .then(() => { + const resultAction = reportActions[ACTION_ID]; + expect(resultAction).toEqual(REPORT_ACTION); + }) .then(() => { // Now that we are subscribed we need to simulate a reportComment action Pusher event. // Then verify that action was handled correctly and merged into the reportActions. @@ -66,6 +93,9 @@ describe('actions/Report', () => { }) .then(() => { const resultAction = reportActions[ACTION_ID]; + + // Verify that our action is no longer in the loading state + REPORT_ACTION.loading = false; expect(resultAction).toEqual(REPORT_ACTION); }); }); diff --git a/tests/actions/SessionTest.js b/tests/actions/SessionTest.js index 53341eadba1e..a79605e52962 100644 --- a/tests/actions/SessionTest.js +++ b/tests/actions/SessionTest.js @@ -1,9 +1,10 @@ import Onyx from 'react-native-onyx'; -import {fetchAccountDetails, signIn} from '../../src/libs/actions/Session'; +import {fetchAccountDetails} from '../../src/libs/actions/Session'; import * as API from '../../src/libs/API'; import HttpUtils from '../../src/libs/HttpUtils'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import ONYXKEYS from '../../src/ONYXKEYS'; +import {signInWithTestUser} from '../utils/TestHelper'; // Set up manual mocks for methods used in the actions so our test does not fail. jest.mock('../../src/libs/Notification/PushNotification', () => ({ @@ -22,15 +23,6 @@ test('Authenticate is called with saved credentials when a session expires', () const TEST_INITIAL_AUTH_TOKEN = 'initialAuthToken'; const TEST_REFRESHED_AUTH_TOKEN = 'refreshedAuthToken'; - // Set up mock responses for all APIs that will be called. The next time this command is called it will return - // jsonCode: 200 and the response here. - HttpUtils.xhr.mockImplementation(() => Promise.resolve({ - jsonCode: 200, - accountExists: true, - canAccessExpensifyCash: true, - requiresTwoFactorAuth: false, - })); - let credentials; Onyx.connect({ key: ONYXKEYS.CREDENTIALS, @@ -43,36 +35,7 @@ test('Authenticate is called with saved credentials when a session expires', () callback: val => session = val, }); - // Simulate user entering their login and populating the credentials.login - fetchAccountDetails(TEST_USER_LOGIN); - - // Note: In order for this test to work we must return a promise! It will pass even with - // failing assertions if we remove the return keyword. - return waitForPromisesToResolve() - .then(() => { - // Next we will simulate signing in and make sure all API calls in this flow succeed. Every time we add - // a mockImplementationOnce() we are altering what Network.post() will return. - HttpUtils.xhr - - // First call to Authenticate - .mockImplementationOnce(() => Promise.resolve({ - jsonCode: 200, - accountID: TEST_USER_ACCOUNT_ID, - authToken: TEST_INITIAL_AUTH_TOKEN, - email: TEST_USER_LOGIN, - })) - - // Next call to CreateLogin - .mockImplementationOnce(() => Promise.resolve({ - jsonCode: 200, - accountID: TEST_USER_ACCOUNT_ID, - authToken: TEST_INITIAL_AUTH_TOKEN, - email: TEST_USER_LOGIN, - })); - - signIn('Password1'); - return waitForPromisesToResolve(); - }) + return signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, 'Password1', TEST_INITIAL_AUTH_TOKEN) .then(() => { // Verify that our credentials were saved and that our session data is correct expect(credentials.login).toBe(TEST_USER_LOGIN); diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js new file mode 100644 index 000000000000..03fcf6757163 --- /dev/null +++ b/tests/utils/TestHelper.js @@ -0,0 +1,82 @@ +import {signIn, fetchAccountDetails} from '../../src/libs/actions/Session'; +import {fetch as fetchPersonalDetails} from '../../src/libs/actions/PersonalDetails'; +import HttpUtils from '../../src/libs/HttpUtils'; +import waitForPromisesToResolve from './waitForPromisesToResolve'; + +/** + * Simulate signing in and make sure all API calls in this flow succeed. Every time we add + * a mockImplementationOnce() we are altering what Network.post() will return. + * + * @param {Number} accountID + * @param {String} login + * @param {String} password + * @param {String} authToken + * @return {Promise} + */ +function signInWithTestUser(accountID, login, password = 'Password1', authToken = 'asdfqwerty') { + HttpUtils.xhr = jest.fn(); + HttpUtils.xhr.mockImplementation(() => Promise.resolve({ + jsonCode: 200, + accountExists: true, + canAccessExpensifyCash: true, + requiresTwoFactorAuth: false, + })); + + // Simulate user entering their login and populating the credentials.login + fetchAccountDetails(login); + return waitForPromisesToResolve() + .then(() => { + // First call to Authenticate + HttpUtils.xhr + .mockImplementationOnce(() => Promise.resolve({ + jsonCode: 200, + accountID, + authToken, + email: login, + })) + + // Next call to CreateLogin + .mockImplementationOnce(() => Promise.resolve({ + jsonCode: 200, + accountID, + authToken, + email: login, + })); + signIn(password); + return waitForPromisesToResolve(); + }); +} + +/** + * Fetch and set personal details with provided personalDetailsList + * + * @param {Number} accountID + * @param {String} email + * @param {Object} personalDetailsList + * @returns {Promise} + */ +function fetchPersonalDetailsForTestUser(accountID, email, personalDetailsList) { + // Mock xhr() + HttpUtils.xhr = jest.fn(); + + // Get the personalDetails + HttpUtils.xhr + + // fetchPersonalDetails + .mockImplementationOnce(() => Promise.resolve({ + accountID, + email, + personalDetailsList, + })) + + // fetchTimezone + .mockImplementationOnce(() => Promise.resolve({})); + + fetchPersonalDetails(); + return waitForPromisesToResolve(); +} + +export { + signInWithTestUser, + fetchPersonalDetailsForTestUser, +}; From fb69894c26992ab5e043764af6c8ef9e4970c0a8 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 11 Jan 2021 20:22:16 -1000 Subject: [PATCH 19/24] fix lint --- tests/actions/SessionTest.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/actions/SessionTest.js b/tests/actions/SessionTest.js index a79605e52962..ebb49ab110cb 100644 --- a/tests/actions/SessionTest.js +++ b/tests/actions/SessionTest.js @@ -1,5 +1,4 @@ import Onyx from 'react-native-onyx'; -import {fetchAccountDetails} from '../../src/libs/actions/Session'; import * as API from '../../src/libs/API'; import HttpUtils from '../../src/libs/HttpUtils'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; From 84bbfb6d7f28eebddf5363fe3871493598d89ad8 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 12 Jan 2021 10:15:28 -1000 Subject: [PATCH 20/24] Respond to Tims feedback. --- tests/actions/ReportTest.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index b3534211a4a2..4ff912c5d999 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -36,7 +36,8 @@ describe('actions/Report', () => { // When using the Pusher mock the act of calling Pusher.isSubscribed will create a // channel already in a subscribed state. These methods are normally used to prevent - // duplicated subscriptions, but we don't need them for this test. + // duplicated subscriptions, but we don't need them for this test so forcing them to + // return false will make the testing less complex. Pusher.isSubscribed = jest.fn().mockReturnValue(false); Pusher.isAlreadySubscribing = jest.fn().mockReturnValue(false); @@ -69,7 +70,7 @@ describe('actions/Report', () => { }, })) .then(() => { - // This is a fire and forget response, but one it completes we should be able to verify that we + // This is a fire and forget response, but once it completes we should be able to verify that we // have an "optimistic" report action in Onyx. addAction(REPORT_ID, 'Testing a comment'); return waitForPromisesToResolve(); @@ -77,26 +78,27 @@ describe('actions/Report', () => { .then(() => { const resultAction = reportActions[ACTION_ID]; expect(resultAction).toEqual(REPORT_ACTION); + expect(resultAction.loading).toEqual(true); }) .then(() => { - // Now that we are subscribed we need to simulate a reportComment action Pusher event. - // Then verify that action was handled correctly and merged into the reportActions. + // We subscribed to the Pusher channel above and now we need to simulate a reportComment action + // Pusher event so we can verify that action was handled correctly and merged into the reportActions. const channel = Pusher.getChannel('private-user-accountID-1'); channel.emit('reportComment', { reportID: REPORT_ID, reportAction: REPORT_ACTION, }); - // Once this happens we should see the comment get processed by the callback and added to the - // storage so we must wait for promises to resolve again and then verify the data is in Onyx. + // Once a reportComment event is emitted to the Pusher channel we should see the comment get processed + // by the Pusher callback and added to the storage so we must wait for promises to resolve again and + // then verify the data is in Onyx. return waitForPromisesToResolve(); }) .then(() => { const resultAction = reportActions[ACTION_ID]; // Verify that our action is no longer in the loading state - REPORT_ACTION.loading = false; - expect(resultAction).toEqual(REPORT_ACTION); + expect(resultAction.loading).toEqual(false); }); }); }); From 422ae85abeda8f7a7c8d8d4b7362817fa096fd51 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 13 Jan 2021 12:01:14 -1000 Subject: [PATCH 21/24] update react-native-onyx --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0247a2aa55d..1004ad78011a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21038,8 +21038,8 @@ } }, "react-native-onyx": { - "version": "git+https://github.com/Expensify/react-native-onyx.git#c769776165baa6ab8b57b122f49a12225a7bd19a", - "from": "git+https://github.com/Expensify/react-native-onyx.git#c769776165baa6ab8b57b122f49a12225a7bd19a", + "version": "git+https://github.com/Expensify/react-native-onyx.git#bbed584f0e9f9ce128361c7138220738d8525a6a", + "from": "git+https://github.com/Expensify/react-native-onyx.git#bbed584f0e9f9ce128361c7138220738d8525a6a", "requires": { "@react-native-community/async-storage": "^1.12.1", "expensify-common": "git+https://github.com/Expensify/expensify-common.git#cd9f195ed1fd340e7e890c41672a97af4f2956ca", diff --git a/package.json b/package.json index 98323e4076af..396c925ddb10 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "react-native-image-picker": "^2.3.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.5.6", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#c769776165baa6ab8b57b122f49a12225a7bd19a", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#bbed584f0e9f9ce128361c7138220738d8525a6a", "react-native-pdf": "^6.2.2", "react-native-render-html": "^6.0.0-alpha.10", "react-native-safe-area-context": "^3.1.4", From 739bbb76cbba15dab74b3ee7a98bdd5613004269 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 13 Jan 2021 12:57:20 -1000 Subject: [PATCH 22/24] dont test strict equal since the timestamp will make the test flaky --- tests/actions/ReportTest.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 4ff912c5d999..0b6efc436957 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -24,14 +24,10 @@ describe('actions/Report', () => { actorEmail: TEST_USER_LOGIN, automatic: false, avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', - isAttachment: false, - isFirstItem: false, - loading: true, message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], sequenceNumber: ACTION_ID, shouldShow: true, - timestamp: moment().unix(), }; // When using the Pusher mock the act of calling Pusher.isSubscribed will create a @@ -77,7 +73,8 @@ describe('actions/Report', () => { }) .then(() => { const resultAction = reportActions[ACTION_ID]; - expect(resultAction).toEqual(REPORT_ACTION); + expect(resultAction.message).toEqual(REPORT_ACTION.message); + expect(resultAction.person).toEqual(REPORT_ACTION.person); expect(resultAction.loading).toEqual(true); }) .then(() => { From fefa0ef8d5c3d39a88a366f9abeac4ba35f97971 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 13 Jan 2021 12:57:36 -1000 Subject: [PATCH 23/24] remove momnet --- tests/actions/ReportTest.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 0b6efc436957..fbf7efe341e1 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -1,4 +1,3 @@ -import moment from 'moment'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as Pusher from '../../src/libs/Pusher/pusher'; From acb4b80d8066da42caae37a4d9aa23cc340ca65d Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 15 Jan 2021 15:00:08 -1000 Subject: [PATCH 24/24] add comment --- tests/actions/SessionTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/actions/SessionTest.js b/tests/actions/SessionTest.js index 2661fb48b898..9f6d6ee5ad31 100644 --- a/tests/actions/SessionTest.js +++ b/tests/actions/SessionTest.js @@ -35,6 +35,7 @@ test('Authenticate is called with saved credentials when a session expires', () callback: val => session = val, }); + // When we sign in with the test user return signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, 'Password1', TEST_INITIAL_AUTH_TOKEN) .then(() => { // Then our re-authentication credentials should be generated and our session data