Skip to content

Commit

Permalink
Merge pull request #33285 from callstack-internal/hur/fix-unit-test-f…
Browse files Browse the repository at this point in the history
…or-transition-end

test: add support for transitionEnd event in unit tests
  • Loading branch information
thienlnam authored Dec 21, 2023
2 parents 9d71a22 + 2497dc5 commit 0a6c4e7
Showing 1 changed file with 76 additions and 13 deletions.
89 changes: 76 additions & 13 deletions tests/ui/UnreadIndicatorsTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {fireEvent, render, screen, waitFor} from '@testing-library/react-native';
import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native';
import {addSeconds, format, subMinutes, subSeconds} from 'date-fns';
import {utcToZonedTime} from 'date-fns-tz';
import lodashGet from 'lodash/get';
Expand Down Expand Up @@ -31,6 +31,61 @@ jest.setTimeout(30000);
jest.mock('../../src/libs/Notification/LocalNotification');
jest.mock('../../src/components/Icon/Expensicons');

/**
* We need to keep track of the transitionEnd callback so we can trigger it in our tests
*/
let transitionEndCB;

/**
* This is a helper function to create a mock for the addListener function of the react-navigation library.
* The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate
* the transitionEnd event that is triggered when the screen transition animation is completed.
*
* P.S: This can't be moved to a utils file because Jest wants any external function to stay in the scope.
*
* @returns {Object} An object with two functions: triggerTransitionEnd and addListener
*/
const createAddListenerMock = () => {
const transitionEndListeners = [];
const triggerTransitionEnd = () => {
transitionEndListeners.forEach((transitionEndListener) => transitionEndListener());
};

const addListener = jest.fn().mockImplementation((listener, callback) => {
if (listener === 'transitionEnd') {
transitionEndListeners.push(callback);
}
return () => {
// eslint-disable-next-line rulesdir/prefer-underscore-method
transitionEndListeners.filter((cb) => cb !== callback);
};
});

return {triggerTransitionEnd, addListener};
};

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
const {triggerTransitionEnd, addListener} = createAddListenerMock();
transitionEndCB = triggerTransitionEnd;
const useNavigation = () => ({
navigate: jest.fn(),
...actualNav.useNavigation,
getState: () => ({
routes: [],
}),
addListener,
});

return {
...actualNav,
useNavigation,
getState: () => ({
routes: [],
}),
};
});

beforeAll(() => {
// In this test, we are generically mocking the responses of all API requests by mocking fetch() and having it
// return 200. In other tests, we might mock HttpUtils.xhr() with a more specific mock data response (which means
Expand Down Expand Up @@ -95,11 +150,11 @@ function navigateToSidebar() {
* @param {Number} index
* @return {Promise}
*/
function navigateToSidebarOption(index) {
async function navigateToSidebarOption(index) {
const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
const optionRows = screen.queryAllByAccessibilityHint(hintText);
fireEvent(optionRows[index], 'press');
return waitForBatchedUpdates();
await waitForBatchedUpdatesWithAct();
}

/**
Expand Down Expand Up @@ -136,19 +191,22 @@ function signInAndGetAppWithUnreadChat() {
const loginForm = screen.queryAllByLabelText(hintText);
expect(loginForm).toHaveLength(1);

return TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A');
await act(async () => {
await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A');
});
return waitForBatchedUpdatesWithAct();
})
.then(() => {
User.subscribeToUserEvents();
return waitForBatchedUpdates();
})
.then(() => {
.then(async () => {
const TEN_MINUTES_AGO = subMinutes(new Date(), 10);
reportAction3CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 30), CONST.DATE.FNS_DB_FORMAT_STRING);
reportAction9CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 90), CONST.DATE.FNS_DB_FORMAT_STRING);

// Simulate setting an unread report and personal details
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
reportID: REPORT_ID,
reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
lastReadTime: reportAction3CreatedDate,
Expand All @@ -158,7 +216,7 @@ function signInAndGetAppWithUnreadChat() {
type: CONST.REPORT.TYPE.CHAT,
});
const createdReportActionID = NumberUtils.rand64();
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, {
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, {
[createdReportActionID]: {
actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
automatic: false,
Expand Down Expand Up @@ -187,13 +245,13 @@ function signInAndGetAppWithUnreadChat() {
8: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 80), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '8'),
9: TestHelper.buildTestReportComment(reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9'),
});
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'),
});

// We manually setting the sidebar as loaded since the onLayout event does not fire in tests
AppActions.setSidebarLoaded(true);
return waitForBatchedUpdates();
return waitForBatchedUpdatesWithAct();
});
}

Expand Down Expand Up @@ -226,7 +284,9 @@ describe('Unread Indicators', () => {

return navigateToSidebarOption(0);
})
.then(() => {
.then(async () => {
await act(() => transitionEndCB && transitionEndCB());

// That the report actions are visible along with the created action
const welcomeMessageHintText = Localize.translateLocal('accessibilityHints.chatWelcomeMessage');
const createdAction = screen.queryByLabelText(welcomeMessageHintText);
Expand All @@ -249,7 +309,8 @@ describe('Unread Indicators', () => {
signInAndGetAppWithUnreadChat()
// Navigate to the unread chat from the sidebar
.then(() => navigateToSidebarOption(0))
.then(() => {
.then(async () => {
await act(() => transitionEndCB && transitionEndCB());
// Verify the unread indicator is present
const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
Expand Down Expand Up @@ -373,7 +434,8 @@ describe('Unread Indicators', () => {
return navigateToSidebarOption(0);
})
.then(waitForBatchedUpdates)
.then(() => {
.then(async () => {
await act(() => transitionEndCB && transitionEndCB());
// Verify that report we navigated to appears in a "read" state while the original unread report still shows as unread
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNameTexts = screen.queryAllByLabelText(hintText);
Expand Down Expand Up @@ -449,7 +511,8 @@ describe('Unread Indicators', () => {
// Navigate to the report and verify the indicator is present
return navigateToSidebarOption(0);
})
.then(() => {
.then(async () => {
await act(() => transitionEndCB && transitionEndCB());
const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
Expand Down

0 comments on commit 0a6c4e7

Please sign in to comment.