diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index b3fbbbd03d8a..56e406ba0ca5 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -163,10 +163,14 @@ function getMicroseconds() { /** * Returns the current time in milliseconds in the format expected by the database + * + * @param {String|Number} [timestamp] + * * @returns {String} */ -function currentDBTime() { - return new Date().toISOString() +function getDBTime(timestamp = '') { + const datetime = timestamp ? new Date(timestamp) : new Date(); + return datetime.toISOString() .replace('T', ' ') .replace('Z', ''); } @@ -183,7 +187,7 @@ const DateUtils = { canUpdateTimezone, setTimezoneUpdated, getMicroseconds, - currentDBTime, + getDBTime, }; export default DateUtils; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index faac86e5f6f2..0ea6448bb802 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -470,7 +470,7 @@ function getOptions(reports, personalDetails, { return -Infinity; } - return report.lastMessageTimestamp; + return report.lastActionCreated; }); orderedReports.reverse(); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b0035909d1d4..9f7ffd1a082f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -643,7 +643,7 @@ function buildOptimisticReportAction(sequenceNumber, text, file) { sequenceNumber, clientID: NumberUtils.generateReportActionClientID(), avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), - created: DateUtils.currentDBTime(), + created: DateUtils.getDBTime(), message: [ { type: CONST.REPORT.MESSAGE.TYPE.COMMENT, @@ -795,7 +795,7 @@ function buildOptimisticIOUReportAction(sequenceNumber, type, amount, currency, reportActionID: NumberUtils.rand64(), sequenceNumber, shouldShow: true, - created: DateUtils.currentDBTime(), + created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }; } @@ -834,7 +834,7 @@ function buildOptimisticChatReport( lastMessageHtml: '', lastMessageText: null, lastReadSequenceNumber: 0, - lastMessageTimestamp: 0, + lastActionCreated: '', lastVisitedTimestamp: 0, maxSequenceNumber: 0, notificationPreference, @@ -883,7 +883,7 @@ function buildOptimisticCreatedReportAction(ownerEmail) { automatic: false, sequenceNumber: 0, avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), - created: DateUtils.currentDBTime(), + created: DateUtils.getDBTime(), shouldShow: true, }, }; diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 0a0f35531902..b0932f85b1e7 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -114,10 +114,10 @@ function getOrderedReportIDs(reportIDFromRoute) { // 2. Outstanding IOUs - Always sorted by iouReportAmount with the largest amounts at the top of the group // 3. Drafts - Always sorted by reportDisplayName // 4. Non-archived reports - // - Sorted by lastMessageTimestamp in default (most recent) view mode + // - Sorted by lastActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode // 5. Archived reports - // - Sorted by lastMessageTimestamp in default (most recent) view mode + // - Sorted by lastActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode let pinnedReports = []; let outstandingIOUReports = []; @@ -153,8 +153,8 @@ function getOrderedReportIDs(reportIDFromRoute) { pinnedReports = _.sortBy(pinnedReports, report => report.displayName.toLowerCase()); outstandingIOUReports = _.sortBy(outstandingIOUReports, 'iouReportAmount').reverse(); draftReports = _.sortBy(draftReports, report => report.displayName.toLowerCase()); - nonArchivedReports = _.sortBy(nonArchivedReports, report => (isInDefaultMode ? report.lastMessageTimestamp : report.displayName.toLowerCase())); - archivedReports = _.sortBy(archivedReports, report => (isInDefaultMode ? report.lastMessageTimestamp : report.displayName.toLowerCase())); + nonArchivedReports = _.sortBy(nonArchivedReports, report => (isInDefaultMode ? report.lastActionCreated : report.displayName.toLowerCase())); + archivedReports = _.sortBy(archivedReports, report => (isInDefaultMode ? report.lastActionCreated : report.displayName.toLowerCase())); // For archived and non-archived reports, ensure that most recent reports are at the top by reversing the order of the arrays because underscore will only sort them in ascending order if (isInDefaultMode) { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 164d0e60bbbb..d6c5220d2785 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -90,8 +90,7 @@ function getParticipantEmailsFromReport({sharedReportList, reportNameValuePairs, * @returns {Object} */ function getSimplifiedReportObject(report) { - const createTimestamp = lodashGet(report, 'lastActionCreated', 0); - const lastMessageTimestamp = moment.utc(createTimestamp).unix(); + const lastActionCreated = lodashGet(report, 'lastActionCreated', 0); const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], ''); const isLastMessageAttachment = new RegExp(`]*${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}\\s*=\\s*"[^"]*"[^>]*>`, 'gi').test(lastActionMessage); const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], ''); @@ -135,7 +134,7 @@ function getSimplifiedReportObject(report) { 'timestamp', ], 0), lastReadSequenceNumber, - lastMessageTimestamp, + lastActionCreated, lastMessageText: isLastMessageAttachment ? '[Attachment]' : lastMessageText, lastActorEmail, notificationPreference, @@ -414,7 +413,7 @@ function addActions(reportID, text = '', file) { // Update the report in Onyx to have the new sequence number const optimisticReport = { maxSequenceNumber: newSequenceNumber, - lastMessageTimestamp: Date.now(), + lastActionCreated: DateUtils.getDBTime(), lastMessageText: ReportUtils.formatReportLastMessageText(lastAction.message[0].text), lastActorEmail: currentUserEmail, lastReadSequenceNumber: newSequenceNumber, diff --git a/src/libs/migrations/AddLastActionCreated.js b/src/libs/migrations/AddLastActionCreated.js new file mode 100644 index 000000000000..38c6a715614b --- /dev/null +++ b/src/libs/migrations/AddLastActionCreated.js @@ -0,0 +1,46 @@ +import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import Log from '../Log'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * This migration adds lastActionCreated to all reports in Onyx, using the value of lastMessageTimestamp + * + * @returns {Promise} + */ +export default function () { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallbacks: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + const reportsToUpdate = {}; + _.each(allReports, (report, key) => { + if (_.has(report, 'lastActionCreated')) { + return; + } + + if (!_.has(report, 'lastMessageTimestamp')) { + return; + } + + reportsToUpdate[key] = report; + reportsToUpdate[key].lastActionCreated = new Date(report.lastMessageTimestamp) + .toISOString() + .replace('T', ' ') + .replace('Z', ''); + }); + + if (_.isEmpty(reportsToUpdate)) { + Log.info('[Migrate Onyx] Skipped migration AddLastActionCreated'); + } else { + Log.info(`[Migrate Onyx] Adding lastActionCreated field to ${_.keys(reportsToUpdate).length} reports`); + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate); + } + }, + }); + return resolve(); + }); +} diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index f2a8ed496220..c5bc3bc7a0a3 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -213,7 +213,7 @@ const reportSelector = report => report && ({ maxSequenceNumber: report.maxSequenceNumber, lastReadSequenceNumber: report.lastReadSequenceNumber, lastMessageText: report.lastMessageText, - lastMessageTimestamp: report.lastMessageTimestamp, + lastActionCreated: report.lastActionCreated, iouReportID: report.iouReportID, hasOutstandingIOU: report.hasOutstandingIOU, statusNum: report.statusNum, diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index 5c26bb2905a1..bc233b305676 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -32,7 +32,7 @@ export default PropTypes.shape({ lastMessageText: PropTypes.string, /** The time of the last message on the report */ - lastMessageTimestamp: PropTypes.number, + lastActionCreated: PropTypes.string, /** The sequence number of the last action read by the user */ lastReadSequenceNumber: PropTypes.number, diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index ee7d5f6320db..6cfba6aca324 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -111,7 +111,7 @@ describe('actions/Report', () => { reportID: REPORT_ID, maxSequenceNumber: 1, notificationPreference: 'always', - lastMessageTimestamp: 0, + lastActionCreated: '2022-11-22 03:48:27.267', lastMessageText: 'Testing a comment', lastActorEmail: TEST_USER_LOGIN, }, @@ -240,7 +240,7 @@ describe('actions/Report', () => { reportID: REPORT_ID, maxSequenceNumber: 1, notificationPreference: 'always', - lastMessageTimestamp: 0, + lastActionCreated: '2022-11-22 03:48:27.267', lastMessageText: 'Comment 1', lastActorEmail: USER_2_LOGIN, lastReadSequenceNumber: 0, @@ -260,7 +260,7 @@ describe('actions/Report', () => { person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], sequenceNumber: 1, shouldShow: true, - created: DateUtils.currentDBTime(), + created: DateUtils.getDBTime(), }, }, }, @@ -325,7 +325,7 @@ describe('actions/Report', () => { avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], shouldShow: true, - created: DateUtils.currentDBTime(), + created: DateUtils.getDBTime(), reportActionID: 'derp', }; @@ -338,7 +338,7 @@ describe('actions/Report', () => { reportID: REPORT_ID, maxSequenceNumber: 4, notificationPreference: 'always', - lastMessageTimestamp: 0, + lastActionCreated: '2022-11-22 03:48:27.267', lastMessageText: 'Current User Comment 3', lastActorEmail: 'test@test.com', lastReadSequenceNumber: 4, diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index 7796e66f27dd..81c954d6f3fd 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -17,6 +17,7 @@ import * as NumberUtils from '../../src/libs/NumberUtils'; 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'; jest.mock('../../src/libs/Notification/LocalNotification'); @@ -133,7 +134,7 @@ function signInAndGetAppWithUnreadChat() { reportName: CONST.REPORT.DEFAULT_REPORT_NAME, maxSequenceNumber: 9, lastReadSequenceNumber: 1, - lastMessageTimestamp: MOMENT_TEN_MINUTES_AGO.utc().valueOf(), + lastActionCreated: DateUtils.getDBTime(MOMENT_TEN_MINUTES_AGO.utc().valueOf()), lastMessageText: 'Test', participants: [USER_B_EMAIL], }); @@ -268,7 +269,7 @@ describe('Unread Indicators', () => { reportName: CONST.REPORT.DEFAULT_REPORT_NAME, maxSequenceNumber: 1, lastReadSequenceNumber: 0, - lastMessageTimestamp: NEW_REPORT_CREATED_MOMENT.utc().valueOf(), + lastActionCreated: DateUtils.getDBTime(NEW_REPORT_CREATED_MOMENT.utc().valueOf()), lastMessageText: 'Comment 1', participants: [USER_C_EMAIL], }); @@ -497,7 +498,7 @@ describe('Unread Indicators', () => { delete lastReportAction[lastReportAction.clientID]; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { lastMessageText: lastReportAction.message[0].text, - lastMessageTimestamp: lastReportAction.timestamp, + lastActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), lastActorEmail: lastReportAction.actorEmail, maxSequenceNumber: lastReportAction.sequenceNumber, reportID: REPORT_ID, diff --git a/tests/unit/DateUtilsTest.js b/tests/unit/DateUtilsTest.js index bd0b55b1fdc7..bae1b3df06f2 100644 --- a/tests/unit/DateUtilsTest.js +++ b/tests/unit/DateUtilsTest.js @@ -47,8 +47,28 @@ describe('DateUtils', () => { expect(DateUtils.datetimeToRelative(LOCALE, anHourAgo)).toBe('an hour ago'); }); - it('should return the date in the format expected by the database when calling currentDBTime', () => { - const currentDBTime = DateUtils.currentDBTime(); - expect(currentDBTime).toBe(moment(currentDBTime).format('YYYY-MM-DD HH:mm:ss.SSS')); + describe('getDBTime', () => { + it('should return the date in the format expected by the database', () => { + const getDBTime = DateUtils.getDBTime(); + expect(getDBTime).toBe(moment(getDBTime).format('YYYY-MM-DD HH:mm:ss.SSS')); + }); + + it('should represent the correct moment in utc when used with a standard datetime string', () => { + const timestamp = 'Mon Nov 21 2022 19:04:14 GMT-0800 (Pacific Standard Time)'; + const getDBTime = DateUtils.getDBTime(timestamp); + expect(getDBTime).toBe('2022-11-22 03:04:14.000'); + }); + + it('should represent the correct moment in time when used with an ISO string', () => { + const timestamp = '2022-11-22T03:08:04.326Z'; + const getDBTime = DateUtils.getDBTime(timestamp); + expect(getDBTime).toBe('2022-11-22 03:08:04.326'); + }); + + it('should represent the correct moment in time when used with a unix timestamp', () => { + const timestamp = 1669086850792; + const getDBTime = DateUtils.getDBTime(timestamp); + expect(getDBTime).toBe('2022-11-22 03:14:10.792'); + }); }); }); diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 0cd808350575..b2d4a81fcd80 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -1,7 +1,10 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; +import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import CONST from '../../src/CONST'; +import Log from '../../src/libs/Log'; import getPlatform from '../../src/libs/getPlatform'; +import AddLastActionCreated from '../../src/libs/migrations/AddLastActionCreated'; import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; import ONYXKEYS from '../../src/ONYXKEYS'; @@ -11,66 +14,140 @@ jest.mock('../../src/libs/getPlatform'); // This seems related: https://github.com/facebook/jest/issues/11876 jest.useRealTimers(); -describe('MoveToIndexedDb', () => { +let LogSpy; + +describe('Migrations', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + LogSpy = jest.spyOn(Log, 'info'); + return waitForPromisesToResolve(); + }); + beforeEach(() => { - jest.resetAllMocks(); - getPlatform.mockImplementation(() => CONST.PLATFORM.WEB); - jest.spyOn(Onyx, 'multiSet').mockImplementation(() => Promise.resolve()); - localStorage.clear(); + jest.clearAllMocks(); + Onyx.clear(); + return waitForPromisesToResolve(); }); - it('Should do nothing for non web/desktop platforms', () => { - // Given the migration is not running on web or desktop - getPlatform.mockImplementation(() => CONST.PLATFORM.ANDROID); + describe('MoveToIndexedDb', () => { + beforeEach(() => { + getPlatform.mockImplementation(() => CONST.PLATFORM.WEB); + jest.spyOn(Onyx, 'multiSet').mockImplementation(() => Promise.resolve()); + localStorage.clear(); + }); + it('Should do nothing for non web/desktop platforms', () => { + // Given the migration is not running on web or desktop + getPlatform.mockImplementation(() => CONST.PLATFORM.ANDROID); - // When the migration runs - return MoveToIndexedDB() - .then(() => { - // Then we don't expect any storage calls - expect(Onyx.multiSet).not.toHaveBeenCalled(); - }); - }); + // When the migration runs + return MoveToIndexedDB() + .then(() => { + // Then we don't expect any storage calls + expect(Onyx.multiSet).not.toHaveBeenCalled(); + }); + }); - it('Should do nothing when there is no old session data', () => { - // Given no session in old storage medium (localStorage) - localStorage.removeItem(ONYXKEYS.SESSION); + it('Should do nothing when there is no old session data', () => { + // Given no session in old storage medium (localStorage) + localStorage.removeItem(ONYXKEYS.SESSION); - // When the migration runs - return MoveToIndexedDB() - .then(() => { - // Then we don't expect any storage calls - expect(Onyx.multiSet).not.toHaveBeenCalled(); - }); - }); + // When the migration runs + return MoveToIndexedDB() + .then(() => { + // Then we don't expect any storage calls + expect(Onyx.multiSet).not.toHaveBeenCalled(); + }); + }); + + it('Should migrate Onyx keys in localStorage to (new) Onyx', () => { + // Given some old data exists in storage + const data = { + [ONYXKEYS.SESSION]: {authToken: 'mock-token', loading: false}, + [ONYXKEYS.ACCOUNT]: {email: 'test@mock.com'}, + [ONYXKEYS.NETWORK]: {isOffline: true}, + }; - it('Should migrate Onyx keys in localStorage to (new) Onyx', () => { - // Given some old data exists in storage - const data = { - [ONYXKEYS.SESSION]: {authToken: 'mock-token', loading: false}, - [ONYXKEYS.ACCOUNT]: {email: 'test@mock.com'}, - [ONYXKEYS.NETWORK]: {isOffline: true}, - }; - - _.forEach(data, (value, key) => localStorage.setItem(key, JSON.stringify(value))); - - // When the migration runs - return MoveToIndexedDB() - .then(() => { - // Then multiset should be called with all the data available in localStorage - expect(Onyx.multiSet).toHaveBeenCalledWith(data); - }); + _.forEach(data, (value, key) => localStorage.setItem(key, JSON.stringify(value))); + + // When the migration runs + return MoveToIndexedDB() + .then(() => { + // Then multiset should be called with all the data available in localStorage + expect(Onyx.multiSet).toHaveBeenCalledWith(data); + }); + }); + + it('Should not clear non Onyx keys from localStorage', () => { + // Given some Onyx and non-Onyx data exists in localStorage + localStorage.setItem(ONYXKEYS.SESSION, JSON.stringify({authToken: 'mock-token'})); + localStorage.setItem('non-onyx-item', 'MOCK'); + + // When the migration runs + return MoveToIndexedDB() + .then(() => { + // Then non-Onyx data should remain in localStorage + expect(localStorage.getItem('non-onyx-item')).toEqual('MOCK'); + }); + }); }); - it('Should not clear non Onyx keys from localStorage', () => { - // Given some Onyx and non-Onyx data exists in localStorage - localStorage.setItem(ONYXKEYS.SESSION, JSON.stringify({authToken: 'mock-token'})); - localStorage.setItem('non-onyx-item', 'MOCK'); - - // When the migration runs - return MoveToIndexedDB() - .then(() => { - // Then non-Onyx data should remain in localStorage - expect(localStorage.getItem('non-onyx-item')).toEqual('MOCK'); - }); + describe('AddLastActionCreated', () => { + it('Should add lastActionCreated wherever lastMessageTimestamp currently is', () => { + Onyx.set(ONYXKEYS.COLLECTION.REPORT, { + report_1: { + lastMessageTimestamp: 1668562273702, + }, + report_2: { + lastMessageTimestamp: 1668562314821, + }, + }) + .then(AddLastActionCreated) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Adding lastActionCreated field to 2 reports'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallbacks: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + expect(_.keys(allReports).length).toBe(2); + _.each(allReports, (report) => { + expect(_.has(report, 'lastActionCreated')).toBe(true); + }); + expect(allReports.report_1.lastActionCreated).toBe('2022-11-16 01:31:13.702'); + expect(allReports.report_2.lastActionCreated).toBe('2022-11-16 01:31:54.821'); + }, + }); + }); + }); + + it('Should skip if the report data already has the correct fields', () => { + Onyx.set(ONYXKEYS.COLLECTION.REPORT, { + report_1: { + lastActionCreated: '2022-11-16 01:31:13.702', + }, + report_2: { + lastActionCreated: '2022-11-16 01:31:54.821', + }, + }) + .then(AddLastActionCreated) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastActionCreated'); + }); + }); + + it('Should work even if there is no report data', () => { + AddLastActionCreated() + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastActionCreated'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallbacks: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); + expect(allReports).toBeEmpty(); + }, + }); + }); + }); }); }); diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index c9680467b81f..db6dffdc0f1a 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -12,7 +12,7 @@ describe('OptionsListUtils', () => { const REPORTS = { 1: { lastVisitedTimestamp: 1610666739295, - lastMessageTimestamp: 15, + lastActionCreated: '2022-11-22 03:26:02.015', isPinned: false, reportID: 1, participants: ['tonystark@expensify.com', 'reedrichards@expensify.com'], @@ -23,7 +23,7 @@ describe('OptionsListUtils', () => { }, 2: { lastVisitedTimestamp: 1610666739296, - lastMessageTimestamp: 16, + lastActionCreated: '2022-11-22 03:26:02.016', isPinned: false, reportID: 2, participants: ['peterparker@expensify.com'], @@ -35,7 +35,7 @@ describe('OptionsListUtils', () => { // This is the only report we are pinning in this test 3: { lastVisitedTimestamp: 1610666739297, - lastMessageTimestamp: 170, + lastActionCreated: '2022-11-22 03:26:02.170', isPinned: true, reportID: 3, participants: ['reedrichards@expensify.com'], @@ -45,7 +45,7 @@ describe('OptionsListUtils', () => { }, 4: { lastVisitedTimestamp: 1610666739298, - lastMessageTimestamp: 180, + lastActionCreated: '2022-11-22 03:26:02.180', isPinned: false, reportID: 4, participants: ['tchalla@expensify.com'], @@ -55,7 +55,7 @@ describe('OptionsListUtils', () => { }, 5: { lastVisitedTimestamp: 1610666739299, - lastMessageTimestamp: 19, + lastActionCreated: '2022-11-22 03:26:02.019', isPinned: false, reportID: 5, participants: ['suestorm@expensify.com'], @@ -65,7 +65,7 @@ describe('OptionsListUtils', () => { }, 6: { lastVisitedTimestamp: 1610666739300, - lastMessageTimestamp: 20, + lastActionCreated: '2022-11-22 03:26:02.020', isPinned: false, reportID: 6, participants: ['thor@expensify.com'], @@ -74,10 +74,10 @@ describe('OptionsListUtils', () => { maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, - // Note: This report has the largest lastMessageTimestamp + // Note: This report has the largest lastActionCreated 7: { lastVisitedTimestamp: 1610666739301, - lastMessageTimestamp: 1611282169, + lastActionCreated: '2022-11-22 03:26:03.999', isPinned: false, reportID: 7, participants: ['steverogers@expensify.com'], @@ -86,10 +86,10 @@ describe('OptionsListUtils', () => { maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, - // Note: This report has no lastMessageTimestamp + // Note: This report has no lastActionCreated 8: { lastVisitedTimestamp: 1610666739301, - lastMessageTimestamp: 0, + lastActionCreated: '2022-11-22 03:26:02.000', isPinned: false, reportID: 8, participants: ['galactus_herald@expensify.com'], @@ -101,7 +101,7 @@ describe('OptionsListUtils', () => { // Note: This report has an IOU 9: { lastVisitedTimestamp: 1610666739302, - lastMessageTimestamp: 1611282168, + lastActionCreated: '2022-11-22 03:26:02.998', isPinned: false, reportID: 9, participants: ['mistersinister@marauders.com'], @@ -115,7 +115,7 @@ describe('OptionsListUtils', () => { // This report is an archived room – it does not have a name and instead falls back on oldPolicyName 10: { lastVisitedTimestamp: 1610666739200, - lastMessageTimestamp: 1, + lastActionCreated: '2022-11-22 03:26:02.001', reportID: 10, isPinned: false, participants: ['tonystark@expensify.com', 'steverogers@expensify.com'], @@ -180,7 +180,7 @@ describe('OptionsListUtils', () => { 11: { lastVisitedTimestamp: 1610666739302, - lastMessageTimestamp: 22, + lastActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: 11, participants: ['concierge@expensify.com'], @@ -194,7 +194,7 @@ describe('OptionsListUtils', () => { ...REPORTS, 12: { lastVisitedTimestamp: 1610666739302, - lastMessageTimestamp: 22, + lastActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: 12, participants: ['chronos@expensify.com'], @@ -208,7 +208,7 @@ describe('OptionsListUtils', () => { ...REPORTS, 13: { lastVisitedTimestamp: 1610666739302, - lastMessageTimestamp: 22, + lastActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: 13, participants: ['receipts@expensify.com'], @@ -297,7 +297,7 @@ describe('OptionsListUtils', () => { // When we filter again but provide a searchValue that should match multiple times results = OptionsListUtils.getSearchOptions(REPORTS, PERSONAL_DETAILS, 'fantastic'); - // Value with latest lastMessageTimestamp should be at the top. + // Value with latest lastActionCreated should be at the top. expect(results.recentReports.length).toBe(2); expect(results.recentReports[0].text).toBe('Mister Fantastic'); expect(results.recentReports[1].text).toBe('Mister Fantastic'); @@ -370,7 +370,7 @@ describe('OptionsListUtils', () => { // Then several options will be returned and they will be each have the search string in their email or name // even though the currently logged in user matches they should not show. - // Should be ordered by lastMessageTimestamp values. + // Should be ordered by lastActionCreated values. expect(results.personalDetails.length).toBe(4); expect(results.recentReports.length).toBe(5); expect(results.personalDetails[0].login).toBe('natasharomanoff@expensify.com'); diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 769e3b8438b3..b9b5da13aca4 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -4,6 +4,7 @@ import lodashGet from 'lodash/get'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import CONST from '../../src/CONST'; +import DateUtils from '../../src/libs/DateUtils'; // Be sure to include the mocked permissions library or else the beta tests won't work jest.mock('../../src/libs/Permissions'); @@ -599,7 +600,7 @@ describe('Sidebar', () => { // Given an archived report with no comments const report = { ...LHNTestUtils.getFakeReport(), - lastMessageTimestamp: 0, + lastActionCreated: '2022-11-22 03:48:27.267', statusNum: CONST.REPORT.STATUS.CLOSED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }; @@ -628,7 +629,9 @@ describe('Sidebar', () => { }) // When the report has comments - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, {lastMessageTimestamp: Date.now()})) + .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, { + lastActionCreated: DateUtils.getDBTime(), + })) // Then the report is rendered in the LHN .then(() => { @@ -779,7 +782,7 @@ describe('Sidebar', () => { // Given an archived report with no comments const report = { ...LHNTestUtils.getFakeReport(), - lastMessageTimestamp: 0, + lastActionCreated: '2022-11-22 03:48:27.267', statusNum: CONST.REPORT.STATUS.CLOSED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }; @@ -808,7 +811,9 @@ describe('Sidebar', () => { }) // When the report has comments - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, {lastMessageTimestamp: Date.now()})) + .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, { + lastActionCreated: DateUtils.getDBTime(), + })) // Then the report is not rendered in the LHN .then(() => { diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 466ebd425d60..0f5a313e00cb 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -4,6 +4,7 @@ import lodashGet from 'lodash/get'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import CONST from '../../src/CONST'; +import DateUtils from '../../src/libs/DateUtils'; // Be sure to include the mocked Permissions and Expensicons libraries or else the beta tests won't work jest.mock('../../src/libs/Permissions'); @@ -180,8 +181,10 @@ describe('Sidebar', () => { [`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3, })) - // When a new comment is added to report 1 (eg. it's lastMessageTimestamp is updated) - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`, {lastMessageTimestamp: Date.now()})) + // When a new comment is added to report 1 (eg. it's lastActionCreated is updated) + .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`, { + lastActionCreated: DateUtils.getDBTime(), + })) // Then the order of the reports should be 1 > 3 > 2 // ^--- (1 goes to the front and pushes other two down) diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index e671c8f9e1f2..aedd71d6ccdc 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -5,6 +5,7 @@ import OnyxProvider from '../../src/components/OnyxProvider'; import {LocaleContextProvider} from '../../src/components/withLocalize'; import SidebarLinks from '../../src/pages/home/sidebar/SidebarLinks'; import CONST from '../../src/CONST'; +import DateUtils from '../../src/libs/DateUtils'; const TEST_MAX_SEQUENCE_NUMBER = 10; @@ -78,7 +79,7 @@ function getFakeReport(participants = ['email1@test.com', 'email2@test.com'], mi reportName: 'Report', maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, - lastMessageTimestamp: Date.now() - millisecondsInThePast, + lastActionCreated: DateUtils.getDBTime(Date.now() - millisecondsInThePast), participants, }; }