diff --git a/lib/shared/message-utils.test.js b/lib/shared/message-utils.test.js index b99b0e050..3d1583db5 100644 --- a/lib/shared/message-utils.test.js +++ b/lib/shared/message-utils.test.js @@ -1,802 +1,802 @@ // @flow import { isInvalidSidebarSource, isInvalidPinSource, isUnableToBeRenderedIndependently, findNewestMessageTimePerKeyserver, } from './message-utils.js'; import { messageSpecs } from '../shared/messages/message-specs.js'; import type { RawMessageInfo, RawSidebarSourceMessageInfo, } from '../types/message-types'; import { messageTypes } from '../types/message-types-enum.js'; import type { RawAddMembersMessageInfo } from '../types/messages/add-members.js'; import type { RawChangeRoleMessageInfo } from '../types/messages/change-role.js'; import type { RawChangeSettingsMessageInfo } from '../types/messages/change-settings.js'; import type { RawCreateEntryMessageInfo } from '../types/messages/create-entry.js'; import type { RawCreateSidebarMessageInfo } from '../types/messages/create-sidebar.js'; import type { RawCreateSubthreadMessageInfo } from '../types/messages/create-subthread.js'; import type { RawCreateThreadMessageInfo } from '../types/messages/create-thread.js'; import type { RawDeleteEntryMessageInfo } from '../types/messages/delete-entry.js'; import type { RawEditEntryMessageInfo } from '../types/messages/edit-entry.js'; import type { RawEditMessageInfo } from '../types/messages/edit.js'; import type { RawImagesMessageInfo } from '../types/messages/images.js'; import type { RawJoinThreadMessageInfo } from '../types/messages/join-thread.js'; import type { RawLeaveThreadMessageInfo } from '../types/messages/leave-thread.js'; +import type { RawLegacyUpdateRelationshipMessageInfo } from '../types/messages/legacy-update-relationship.js'; import type { RawMediaMessageInfo } from '../types/messages/media.js'; import type { RawReactionMessageInfo } from '../types/messages/reaction.js'; import type { RawRemoveMembersMessageInfo } from '../types/messages/remove-members.js'; import type { RawRestoreEntryMessageInfo } from '../types/messages/restore-entry.js'; import type { RawTextMessageInfo } from '../types/messages/text.js'; import type { RawTogglePinMessageInfo } from '../types/messages/toggle-pin.js'; -import type { RawUpdateRelationshipMessageInfo } from '../types/messages/update-relationship.js'; import { threadTypes } from '../types/thread-types-enum.js'; const textMessageInfo: RawTextMessageInfo = { type: messageTypes.TEXT, localID: 'local1', threadID: '10001', creatorID: '123', time: 10000, text: 'This is a text message', id: '1', }; const createThreadMessageInfo: RawCreateThreadMessageInfo = { type: 1, threadID: '10001', creatorID: '123', time: 10000, initialThreadState: { type: threadTypes.COMMUNITY_ROOT, name: 'Random Thread', parentThreadID: '10000', color: '#FFFFFF', memberIDs: ['1', '2', '3'], }, id: '1', }; const addMembersMessageInfo: RawAddMembersMessageInfo = { type: messageTypes.ADD_MEMBERS, threadID: '10001', creatorID: '123', time: 10000, addedUserIDs: ['4', '5'], id: '1', }; const createSubthreadMessageInfo: RawCreateSubthreadMessageInfo = { type: messageTypes.CREATE_SUB_THREAD, threadID: '10001', creatorID: '123', time: 10000, childThreadID: '10002', id: '1', }; const changeSettingsMessageInfo: RawChangeSettingsMessageInfo = { type: messageTypes.CHANGE_SETTINGS, threadID: '10000', creatorID: '123', time: 10000, field: 'color', value: '#FFFFFF', id: '1', }; const removeMembersMessageInfo: RawRemoveMembersMessageInfo = { type: messageTypes.REMOVE_MEMBERS, threadID: '10000', creatorID: '123', time: 10000, removedUserIDs: ['1', '2', '3'], id: '1', }; const changeRoleMessageinfo: RawChangeRoleMessageInfo = { type: messageTypes.CHANGE_ROLE, threadID: '10000', creatorID: '123', time: 10000, userIDs: ['1', '2', '3'], newRole: '101', roleName: 'Moderators', id: '1', }; const leaveThreadMessageInfo: RawLeaveThreadMessageInfo = { type: messageTypes.LEAVE_THREAD, threadID: '10000', creatorID: '123', time: 10000, id: '1', }; const joinThreadMessageInfo: RawJoinThreadMessageInfo = { type: messageTypes.JOIN_THREAD, threadID: '10000', creatorID: '123', time: 10000, id: '1', }; const createEntryMessageInfo: RawCreateEntryMessageInfo = { type: messageTypes.CREATE_ENTRY, threadID: '10000', creatorID: '123', time: 10000, entryID: '001', date: '2018-01-01', text: 'This is a calendar entry', id: '1', }; const editEntryMessageInfo: RawEditEntryMessageInfo = { type: messageTypes.EDIT_ENTRY, threadID: '10000', creatorID: '123', time: 10000, entryID: '001', date: '2018-01-01', text: 'This is an edited calendar entry', id: '1', }; const deleteEntryMessageInfo: RawDeleteEntryMessageInfo = { type: messageTypes.DELETE_ENTRY, threadID: '10000', creatorID: '123', time: 10000, entryID: '001', date: '2018-01-01', text: 'This is a deleted calendar entry', id: '1', }; const restoreEntryMessageInfo: RawRestoreEntryMessageInfo = { type: messageTypes.RESTORE_ENTRY, threadID: '10000', creatorID: '123', time: 10000, entryID: '001', date: '2018-01-01', text: 'This is a restored calendar entry', id: '1', }; -const updateRelationshipMessageInfo: RawUpdateRelationshipMessageInfo = { +const updateRelationshipMessageInfo: RawLegacyUpdateRelationshipMessageInfo = { type: messageTypes.LEGACY_UPDATE_RELATIONSHIP, threadID: '10000', creatorID: '123', targetID: '456', time: 10000, operation: 'request_sent', id: '1', }; const imageMessageInfo: RawImagesMessageInfo = { type: messageTypes.IMAGES, localID: 'local1', threadID: '10001', creatorID: '123', time: 10000, media: [ { id: '1', uri: 'https://example.com/image1.jpg', type: 'photo', dimensions: { height: 100, width: 100, }, thumbHash: '1234567890', }, ], id: '1', }; const mediaMessageInfo: RawMediaMessageInfo = { type: messageTypes.MULTIMEDIA, localID: 'local1', threadID: '10001', creatorID: '123', time: 10000, media: [ { id: '1', uri: 'https://example.com/image1.jpg', type: 'photo', dimensions: { height: 100, width: 100, }, thumbHash: '1234567890', }, ], id: '1', }; const sidebarSourceMessageInfo: RawSidebarSourceMessageInfo = { type: messageTypes.SIDEBAR_SOURCE, threadID: '10000', creatorID: '123', time: 10000, sourceMessage: { type: messageTypes.TEXT, localID: 'local1', threadID: '10001', creatorID: '123', time: 10000, text: 'This is a text message', id: '1', }, id: '1', }; const createSidebarMessageInfo: RawCreateSidebarMessageInfo = { type: messageTypes.CREATE_SIDEBAR, threadID: '10000', creatorID: '123', time: 10000, sourceMessageAuthorID: '123', initialThreadState: { name: 'Random Thread', parentThreadID: '10000', color: '#FFFFFF', memberIDs: ['1', '2', '3'], }, id: '1', }; const reactionMessageInfo: RawReactionMessageInfo = { type: messageTypes.REACTION, localID: 'local1', threadID: '10001', creatorID: '123', time: 10000, targetMessageID: '1', reaction: 'like', action: 'add_reaction', id: '1', }; const editMessageInfo: RawEditMessageInfo = { type: messageTypes.EDIT_MESSAGE, threadID: '10000', creatorID: '123', time: 10000, targetMessageID: '1', text: 'This is an edited message', id: '1', }; const togglePinMessageInfo: RawTogglePinMessageInfo = { type: messageTypes.TOGGLE_PIN, threadID: '10000', targetMessageID: '1', action: 'pin', pinnedContent: 'a message', creatorID: '123', time: 10000, id: '1', }; describe('isInvalidSidebarSource & canBeSidebarSource', () => { it('should return false for RawTextMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.TEXT]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(textMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawCreateThreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_THREAD]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( createThreadMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawAddMembersMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.ADD_MEMBERS]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( addMembersMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawCreateSubthreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_SUB_THREAD]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( createSubthreadMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawChangeSettingsMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CHANGE_SETTINGS]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( changeSettingsMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawRemoveMembersMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.REMOVE_MEMBERS]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( removeMembersMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawChangeRoleMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CHANGE_ROLE]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( changeRoleMessageinfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawLeaveThreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.LEAVE_THREAD]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( leaveThreadMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawJoinThreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.JOIN_THREAD]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( joinThreadMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawCreateEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_ENTRY]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( createEntryMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawEditEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.EDIT_ENTRY]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(editEntryMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawDeleteEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.DELETE_ENTRY]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( deleteEntryMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawRestoreEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.RESTORE_ENTRY]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( restoreEntryMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawUpdateRelationshipMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.LEGACY_UPDATE_RELATIONSHIP]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( updateRelationshipMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawImagesMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.IMAGES]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(imageMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return false for RawMediaMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.MULTIMEDIA]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(mediaMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return true for RawSidebarSourceMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.SIDEBAR_SOURCE]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( sidebarSourceMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(true); expect(canBeSidebarSource).toBe(false); }); it('should return false for RawCreateSidebarMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_SIDEBAR]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource( createSidebarMessageInfo, ); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(false); expect(canBeSidebarSource).toBe(true); }); it('should return true for RawReactionMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.REACTION]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(reactionMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(true); expect(canBeSidebarSource).toBe(false); }); it('should return true for RawEditMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.EDIT_MESSAGE]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(editMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(true); expect(canBeSidebarSource).toBe(false); }); it('should return true for RawTogglePinMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.TOGGLE_PIN]; const shouldBeInvalidSidebarSource = isInvalidSidebarSource(togglePinMessageInfo); const canBeSidebarSource = messageSpec.canBeSidebarSource; expect(shouldBeInvalidSidebarSource).toBe(true); expect(canBeSidebarSource).toBe(false); }); }); describe('isInvalidPinSource & canBePinned', () => { it('should return true for RawTextMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.TEXT]; const shouldBeInvalidPinSource = isInvalidPinSource(textMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(false); expect(canBePinned).toBe(true); }); it('should return false for RawCreateThreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_THREAD]; const shouldBeInvalidPinSource = isInvalidPinSource( createThreadMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawAddMembersMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.ADD_MEMBERS]; const shouldBeInvalidPinSource = isInvalidPinSource(addMembersMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawCreateSubthreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_SUB_THREAD]; const shouldBeInvalidPinSource = isInvalidPinSource( createSubthreadMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawChangeSettingsMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CHANGE_SETTINGS]; const shouldBeInvalidPinSource = isInvalidPinSource( changeSettingsMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawRemoveMembersMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.REMOVE_MEMBERS]; const shouldBeInvalidPinSource = isInvalidPinSource( removeMembersMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawChangeRoleMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CHANGE_ROLE]; const shouldBeInvalidPinSource = isInvalidPinSource(changeRoleMessageinfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawLeaveThreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.LEAVE_THREAD]; const shouldBeInvalidPinSource = isInvalidPinSource(leaveThreadMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawJoinThreadMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.JOIN_THREAD]; const shouldBeInvalidPinSource = isInvalidPinSource(joinThreadMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawCreateEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_ENTRY]; const shouldBeInvalidPinSource = isInvalidPinSource(createEntryMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawEditEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.EDIT_ENTRY]; const shouldBeInvalidPinSource = isInvalidPinSource(editEntryMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawDeleteEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.DELETE_ENTRY]; const shouldBeInvalidPinSource = isInvalidPinSource(deleteEntryMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawRestoreEntryMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.RESTORE_ENTRY]; const shouldBeInvalidPinSource = isInvalidPinSource( restoreEntryMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawUpdateRelationshipMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.LEGACY_UPDATE_RELATIONSHIP]; const shouldBeInvalidPinSource = isInvalidPinSource( updateRelationshipMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return true for RawImagesMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.IMAGES]; const shouldBeInvalidPinSource = isInvalidPinSource(imageMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(false); expect(canBePinned).toBe(true); }); it('should return true for RawMediaMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.MULTIMEDIA]; const shouldBeInvalidPinSource = isInvalidPinSource(mediaMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(false); expect(canBePinned).toBe(true); }); it('should return false for RawSidebarSourceMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.SIDEBAR_SOURCE]; const shouldBeInvalidPinSource = isInvalidPinSource( sidebarSourceMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawCreateSidebarMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.CREATE_SIDEBAR]; const shouldBeInvalidPinSource = isInvalidPinSource( createSidebarMessageInfo, ); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawReactionMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.REACTION]; const shouldBeInvalidPinSource = isInvalidPinSource(reactionMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawEditMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.EDIT_MESSAGE]; const shouldBeInvalidPinSource = isInvalidPinSource(editMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); it('should return false for RawTogglePinMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.TOGGLE_PIN]; const shouldBeInvalidPinSource = isInvalidPinSource(togglePinMessageInfo); const canBePinned = messageSpec.canBePinned; expect(shouldBeInvalidPinSource).toBe(true); expect(canBePinned).toBe(false); }); describe('isUnableToBeRenderedIndependently & canBeRenderedIndependently', () => { it('should return false for RawReactionMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.REACTION]; const shouldBeUnableToBeRenderedIndependently = isUnableToBeRenderedIndependently(reactionMessageInfo); const canBeRenderedIndependently = messageSpec.canBeRenderedIndependently; expect(shouldBeUnableToBeRenderedIndependently).toBe(true); expect(canBeRenderedIndependently).toBe(false); }); it('should return false for RawEditMessageInfo', () => { const messageSpec = messageSpecs[messageTypes.EDIT_MESSAGE]; const shouldBeUnableToBeRenderedIndependently = isUnableToBeRenderedIndependently(editMessageInfo); const canBeRenderedIndependently = messageSpec.canBeRenderedIndependently; expect(shouldBeUnableToBeRenderedIndependently).toBe(true); expect(canBeRenderedIndependently).toBe(false); }); }); }); describe('findNewestMessageTimePerKeyserver', () => { it('should return the time of the newest message for every keyserver', () => { const messages: $ReadOnlyArray = [ { type: 0, threadID: '256|100', creatorID: '256', time: 4, text: 'test', }, { type: 0, threadID: '1|100', creatorID: '256', time: 2, text: 'test', }, { type: 0, threadID: '1|100', creatorID: '256', time: 3, text: 'test', }, { type: 0, threadID: '256|100', creatorID: '256', time: 1, text: 'test', }, ]; const result = { ['256']: 4, ['1']: 3 }; expect(findNewestMessageTimePerKeyserver(messages)).toEqual(result); }); }); diff --git a/lib/shared/messages/update-relationship-message-spec.js b/lib/shared/messages/update-relationship-message-spec.js index e83dcb648..08d77d1fe 100644 --- a/lib/shared/messages/update-relationship-message-spec.js +++ b/lib/shared/messages/update-relationship-message-spec.js @@ -1,177 +1,183 @@ // @flow import invariant from 'invariant'; import { type CreateMessageInfoParams, type MessageSpec, pushTypes, } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { ClientDBMessageInfo, MessageInfo, } from '../../types/message-types.js'; import { - type RawUpdateRelationshipMessageInfo, - rawUpdateRelationshipMessageInfoValidator, - type UpdateRelationshipMessageData, - type UpdateRelationshipMessageInfo, -} from '../../types/messages/update-relationship.js'; + type RawLegacyUpdateRelationshipMessageInfo, + rawLegacyUpdateRelationshipMessageInfoValidator, + type LegacyUpdateRelationshipMessageData, + type LegacyUpdateRelationshipMessageInfo, +} from '../../types/messages/legacy-update-relationship.js'; import type { ThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { type EntityText, ET } from '../../utils/entity-text.js'; type UpdateRelationshipMessageSpec = MessageSpec< - UpdateRelationshipMessageData, - RawUpdateRelationshipMessageInfo, - UpdateRelationshipMessageInfo, + LegacyUpdateRelationshipMessageData, + RawLegacyUpdateRelationshipMessageInfo, + LegacyUpdateRelationshipMessageInfo, > & { // We need to explicitly type this as non-optional so that // it can be referenced from messageContentForClientDB below +messageContentForServerDB: ( - data: UpdateRelationshipMessageData | RawUpdateRelationshipMessageInfo, + data: + | LegacyUpdateRelationshipMessageData + | RawLegacyUpdateRelationshipMessageInfo, ) => string, ... }; export const updateRelationshipMessageSpec: UpdateRelationshipMessageSpec = Object.freeze({ messageContentForServerDB( - data: UpdateRelationshipMessageData | RawUpdateRelationshipMessageInfo, + data: + | LegacyUpdateRelationshipMessageData + | RawLegacyUpdateRelationshipMessageInfo, ): string { return JSON.stringify({ operation: data.operation, targetID: data.targetID, }); }, - messageContentForClientDB(data: RawUpdateRelationshipMessageInfo): string { + messageContentForClientDB( + data: RawLegacyUpdateRelationshipMessageInfo, + ): string { return updateRelationshipMessageSpec.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow( row: Object, - ): RawUpdateRelationshipMessageInfo { + ): RawLegacyUpdateRelationshipMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.LEGACY_UPDATE_RELATIONSHIP, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), targetID: content.targetID, operation: content.operation, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, - ): RawUpdateRelationshipMessageInfo { + ): RawLegacyUpdateRelationshipMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, 'content must be defined for UpdateRelationship', ); const content = JSON.parse(clientDBMessageInfo.content); - const rawUpdateRelationshipMessageInfo: RawUpdateRelationshipMessageInfo = + const rawUpdateRelationshipMessageInfo: RawLegacyUpdateRelationshipMessageInfo = { type: messageTypes.LEGACY_UPDATE_RELATIONSHIP, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, targetID: content.targetID, operation: content.operation, }; return rawUpdateRelationshipMessageInfo; }, createMessageInfo( - rawMessageInfo: RawUpdateRelationshipMessageInfo, + rawMessageInfo: RawLegacyUpdateRelationshipMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, - ): ?UpdateRelationshipMessageInfo { + ): ?LegacyUpdateRelationshipMessageInfo { const target = params.createRelativeUserInfos([ rawMessageInfo.targetID, ])[0]; if (!target) { return null; } return { type: messageTypes.LEGACY_UPDATE_RELATIONSHIP, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, target, time: rawMessageInfo.time, operation: rawMessageInfo.operation, }; }, rawMessageInfoFromMessageData( - messageData: UpdateRelationshipMessageData, + messageData: LegacyUpdateRelationshipMessageData, id: ?string, - ): RawUpdateRelationshipMessageInfo { + ): RawLegacyUpdateRelationshipMessageInfo { invariant(id, 'RawUpdateRelationshipMessageInfo needs id'); return { ...messageData, id }; }, // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return - robotext(messageInfo: UpdateRelationshipMessageInfo): EntityText { + robotext(messageInfo: LegacyUpdateRelationshipMessageInfo): EntityText { const creator = ET.user({ userInfo: messageInfo.creator }); if (messageInfo.operation === 'request_sent') { const target = ET.user({ userInfo: messageInfo.target }); return ET`${creator} sent ${target} a friend request`; } else if (messageInfo.operation === 'request_accepted') { const targetPossessive = ET.user({ userInfo: messageInfo.target, possessive: true, }); return ET`${creator} accepted ${targetPossessive} friend request`; } invariant( false, `Invalid operation ${messageInfo.operation} ` + `of message with type ${messageInfo.type}`, ); }, unshimMessageInfo( - unwrapped: RawUpdateRelationshipMessageInfo, - ): RawUpdateRelationshipMessageInfo { + unwrapped: RawLegacyUpdateRelationshipMessageInfo, + ): RawLegacyUpdateRelationshipMessageInfo { return unwrapped; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); const creator = ET.user({ userInfo: messageInfo.creator }); const prefix = ET`${creator}`; const title = threadInfo.uiName; const body = messageInfo.operation === 'request_sent' ? 'sent you a friend request' : 'accepted your friend request'; const merged = ET`${prefix} ${body}`; return { merged, body, title, prefix, }; }, generatesNotifs: async () => pushTypes.NOTIF, canBeSidebarSource: true, canBePinned: false, - validator: rawUpdateRelationshipMessageInfoValidator, + validator: rawLegacyUpdateRelationshipMessageInfoValidator, }); diff --git a/lib/types/message-types.js b/lib/types/message-types.js index 53a3b2f21..54848cd9c 100644 --- a/lib/types/message-types.js +++ b/lib/types/message-types.js @@ -1,684 +1,684 @@ // @flow import invariant from 'invariant'; import t, { type TDict, type TEnums, type TInterface, type TUnion, } from 'tcomb'; import { type ClientDBMediaInfo } from './media-types.js'; import { type MessageType, messageTypes } from './message-types-enum.js'; import { type AddMembersMessageData, type AddMembersMessageInfo, type RawAddMembersMessageInfo, rawAddMembersMessageInfoValidator, } from './messages/add-members.js'; import { type ChangeRoleMessageData, type ChangeRoleMessageInfo, type RawChangeRoleMessageInfo, rawChangeRoleMessageInfoValidator, } from './messages/change-role.js'; import { type ChangeSettingsMessageData, type ChangeSettingsMessageInfo, type RawChangeSettingsMessageInfo, rawChangeSettingsMessageInfoValidator, } from './messages/change-settings.js'; import { type CreateEntryMessageData, type CreateEntryMessageInfo, type RawCreateEntryMessageInfo, rawCreateEntryMessageInfoValidator, } from './messages/create-entry.js'; import { type CreateSidebarMessageData, type CreateSidebarMessageInfo, type RawCreateSidebarMessageInfo, rawCreateSidebarMessageInfoValidator, } from './messages/create-sidebar.js'; import { type CreateSubthreadMessageData, type CreateSubthreadMessageInfo, type RawCreateSubthreadMessageInfo, rawCreateSubthreadMessageInfoValidator, } from './messages/create-subthread.js'; import { type CreateThreadMessageData, type CreateThreadMessageInfo, type RawCreateThreadMessageInfo, rawCreateThreadMessageInfoValidator, } from './messages/create-thread.js'; import { type DeleteEntryMessageData, type DeleteEntryMessageInfo, type RawDeleteEntryMessageInfo, rawDeleteEntryMessageInfoValidator, } from './messages/delete-entry.js'; import { type EditEntryMessageData, type EditEntryMessageInfo, type RawEditEntryMessageInfo, rawEditEntryMessageInfoValidator, } from './messages/edit-entry.js'; import { type EditMessageData, type EditMessageInfo, type RawEditMessageInfo, rawEditMessageInfoValidator, } from './messages/edit.js'; import { type ImagesMessageData, type ImagesMessageInfo, type RawImagesMessageInfo, rawImagesMessageInfoValidator, } from './messages/images.js'; import { type JoinThreadMessageData, type JoinThreadMessageInfo, type RawJoinThreadMessageInfo, rawJoinThreadMessageInfoValidator, } from './messages/join-thread.js'; import { type LeaveThreadMessageData, type LeaveThreadMessageInfo, type RawLeaveThreadMessageInfo, rawLeaveThreadMessageInfoValidator, } from './messages/leave-thread.js'; +import { + type RawLegacyUpdateRelationshipMessageInfo, + rawLegacyUpdateRelationshipMessageInfoValidator, + type LegacyUpdateRelationshipMessageData, + type LegacyUpdateRelationshipMessageInfo, +} from './messages/legacy-update-relationship.js'; import { type MediaMessageData, type MediaMessageInfo, type MediaMessageServerDBContent, type RawMediaMessageInfo, rawMediaMessageInfoValidator, } from './messages/media.js'; import { type RawReactionMessageInfo, rawReactionMessageInfoValidator, type ReactionMessageData, type ReactionMessageInfo, } from './messages/reaction.js'; import { type RawRemoveMembersMessageInfo, rawRemoveMembersMessageInfoValidator, type RemoveMembersMessageData, type RemoveMembersMessageInfo, } from './messages/remove-members.js'; import { type RawRestoreEntryMessageInfo, rawRestoreEntryMessageInfoValidator, type RestoreEntryMessageData, type RestoreEntryMessageInfo, } from './messages/restore-entry.js'; import { type RawTextMessageInfo, rawTextMessageInfoValidator, type TextMessageData, type TextMessageInfo, } from './messages/text.js'; import { type RawTogglePinMessageInfo, rawTogglePinMessageInfoValidator, type TogglePinMessageData, type TogglePinMessageInfo, } from './messages/toggle-pin.js'; import { type RawUnsupportedMessageInfo, rawUnsupportedMessageInfoValidator, type UnsupportedMessageInfo, } from './messages/unsupported.js'; -import { - type RawUpdateRelationshipMessageInfo, - rawUpdateRelationshipMessageInfoValidator, - type UpdateRelationshipMessageData, - type UpdateRelationshipMessageInfo, -} from './messages/update-relationship.js'; import { type RelativeUserInfo, type UserInfos } from './user-types.js'; import type { CallSingleKeyserverEndpointResultInfoInterface } from '../utils/call-single-keyserver-endpoint.js'; import { values } from '../utils/objects.js'; import { tID, tNumber, tShape } from '../utils/validation-utils.js'; const composableMessageTypes = new Set([ messageTypes.TEXT, messageTypes.IMAGES, messageTypes.MULTIMEDIA, ]); export function isComposableMessageType(ourMessageType: MessageType): boolean { return composableMessageTypes.has(ourMessageType); } export function assertComposableMessageType( ourMessageType: MessageType, ): MessageType { invariant( isComposableMessageType(ourMessageType), 'MessageType is not composed', ); return ourMessageType; } export function assertComposableRawMessage( message: RawMessageInfo, ): RawComposableMessageInfo { invariant( message.type === messageTypes.TEXT || message.type === messageTypes.IMAGES || message.type === messageTypes.MULTIMEDIA, 'Message is not composable', ); return message; } export function messageDataLocalID(messageData: MessageData): ?string { if ( messageData.type !== messageTypes.TEXT && messageData.type !== messageTypes.IMAGES && messageData.type !== messageTypes.MULTIMEDIA && messageData.type !== messageTypes.REACTION ) { return null; } return messageData.localID; } const mediaMessageTypes = new Set([ messageTypes.IMAGES, messageTypes.MULTIMEDIA, ]); export function isMediaMessageType(ourMessageType: MessageType): boolean { return mediaMessageTypes.has(ourMessageType); } export function assertMediaMessageType( ourMessageType: MessageType, ): MessageType { invariant(isMediaMessageType(ourMessageType), 'MessageType is not media'); return ourMessageType; } // *MessageData = passed to createMessages function to insert into database // Raw*MessageInfo = used by server, and contained in client's local store // *MessageInfo = used by client in UI code export type ValidRawSidebarSourceMessageInfo = | RawTextMessageInfo | RawCreateThreadMessageInfo | RawAddMembersMessageInfo | RawCreateSubthreadMessageInfo | RawChangeSettingsMessageInfo | RawRemoveMembersMessageInfo | RawChangeRoleMessageInfo | RawLeaveThreadMessageInfo | RawJoinThreadMessageInfo | RawCreateEntryMessageInfo | RawEditEntryMessageInfo | RawDeleteEntryMessageInfo | RawRestoreEntryMessageInfo | RawImagesMessageInfo | RawMediaMessageInfo - | RawUpdateRelationshipMessageInfo + | RawLegacyUpdateRelationshipMessageInfo | RawCreateSidebarMessageInfo | RawUnsupportedMessageInfo; export type SidebarSourceMessageData = { +type: 17, +threadID: string, +creatorID: string, +time: number, +sourceMessage?: ValidRawSidebarSourceMessageInfo, }; export type MessageData = | TextMessageData | CreateThreadMessageData | AddMembersMessageData | CreateSubthreadMessageData | ChangeSettingsMessageData | RemoveMembersMessageData | ChangeRoleMessageData | LeaveThreadMessageData | JoinThreadMessageData | CreateEntryMessageData | EditEntryMessageData | DeleteEntryMessageData | RestoreEntryMessageData | ImagesMessageData | MediaMessageData - | UpdateRelationshipMessageData + | LegacyUpdateRelationshipMessageData | SidebarSourceMessageData | CreateSidebarMessageData | ReactionMessageData | EditMessageData | TogglePinMessageData; export type MultimediaMessageData = ImagesMessageData | MediaMessageData; export type RawMultimediaMessageInfo = | RawImagesMessageInfo | RawMediaMessageInfo; export const rawMultimediaMessageInfoValidator: TUnion = t.union([rawImagesMessageInfoValidator, rawMediaMessageInfoValidator]); export type RawComposableMessageInfo = | RawTextMessageInfo | RawMultimediaMessageInfo; const rawComposableMessageInfoValidator = t.union([ rawTextMessageInfoValidator, rawMultimediaMessageInfoValidator, ]); export type RawRobotextMessageInfo = | RawCreateThreadMessageInfo | RawAddMembersMessageInfo | RawCreateSubthreadMessageInfo | RawChangeSettingsMessageInfo | RawRemoveMembersMessageInfo | RawChangeRoleMessageInfo | RawLeaveThreadMessageInfo | RawJoinThreadMessageInfo | RawCreateEntryMessageInfo | RawEditEntryMessageInfo | RawDeleteEntryMessageInfo | RawRestoreEntryMessageInfo - | RawUpdateRelationshipMessageInfo + | RawLegacyUpdateRelationshipMessageInfo | RawCreateSidebarMessageInfo | RawUnsupportedMessageInfo | RawTogglePinMessageInfo; const rawRobotextMessageInfoValidator = t.union([ rawCreateThreadMessageInfoValidator, rawAddMembersMessageInfoValidator, rawCreateSubthreadMessageInfoValidator, rawChangeSettingsMessageInfoValidator, rawRemoveMembersMessageInfoValidator, rawChangeRoleMessageInfoValidator, rawLeaveThreadMessageInfoValidator, rawJoinThreadMessageInfoValidator, rawCreateEntryMessageInfoValidator, rawEditEntryMessageInfoValidator, rawDeleteEntryMessageInfoValidator, rawRestoreEntryMessageInfoValidator, - rawUpdateRelationshipMessageInfoValidator, + rawLegacyUpdateRelationshipMessageInfoValidator, rawCreateSidebarMessageInfoValidator, rawUnsupportedMessageInfoValidator, rawTogglePinMessageInfoValidator, ]); export type RawSidebarSourceMessageInfo = { ...SidebarSourceMessageData, id: string, }; export const rawSidebarSourceMessageInfoValidator: TInterface = tShape({ type: tNumber(messageTypes.SIDEBAR_SOURCE), threadID: tID, creatorID: t.String, time: t.Number, sourceMessage: t.maybe( t.union([ rawComposableMessageInfoValidator, rawRobotextMessageInfoValidator, ]), ), id: tID, }); export type RawMessageInfo = | RawComposableMessageInfo | RawRobotextMessageInfo | RawSidebarSourceMessageInfo | RawReactionMessageInfo | RawEditMessageInfo; export const rawMessageInfoValidator: TUnion = t.union([ rawComposableMessageInfoValidator, rawRobotextMessageInfoValidator, rawSidebarSourceMessageInfoValidator, rawReactionMessageInfoValidator, rawEditMessageInfoValidator, ]); export type LocallyComposedMessageInfo = | ({ ...RawImagesMessageInfo, +localID: string, } & RawImagesMessageInfo) | ({ ...RawMediaMessageInfo, +localID: string, } & RawMediaMessageInfo) | ({ ...RawTextMessageInfo, +localID: string, } & RawTextMessageInfo) | ({ ...RawReactionMessageInfo, +localID: string, } & RawReactionMessageInfo); export type MultimediaMessageInfo = ImagesMessageInfo | MediaMessageInfo; export type ComposableMessageInfo = TextMessageInfo | MultimediaMessageInfo; export type RobotextMessageInfo = | CreateThreadMessageInfo | AddMembersMessageInfo | CreateSubthreadMessageInfo | ChangeSettingsMessageInfo | RemoveMembersMessageInfo | ChangeRoleMessageInfo | LeaveThreadMessageInfo | JoinThreadMessageInfo | CreateEntryMessageInfo | EditEntryMessageInfo | DeleteEntryMessageInfo | RestoreEntryMessageInfo | UnsupportedMessageInfo - | UpdateRelationshipMessageInfo + | LegacyUpdateRelationshipMessageInfo | CreateSidebarMessageInfo | TogglePinMessageInfo; export type PreviewableMessageInfo = | RobotextMessageInfo | MultimediaMessageInfo | ReactionMessageInfo; export type ValidSidebarSourceMessageInfo = | TextMessageInfo | CreateThreadMessageInfo | AddMembersMessageInfo | CreateSubthreadMessageInfo | ChangeSettingsMessageInfo | RemoveMembersMessageInfo | ChangeRoleMessageInfo | LeaveThreadMessageInfo | JoinThreadMessageInfo | CreateEntryMessageInfo | EditEntryMessageInfo | DeleteEntryMessageInfo | RestoreEntryMessageInfo | ImagesMessageInfo | MediaMessageInfo - | UpdateRelationshipMessageInfo + | LegacyUpdateRelationshipMessageInfo | CreateSidebarMessageInfo | UnsupportedMessageInfo; export type SidebarSourceMessageInfo = { +type: 17, +id: string, +threadID: string, +creator: RelativeUserInfo, +time: number, +sourceMessage: ValidSidebarSourceMessageInfo, }; export type MessageInfo = | ComposableMessageInfo | RobotextMessageInfo | SidebarSourceMessageInfo | ReactionMessageInfo | EditMessageInfo; export type ThreadMessageInfo = { messageIDs: string[], startReached: boolean, }; const threadMessageInfoValidator: TInterface = tShape({ messageIDs: t.list(tID), startReached: t.Boolean, }); // Tracks client-local information about a message that hasn't been assigned an // ID by the server yet. As soon as the client gets an ack from the server for // this message, it will clear the LocalMessageInfo. export type LocalMessageInfo = { +sendFailed?: boolean, }; const localMessageInfoValidator: TInterface = tShape({ sendFailed: t.maybe(t.Boolean), }); export type MessageStoreThreads = { +[threadID: string]: ThreadMessageInfo, }; const messageStoreThreadsValidator: TDict = t.dict( tID, threadMessageInfoValidator, ); export type MessageStore = { +messages: { +[id: string]: RawMessageInfo }, +threads: MessageStoreThreads, +local: { +[id: string]: LocalMessageInfo }, +currentAsOf: { +[keyserverID: string]: number }, }; export const messageStoreValidator: TInterface = tShape({ messages: t.dict(tID, rawMessageInfoValidator), threads: messageStoreThreadsValidator, local: t.dict(t.String, localMessageInfoValidator), currentAsOf: t.dict(t.String, t.Number), }); // We were initially using `number`s` for `thread`, `type`, `future_type`, etc. // However, we ended up changing `thread` to `string` to account for thread IDs // including information about the keyserver (eg 'GENESIS|123') in the future. // // At that point we discussed whether we should switch the remaining `number` // fields to `string`s for consistency and flexibility. We researched whether // there was any performance cost to using `string`s instead of `number`s and // found the differences to be negligible. We also concluded using `string`s // may be safer after considering `jsi::Number` and the various C++ number // representations on the CommCoreModule side. export type ClientDBMessageInfo = { +id: string, +local_id: ?string, +thread: string, +user: string, +type: string, +future_type: ?string, +content: ?string, +time: string, +media_infos: ?$ReadOnlyArray, }; export type ClientDBThreadMessageInfo = { +id: string, +start_reached: string, }; export const messageTruncationStatus = Object.freeze({ // EXHAUSTIVE means we've reached the start of the thread. Either the result // set includes the very first message for that thread, or there is nothing // behind the cursor you queried for. Given that the client only ever issues // ranged queries whose range, when unioned with what is in state, represent // the set of all messages for a given thread, we can guarantee that getting // EXHAUSTIVE means the start has been reached. EXHAUSTIVE: 'exhaustive', // TRUNCATED is rare, and means that the server can't guarantee that the // result set for a given thread is contiguous with what the client has in its // state. If the client can't verify the contiguousness itself, it needs to // replace its Redux store's contents with what it is in this payload. // 1) getMessageInfosSince: Result set for thread is equal to max, and the // truncation status isn't EXHAUSTIVE (ie. doesn't include the very first // message). // 2) getMessageInfos: MessageSelectionCriteria does not specify cursors, the // result set for thread is equal to max, and the truncation status isn't // EXHAUSTIVE. If cursors are specified, we never return truncated, since // the cursor given us guarantees the contiguousness of the result set. // Note that in the reducer, we can guarantee contiguousness if there is any // intersection between messageIDs in the result set and the set currently in // the Redux store. TRUNCATED: 'truncated', // UNCHANGED means the result set is guaranteed to be contiguous with what the // client has in its state, but is not EXHAUSTIVE. Basically, it's anything // that isn't either EXHAUSTIVE or TRUNCATED. UNCHANGED: 'unchanged', }); export type MessageTruncationStatus = $Values; export function assertMessageTruncationStatus( ourMessageTruncationStatus: string, ): MessageTruncationStatus { invariant( ourMessageTruncationStatus === 'truncated' || ourMessageTruncationStatus === 'unchanged' || ourMessageTruncationStatus === 'exhaustive', 'string is not ourMessageTruncationStatus enum', ); return ourMessageTruncationStatus; } export const messageTruncationStatusValidator: TEnums = t.enums.of( values(messageTruncationStatus), ); export type MessageTruncationStatuses = { [threadID: string]: MessageTruncationStatus, }; export const messageTruncationStatusesValidator: TDict = t.dict(tID, messageTruncationStatusValidator); export type ThreadCursors = { +[threadID: string]: ?string }; export type MessageSelectionCriteria = { +threadCursors?: ?ThreadCursors, +joinedThreads?: ?boolean, +newerThan?: ?number, }; export type FetchMessageInfosRequest = { +cursors: ThreadCursors, +numberPerThread?: ?number, }; export type FetchMessageInfosResponse = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +userInfos: UserInfos, }; export type FetchMessageInfosResult = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, }; export type FetchMessageInfosPayload = { +threadID: string, +rawMessageInfos: $ReadOnlyArray, +truncationStatus: MessageTruncationStatus, }; export type MessagesResponse = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, +currentAsOf: number, }; export const messagesResponseValidator: TInterface = tShape({ rawMessageInfos: t.list(rawMessageInfoValidator), truncationStatuses: messageTruncationStatusesValidator, currentAsOf: t.Number, }); export type SimpleMessagesPayload = { +rawMessageInfos: $ReadOnlyArray, +truncationStatuses: MessageTruncationStatuses, }; export const defaultNumberPerThread = 20; export const defaultMaxMessageAge = 14 * 24 * 60 * 60 * 1000; // 2 weeks export type SendMessageResponse = { +newMessageInfo: RawMessageInfo, }; export type SendMessageResult = { +id: string, +time: number, +interface: CallSingleKeyserverEndpointResultInfoInterface, }; export type SendMessagePayload = { +localID: string, +serverID: string, +threadID: string, +time: number, +interface: CallSingleKeyserverEndpointResultInfoInterface, }; export type SendTextMessageRequest = { +threadID: string, +localID?: string, +text: string, +sidebarCreation?: boolean, }; export type SendMultimediaMessageRequest = // This option is only used for messageTypes.IMAGES | { +threadID: string, +localID: string, +sidebarCreation?: boolean, +mediaIDs: $ReadOnlyArray, } | { +threadID: string, +localID: string, +sidebarCreation?: boolean, +mediaMessageContents: $ReadOnlyArray, }; export type SendReactionMessageRequest = { +threadID: string, +localID?: string, +targetMessageID: string, +reaction: string, +action: 'add_reaction' | 'remove_reaction', }; export type SendEditMessageRequest = { +targetMessageID: string, +text: string, }; export type SendEditMessageResponse = { +newMessageInfos: $ReadOnlyArray, }; export type EditMessagePayload = SendEditMessageResponse; export type SendEditMessageResult = SendEditMessageResponse; export type EditMessageContent = { +text: string, }; // Used for the message info included in log-in type actions export type GenericMessagesResult = { +messageInfos: RawMessageInfo[], +truncationStatus: MessageTruncationStatuses, +watchedIDsAtRequestTime: $ReadOnlyArray, +currentAsOf: { +[keyserverID: string]: number }, }; export type SaveMessagesPayload = { +rawMessageInfos: $ReadOnlyArray, +updatesCurrentAsOf: number, }; export type NewMessagesPayload = { +messagesResult: MessagesResponse, }; export const newMessagesPayloadValidator: TInterface = tShape({ messagesResult: messagesResponseValidator, }); export type MessageStorePrunePayload = { +threadIDs: $ReadOnlyArray, }; export type FetchPinnedMessagesRequest = { +threadID: string, }; export type FetchPinnedMessagesResult = { +pinnedMessages: $ReadOnlyArray, }; export type SearchMessagesRequest = { +query: string, +threadID: string, +cursor?: ?string, }; export type SearchMessagesResponse = { +messages: $ReadOnlyArray, +endReached: boolean, }; diff --git a/lib/types/messages/update-relationship.js b/lib/types/messages/legacy-update-relationship.js similarity index 69% rename from lib/types/messages/update-relationship.js rename to lib/types/messages/legacy-update-relationship.js index 0ca3b05f4..22c0e2ad7 100644 --- a/lib/types/messages/update-relationship.js +++ b/lib/types/messages/legacy-update-relationship.js @@ -1,42 +1,42 @@ // @flow import t, { type TInterface } from 'tcomb'; import { tID, tNumber, tShape } from '../../utils/validation-utils.js'; import { messageTypes } from '../message-types-enum.js'; import type { RelativeUserInfo } from '../user-types.js'; -export type UpdateRelationshipMessageData = { +export type LegacyUpdateRelationshipMessageData = { +type: 16, +threadID: string, +creatorID: string, +targetID: string, +time: number, +operation: 'request_sent' | 'request_accepted', }; -export type RawUpdateRelationshipMessageInfo = { - ...UpdateRelationshipMessageData, +export type RawLegacyUpdateRelationshipMessageInfo = { + ...LegacyUpdateRelationshipMessageData, id: string, }; -export const rawUpdateRelationshipMessageInfoValidator: TInterface = - tShape({ +export const rawLegacyUpdateRelationshipMessageInfoValidator: TInterface = + tShape({ type: tNumber(messageTypes.LEGACY_UPDATE_RELATIONSHIP), threadID: tID, creatorID: t.String, targetID: t.String, time: t.Number, operation: t.enums.of(['request_sent', 'request_accepted']), id: tID, }); -export type UpdateRelationshipMessageInfo = { +export type LegacyUpdateRelationshipMessageInfo = { +type: 16, +id: string, +threadID: string, +creator: RelativeUserInfo, +target: RelativeUserInfo, +time: number, +operation: 'request_sent' | 'request_accepted', }; diff --git a/lib/utils/message-ops-utils.test.js b/lib/utils/message-ops-utils.test.js index 268d7b942..a28229b53 100644 --- a/lib/utils/message-ops-utils.test.js +++ b/lib/utils/message-ops-utils.test.js @@ -1,548 +1,549 @@ // @flow import { translateRawMessageInfoToClientDBMessageInfo, translateClientDBMessageInfoToRawMessageInfo, translateClientDBMediaInfosToMedia, } from './message-ops-utils.js'; import type { ClientDBMessageInfo, RawSidebarSourceMessageInfo, } from '../types/message-types.js'; import type { RawAddMembersMessageInfo } from '../types/messages/add-members.js'; import type { RawChangeSettingsMessageInfo } from '../types/messages/change-settings.js'; import type { RawCreateEntryMessageInfo } from '../types/messages/create-entry.js'; import type { RawCreateSidebarMessageInfo } from '../types/messages/create-sidebar.js'; import type { RawCreateSubthreadMessageInfo } from '../types/messages/create-subthread.js'; import type { RawCreateThreadMessageInfo } from '../types/messages/create-thread.js'; import type { RawDeleteEntryMessageInfo } from '../types/messages/delete-entry.js'; import type { RawEditEntryMessageInfo } from '../types/messages/edit-entry.js'; import type { RawImagesMessageInfo } from '../types/messages/images.js'; import type { RawJoinThreadMessageInfo } from '../types/messages/join-thread.js'; import type { RawLeaveThreadMessageInfo } from '../types/messages/leave-thread.js'; +import type { RawLegacyUpdateRelationshipMessageInfo } from '../types/messages/legacy-update-relationship.js'; import type { RawRemoveMembersMessageInfo } from '../types/messages/remove-members.js'; import type { RawRestoreEntryMessageInfo } from '../types/messages/restore-entry.js'; import type { RawTextMessageInfo } from '../types/messages/text.js'; -import type { RawUpdateRelationshipMessageInfo } from '../types/messages/update-relationship.js'; test('TEXT: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawTextMessageInfo: RawTextMessageInfo = { type: 0, localID: 'local7', threadID: '85466', text: 'Hello world', creatorID: '85435', time: 1637788332565, id: '85551', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawTextMessageInfo), ), ).toStrictEqual(rawTextMessageInfo); }); test('TEXT (local): rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const localRawTextMessageInfo: RawTextMessageInfo = { type: 0, localID: 'local7', threadID: '85466', text: 'Hello world', creatorID: '85435', time: 1637788332565, }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(localRawTextMessageInfo), ), ).toStrictEqual(localRawTextMessageInfo); }); test('CREATE_THREAD: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawCreateThreadMessageInfo: RawCreateThreadMessageInfo = { type: 1, threadID: '85466', creatorID: '85435', time: 1637778853178, initialThreadState: { type: 6, name: null, parentThreadID: '1', color: '648CAA', memberIDs: ['256', '85435'], }, id: '85482', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawCreateThreadMessageInfo), ), ).toStrictEqual(rawCreateThreadMessageInfo); }); test('ADD_MEMBER: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawAddMemberMessageInfo: RawAddMembersMessageInfo = { type: 2, threadID: '85946', creatorID: '83809', time: 1638236346010, addedUserIDs: ['256'], id: '85986', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawAddMemberMessageInfo), ), ).toStrictEqual(rawAddMemberMessageInfo); }); test('CREATE_SUB_THREAD: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawCreateSubthreadMessageInfo: RawCreateSubthreadMessageInfo = { type: 3, threadID: '85946', creatorID: '83809', time: 1638237345553, childThreadID: '85990', id: '85997', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo( rawCreateSubthreadMessageInfo, ), ), ).toStrictEqual(rawCreateSubthreadMessageInfo); }); test('CHANGE_SETTINGS: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawChangeSettingsMessageInfo: RawChangeSettingsMessageInfo = { type: 4, threadID: '85946', creatorID: '83809', time: 1638236125774, field: 'color', value: '009cc8', id: '85972', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo( rawChangeSettingsMessageInfo, ), ), ).toStrictEqual(rawChangeSettingsMessageInfo); }); test('REMOVE_MEMBERS: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawRemoveMembersMessageInfo: RawRemoveMembersMessageInfo = { type: 5, threadID: '85990', creatorID: '83809', time: 1638237832234, removedUserIDs: ['85435'], id: '86014', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawRemoveMembersMessageInfo), ), ).toStrictEqual(rawRemoveMembersMessageInfo); }); test('LEAVE_THREAD: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawLeaveThreadMessageInfo: RawLeaveThreadMessageInfo = { type: 7, id: '86088', threadID: '85946', time: 1638238389038, creatorID: '85435', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawLeaveThreadMessageInfo), ), ).toStrictEqual(rawLeaveThreadMessageInfo); }); test('JOIN_THREAD: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawJoinThreadMessageInfo: RawJoinThreadMessageInfo = { type: 8, threadID: '86125', creatorID: '85435', time: 1638239691665, id: '86149', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawJoinThreadMessageInfo), ), ).toStrictEqual(rawJoinThreadMessageInfo); }); test('CREATE_ENTRY: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawCreateEntryMessageInfo: RawCreateEntryMessageInfo = { type: 9, threadID: '85630', creatorID: '85435', time: 1638239928303, entryID: '86151', date: '2021-11-29', text: 'Hello world', id: '86154', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawCreateEntryMessageInfo), ), ).toStrictEqual(rawCreateEntryMessageInfo); }); test('EDIT_ENTRY: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawEditEntryMessageInfo: RawEditEntryMessageInfo = { type: 10, threadID: '85630', creatorID: '85435', time: 1638240110661, entryID: '86151', date: '2021-11-29', text: 'Hello universe', id: '86179', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawEditEntryMessageInfo), ), ).toStrictEqual(rawEditEntryMessageInfo); }); test('DELETE_ENTRY: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawDeleteEntryMessageInfo: RawDeleteEntryMessageInfo = { type: 11, threadID: '85630', creatorID: '85435', time: 1638240286574, entryID: '86151', date: '2021-11-29', text: 'Hello universe', id: '86189', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawDeleteEntryMessageInfo), ), ).toStrictEqual(rawDeleteEntryMessageInfo); }); test('RESTORE_ENTRY: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawRestoreEntryMessageInfo: RawRestoreEntryMessageInfo = { type: 12, threadID: '85630', creatorID: '83809', time: 1638240605195, entryID: '86151', date: '2021-11-29', text: 'Hello universe', id: '86211', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawRestoreEntryMessageInfo), ), ).toStrictEqual(rawRestoreEntryMessageInfo); }); test('IMAGES: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawImagesMessageInfo: RawImagesMessageInfo = { type: 14, threadID: '85466', creatorID: '85435', time: 1637779260087, media: [ { id: '85504', type: 'photo', uri: 'http://localhost/comm/upload/85504/ba36cea2b5a796f6', dimensions: { width: 1920, height: 1281, }, thumbHash: 'some_thumb_hash', }, ], id: '85505', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawImagesMessageInfo), ), ).toStrictEqual(rawImagesMessageInfo); }); test('IMAGES (local): rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const localRawImagesMessageInfo: RawImagesMessageInfo = { type: 14, threadID: '85466', creatorID: '85435', time: 1637779260087, media: [ { id: '85504', type: 'photo', uri: 'http://localhost/comm/upload/85504/ba36cea2b5a796f6', dimensions: { width: 1920, height: 1281, }, thumbHash: 'some_thumb_hash', }, ], localID: 'local123', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(localRawImagesMessageInfo), ), ).toStrictEqual(localRawImagesMessageInfo); }); test('LEGACY_UPDATE_RELATIONSHIP: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { - const rawUpdateRelationshipMessageInfo: RawUpdateRelationshipMessageInfo = { - type: 16, - id: '85651', - threadID: '85630', - time: 1638235869690, - creatorID: '83809', - targetID: '85435', - operation: 'request_accepted', - }; + const rawUpdateRelationshipMessageInfo: RawLegacyUpdateRelationshipMessageInfo = + { + type: 16, + id: '85651', + threadID: '85630', + time: 1638235869690, + creatorID: '83809', + targetID: '85435', + operation: 'request_accepted', + }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo( rawUpdateRelationshipMessageInfo, ), ), ).toStrictEqual(rawUpdateRelationshipMessageInfo); }); test('SIDEBAR_SOURCE: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawSidebarSourceMessageInfo: RawSidebarSourceMessageInfo = { type: 17, threadID: '86219', creatorID: '85435', time: 1638250532831, sourceMessage: { type: 0, id: '85486', threadID: '85466', time: 1637778853216, creatorID: '256', text: 'as you inevitably discover bugs, have feature requests, or design suggestions, feel free to message them to me in the app.', }, id: '86223', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawSidebarSourceMessageInfo), ), ).toStrictEqual(rawSidebarSourceMessageInfo); }); test('CREATE_SIDEBAR: rawMessageInfo -> clientDBMessageInfo -> rawMessageInfo', () => { const rawCreateSidebarMessageInfo: RawCreateSidebarMessageInfo = { type: 18, threadID: '86219', creatorID: '85435', time: 1638250532831, sourceMessageAuthorID: '256', initialThreadState: { name: 'as you inevitably discover ...', parentThreadID: '85466', color: 'ffffff', memberIDs: ['256', '85435'], }, id: '86224', }; expect( translateClientDBMessageInfoToRawMessageInfo( translateRawMessageInfoToClientDBMessageInfo(rawCreateSidebarMessageInfo), ), ).toStrictEqual(rawCreateSidebarMessageInfo); }); test('Test translateClientDBMediaInfosToMedia(...) with one video', () => { const clientDBMessageInfo: ClientDBMessageInfo = { id: 'local0', local_id: 'local0', thread: '90145', user: '90134', type: '15', future_type: null, time: '1665014145088', content: '[{"type":"video","uploadID":"localUpload0","thumbnailUploadID":"localUpload1"}]', media_infos: [ { id: 'localUpload0', uri: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', type: 'video', extras: '{"dimensions":{"height":1010,"width":576},"loop":false,"local_media_selection":{"step":"video_library","dimensions":{"height":1010,"width":576},"uri":"assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov","filename":"IMG_0007.MOV","mediaNativeID":"6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2/L0/001","duration":25.866666666666667,"selectTime":1665014144968,"sendTime":1665014144968,"retries":0}}', }, { id: 'localUpload1', uri: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', type: 'photo', extras: '{"dimensions":{"height":1010,"width":576},"loop":false}', }, ], }; const rawMessageInfo = { type: 15, threadID: '90145', creatorID: '90134', time: 1665014145088, media: [ { id: 'localUpload0', uri: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', type: 'video', dimensions: { height: 1010, width: 576 }, localMediaSelection: { step: 'video_library', dimensions: { height: 1010, width: 576 }, uri: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', filename: 'IMG_0007.MOV', mediaNativeID: '6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2/L0/001', duration: 25.866666666666667, selectTime: 1665014144968, sendTime: 1665014144968, retries: 0, }, loop: false, thumbnailThumbHash: undefined, thumbnailID: 'localUpload1', thumbnailURI: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', }, ], localID: 'local0', }; expect(translateClientDBMediaInfosToMedia(clientDBMessageInfo)).toStrictEqual( rawMessageInfo.media, ); }); test('Test translateClientDBMediaInfosToMedia() with encrypted photo', () => { const clientDBMessageInfo: ClientDBMessageInfo = { id: 'local0', local_id: 'local0', thread: '90145', user: '90134', type: '15', future_type: null, time: '1665014145088', content: '[{"type":"photo","uploadID":"localUpload0"}]', media_infos: [ { id: 'localUpload0', uri: 'assets-library://asset/asset.jpeg?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=jpeg', type: 'photo', extras: '{"dimensions":{"height":1010,"width":576},"loop":false,"encryption_key":"someKey","thumb_hash":"thumb"}', }, ], }; const rawMessageInfo = { type: 15, threadID: '90145', creatorID: '90134', time: 1665014145088, media: [ { id: 'localUpload0', type: 'encrypted_photo', blobURI: 'assets-library://asset/asset.jpeg?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=jpeg', encryptionKey: 'someKey', dimensions: { height: 1010, width: 576 }, thumbHash: 'thumb', }, ], localID: 'local0', }; expect(translateClientDBMediaInfosToMedia(clientDBMessageInfo)).toStrictEqual( rawMessageInfo.media, ); }); test('Test translateClientDBMediaInfosToMedia() with encrypted video', () => { const clientDBMessageInfo: ClientDBMessageInfo = { id: 'local0', local_id: 'local0', thread: '90145', user: '90134', type: '15', future_type: null, time: '1665014145088', content: '[{"type":"video","uploadID":"localUpload0","thumbnailUploadID":"localUpload1"}]', media_infos: [ { id: 'localUpload0', uri: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', type: 'video', extras: '{"dimensions":{"height":1010,"width":576},"loop":false,"encryption_key":"someVideoKey"}', }, { id: 'localUpload1', uri: 'assets-library://asset/asset.jpeg?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=jpeg', type: 'photo', extras: '{"dimensions":{"height":1010,"width":576},"loop":false,"encryption_key":"someThumbKey","thumb_hash":"thumb"}', }, ], }; const rawMessageInfo = { type: 15, threadID: '90145', creatorID: '90134', time: 1665014145088, media: [ { id: 'localUpload0', type: 'encrypted_video', blobURI: 'assets-library://asset/asset.mov?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=mov', encryptionKey: 'someVideoKey', dimensions: { height: 1010, width: 576 }, loop: false, thumbnailID: 'localUpload1', thumbnailBlobURI: 'assets-library://asset/asset.jpeg?id=6F1BEA56-3875-474C-B3AF-B11DEDCBAFF2&ext=jpeg', thumbnailEncryptionKey: 'someThumbKey', thumbnailThumbHash: 'thumb', }, ], localID: 'local0', }; expect(translateClientDBMediaInfosToMedia(clientDBMessageInfo)).toStrictEqual( rawMessageInfo.media, ); });