Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add support for transitionEnd event in unit tests #33285

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading