Skip to content

Commit

Permalink
Merge pull request #14375 from Expensify/arosiclair-unread-test-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
roryabraham authored Jan 21, 2023
2 parents f9fe42b + 13e5520 commit 28aab08
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 80 deletions.
37 changes: 20 additions & 17 deletions src/libs/ReportActionsUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,27 @@ function getSortedReportActions(reportActions, shouldSortInDescendingOrder = fal
if (!_.isArray(reportActions)) {
throw new Error(`ReportActionsUtils.getSortedReportActions requires an array, received ${typeof reportActions}`);
}
const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1;
reportActions.sort((first, second) => {
// First sort by timestamp
if (first.created !== second.created) {
return (first.created < second.created ? -1 : 1) * invertedMultiplier;
}

// Then by action type, ensuring that `CREATED` actions always come first if they have the same timestamp as another action type
if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) {
return ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ? -1 : 1) * invertedMultiplier;
}

// Then fallback on reportActionID as the final sorting criteria. It is a random number,
// but using this will ensure that the order of reportActions with the same created time and action type
// will be consistent across all users and devices
return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier;
});
return reportActions;
const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1;
return _.chain(reportActions)
.compact()
.sort((first, second) => {
// First sort by timestamp
if (first.created !== second.created) {
return (first.created < second.created ? -1 : 1) * invertedMultiplier;
}

// Then by action type, ensuring that `CREATED` actions always come first if they have the same timestamp as another action type
if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) {
return ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ? -1 : 1) * invertedMultiplier;
}

// Then fallback on reportActionID as the final sorting criteria. It is a random number,
// but using this will ensure that the order of reportActions with the same created time and action type
// will be consistent across all users and devices
return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier;
})
.value();
}

/**
Expand Down
53 changes: 30 additions & 23 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,27 @@ function unsubscribeFromReportChannel(reportID) {
Pusher.unsubscribe(pusherChannelName);
}

const defaultNewActionSubscriber = {
reportID: '',
callback: () => {},
};

let newActionSubscriber = defaultNewActionSubscriber;

/**
* Enables the Report actions file to let the ReportActionsView know that a new comment has arrived in realtime for the current report
*
* @param {String} reportID
* @param {Function} callback
* @returns {Function}
*/
function subscribeToNewActionEvent(reportID, callback) {
newActionSubscriber = {callback, reportID};
return () => {
newActionSubscriber = defaultNewActionSubscriber;
};
}

/**
* Add up to two report actions to a report. This method can be called for the following situations:
*
Expand Down Expand Up @@ -265,6 +286,12 @@ function addActions(reportID, text = '', file) {
API.write(commandName, parameters, {
optimisticData,
});

// Notify the ReportActionsView that a new comment has arrived
if (reportID === newActionSubscriber.reportID) {
const isFromCurrentUser = lastAction.actorAccountID === currentUserAccountID;
newActionSubscriber.callback(isFromCurrentUser, lastAction.reportActionID);
}
}

/**
Expand Down Expand Up @@ -1065,33 +1092,13 @@ function setIsComposerFullSize(reportID, isComposerFullSize) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${reportID}`, isComposerFullSize);
}

const defaultNewActionSubscriber = {
reportID: '',
callback: () => {},
};

let newActionSubscriber = defaultNewActionSubscriber;

/**
* Enables the Report actions file to let the ReportActionsView know that a new comment has arrived in realtime for the current report
*
* @param {String} reportID
* @param {Function} callback
* @returns {Function}
*/
function subscribeToNewActionEvent(reportID, callback) {
newActionSubscriber = {callback, reportID};
return () => {
newActionSubscriber = defaultNewActionSubscriber;
};
}

/**
* @param {String} reportID
* @param {Object} action
*/
function showReportActionNotification(reportID, action) {
if (ReportActionsUtils.isDeletedAction(action)) {
Log.info('[LOCAL_NOTIFICATION] Skipping notification because the action was deleted', false, {reportID, action});
return;
}

Expand All @@ -1115,7 +1122,7 @@ function showReportActionNotification(reportID, action) {

// If we are currently viewing this report do not show a notification.
if (reportID === Navigation.getReportIDFromRoute() && Visibility.isVisible()) {
Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report');
Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report', false, {currentReport: Navigation.getReportIDFromRoute(), reportID, action});
return;
}

Expand All @@ -1126,7 +1133,7 @@ function showReportActionNotification(reportID, action) {

// Don't show a notification if no comment exists
if (!_.some(action.message, f => f.type === 'COMMENT')) {
Log.info('[LOCAL_NOTIFICATION] No notification because no comments exist for the current report');
Log.info('[LOCAL_NOTIFICATION] No notification because no comments exist for the current action');
return;
}

Expand Down
10 changes: 4 additions & 6 deletions src/libs/actions/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as Link from './Link';
import * as SequentialQueue from '../Network/SequentialQueue';
import PusherUtils from '../PusherUtils';
import * as Report from './Report';
import * as ReportActionsUtils from '../ReportActionsUtils';

let currentUserAccountID = '';
Onyx.connect({
Expand Down Expand Up @@ -264,12 +265,9 @@ function triggerNotifications(onyxUpdates) {
}

const reportID = update.key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, '');
const reportAction = _.chain(update.value)
.values()
.compact()
.first()
.value();
Report.showReportActionNotification(reportID, reportAction);
const reportActions = _.values(update.value);
const sortedReportActions = ReportActionsUtils.getSortedReportActions(reportActions);
Report.showReportActionNotification(reportID, _.last(sortedReportActions));
});
}

Expand Down
104 changes: 70 additions & 34 deletions tests/ui/UnreadIndicatorsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import LocalNotification from '../../src/libs/Notification/LocalNotification';
import * as Report from '../../src/libs/actions/Report';
import * as CollectionUtils from '../../src/libs/CollectionUtils';
import DateUtils from '../../src/libs/DateUtils';
import * as User from '../../src/libs/actions/User';
import * as Pusher from '../../src/libs/Pusher/pusher';
import PusherConnectionManager from '../../src/libs/PusherConnectionManager';
import CONFIG from '../../src/CONFIG';

jest.mock('../../src/libs/Notification/LocalNotification');

Expand All @@ -33,6 +37,14 @@ beforeAll(() => {
jest.setTimeout(30000);
Linking.setInitialURL('https://new.expensify.com/r/1');
appSetup();

// Connect to Pusher
PusherConnectionManager.init();
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
});
});

/**
Expand Down Expand Up @@ -127,6 +139,10 @@ function signInAndGetAppWithUnreadChat() {

return TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A');
})
.then(() => {
User.subscribeToUserEvents();
return waitForPromisesToResolve();
})
.then(() => {
const MOMENT_TEN_MINUTES_AGO = moment().subtract(10, 'minutes');
reportAction3CreatedDate = MOMENT_TEN_MINUTES_AGO.clone().add(30, 'seconds').format(MOMENT_FORMAT);
Expand Down Expand Up @@ -184,8 +200,11 @@ function signInAndGetAppWithUnreadChat() {
.then(() => renderedApp);
}

describe.skip('Unread Indicators', () => {
afterEach(() => Onyx.clear());
describe('Unread Indicators', () => {
afterEach(() => {
jest.clearAllMocks();
Onyx.clear();
});

it('Display bold in the LHN for unread chat and new line indicator above the chat message when we navigate to it', () => {
let renderedApp;
Expand Down Expand Up @@ -283,43 +302,60 @@ describe.skip('Unread Indicators', () => {
const NEW_REPORT_ID = '2';
const NEW_REPORT_CREATED_MOMENT = moment().subtract(5, 'seconds');
const NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT = NEW_REPORT_CREATED_MOMENT.add(1, 'seconds');
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${NEW_REPORT_ID}`, {
reportID: NEW_REPORT_ID,
reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
maxSequenceNumber: 1,
lastReadSequenceNumber: 0,
lastReadTime: '',
lastActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()),
lastMessageText: 'Comment 1',
participants: [USER_C_EMAIL],
});
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${NEW_REPORT_ID}`, {
0: {
actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
automatic: false,
sequenceNumber: 0,
created: NEW_REPORT_CREATED_MOMENT.format(MOMENT_FORMAT),
reportActionID: NumberUtils.rand64(),

const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${USER_A_ACCOUNT_ID}${CONFIG.PUSHER.SUFFIX}`);
channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${NEW_REPORT_ID}`,
value: {
reportID: NEW_REPORT_ID,
reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
maxSequenceNumber: 1,
lastReadSequenceNumber: 0,
lastReadTime: '',
lastActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()),
lastMessageText: 'Comment 1',
participants: [USER_C_EMAIL],
},
},
1: {
actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
actorEmail: USER_C_EMAIL,
actorAccountID: USER_C_ACCOUNT_ID,
person: [{type: 'TEXT', style: 'strong', text: 'User C'}],
sequenceNumber: 1,
created: NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.format(MOMENT_FORMAT),
message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}],
reportActionID: NumberUtils.rand64(),
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${NEW_REPORT_ID}`,
value: {
0: {
actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
automatic: false,
sequenceNumber: 0,
created: NEW_REPORT_CREATED_MOMENT.format(MOMENT_FORMAT),
reportActionID: NumberUtils.rand64(),
},
1: {
actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
actorEmail: USER_C_EMAIL,
actorAccountID: USER_C_ACCOUNT_ID,
person: [{type: 'TEXT', style: 'strong', text: 'User C'}],
sequenceNumber: 1,
created: NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.format(MOMENT_FORMAT),
message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}],
reportActionID: NumberUtils.rand64(),
},
},
shouldNotify: true,
},
});
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {
[USER_C_EMAIL]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'),
});
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[USER_C_EMAIL]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'),
},
},
]);
return waitForPromisesToResolve();
})
.then(() => {
// Verify notification was created as the new message that has arrived is very recent
expect(LocalNotification.showCommentNotification.mock.calls).toHaveLength(1);
// Verify notification was created
expect(LocalNotification.showCommentNotification).toBeCalled();

// // Navigate back to the sidebar
return navigateToSidebar(renderedApp);
Expand Down

0 comments on commit 28aab08

Please sign in to comment.