2023-01-03 19:55:46 +00:00
// Copyright 2020 Signal Messenger, LLC
2020-12-04 20:41:40 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai' ;
2021-02-23 20:34:28 +00:00
import * as sinon from 'sinon' ;
2021-03-03 20:09:58 +00:00
import { v4 as uuid } from 'uuid' ;
import { times } from 'lodash' ;
2021-02-23 20:34:28 +00:00
import { reducer as rootReducer } from '../../../state/reducer' ;
import { noopAction } from '../../../state/ducks/noop' ;
2021-11-01 19:13:35 +00:00
import {
ComposerStep ,
2022-02-16 18:36:21 +00:00
ConversationVerificationState ,
OneTimeModalState ,
2021-11-01 19:13:35 +00:00
} from '../../../state/ducks/conversationsEnums' ;
2021-10-26 19:15:33 +00:00
import type {
2022-02-16 18:36:21 +00:00
CancelVerificationDataByConversationActionType ,
2020-12-04 20:41:40 +00:00
ConversationMessageType ,
2022-02-16 18:36:21 +00:00
ConversationType ,
2022-06-16 19:12:50 +00:00
ConversationsStateType ,
2021-03-03 20:09:58 +00:00
MessageType ,
2022-06-16 19:12:50 +00:00
SelectedConversationChangedActionType ,
2021-03-03 20:09:58 +00:00
ToggleConversationInChooseMembersActionType ,
2021-10-26 19:15:33 +00:00
} from '../../../state/ducks/conversations' ;
import {
2022-06-16 19:12:50 +00:00
SELECTED_CONVERSATION_CHANGED ,
2021-10-26 19:15:33 +00:00
actions ,
2022-02-16 18:36:21 +00:00
cancelConversationVerification ,
clearCancelledConversationVerification ,
2020-12-04 20:41:40 +00:00
getConversationCallMode ,
2021-01-06 15:41:43 +00:00
getEmptyState ,
2020-12-04 20:41:40 +00:00
reducer ,
2021-01-06 15:41:43 +00:00
updateConversationLookups ,
2020-12-04 20:41:40 +00:00
} from '../../../state/ducks/conversations' ;
2021-08-12 18:15:55 +00:00
import { ReadStatus } from '../../../messages/MessageReadStatus' ;
2021-06-01 23:30:25 +00:00
import { ContactSpoofingType } from '../../../util/contactSpoofing' ;
2020-12-04 20:41:40 +00:00
import { CallMode } from '../../../types/Calling' ;
2021-10-26 22:59:08 +00:00
import { UUID } from '../../../types/UUID' ;
import {
getDefaultConversation ,
getDefaultConversationWithUuid ,
2022-11-19 08:31:18 +00:00
getDefaultGroup ,
2021-10-26 22:59:08 +00:00
} from '../../../test-both/helpers/getDefaultConversation' ;
2021-08-06 00:17:05 +00:00
import { getDefaultAvatars } from '../../../types/Avatar' ;
2021-08-16 14:33:27 +00:00
import {
defaultStartDirectConversationComposerState ,
defaultChooseGroupMembersComposerState ,
defaultSetGroupMetadataComposerState ,
} from '../../../test-both/helpers/defaultComposerStates' ;
2022-02-11 21:38:52 +00:00
import { updateRemoteConfig } from '../../../test-both/helpers/RemoteConfigStub' ;
2022-11-11 04:10:30 +00:00
import type { ShowSendAnywayDialogActionType } from '../../../state/ducks/globalModals' ;
import { SHOW_SEND_ANYWAY_DIALOG } from '../../../state/ducks/globalModals' ;
import type { StoryDistributionListsActionType } from '../../../state/ducks/storyDistributionLists' ;
import {
DELETE_LIST ,
HIDE_MY_STORIES_FROM ,
MODIFY_LIST ,
VIEWERS_CHANGED ,
} from '../../../state/ducks/storyDistributionLists' ;
import { MY_STORY_ID } from '../../../types/Stories' ;
2020-12-04 20:41:40 +00:00
2020-12-07 20:43:19 +00:00
const {
2021-03-03 20:09:58 +00:00
clearGroupCreationError ,
2021-10-26 22:59:08 +00:00
clearInvitedUuidsForNewlyCreatedGroup ,
2021-04-21 16:31:12 +00:00
closeContactSpoofingReview ,
2021-03-03 20:09:58 +00:00
closeMaximumGroupSizeModal ,
closeRecommendedGroupSizeModal ,
2022-02-16 18:36:21 +00:00
conversationStoppedByMissingVerification ,
2022-03-03 20:23:10 +00:00
createGroup ,
2022-05-10 20:19:58 +00:00
discardMessages ,
2022-05-11 22:41:45 +00:00
messageChanged ,
2020-12-07 20:43:19 +00:00
repairNewestMessage ,
repairOldestMessage ,
2022-05-11 22:41:45 +00:00
resetAllChatColors ,
reviewGroupMemberNameCollision ,
reviewMessageRequestNameCollision ,
2021-03-03 20:09:58 +00:00
setComposeGroupAvatar ,
setComposeGroupName ,
2021-02-23 20:34:28 +00:00
setComposeSearchTerm ,
2021-01-29 22:16:48 +00:00
setPreJoinConversation ,
2021-02-23 20:34:28 +00:00
showArchivedConversations ,
2022-05-11 22:41:45 +00:00
showChooseGroupMembers ,
2022-06-16 19:12:50 +00:00
showConversation ,
2021-02-23 20:34:28 +00:00
showInbox ,
startComposing ,
2021-03-03 20:09:58 +00:00
startSettingGroupMetadata ,
toggleConversationInChooseMembers ,
2020-12-07 20:43:19 +00:00
} = actions ;
2020-12-04 20:41:40 +00:00
describe ( 'both/state/ducks/conversations' , ( ) = > {
2022-11-11 04:10:30 +00:00
const UUID_1 = UUID . generate ( ) . toString ( ) ;
const UUID_2 = UUID . generate ( ) . toString ( ) ;
const UUID_3 = UUID . generate ( ) . toString ( ) ;
const UUID_4 = UUID . generate ( ) . toString ( ) ;
2021-02-23 20:34:28 +00:00
const getEmptyRootState = ( ) = > rootReducer ( undefined , noopAction ( ) ) ;
let sinonSandbox : sinon.SinonSandbox ;
2021-03-03 20:09:58 +00:00
let createGroupStub : sinon.SinonStub ;
2021-02-23 20:34:28 +00:00
beforeEach ( ( ) = > {
sinonSandbox = sinon . createSandbox ( ) ;
2021-03-03 20:09:58 +00:00
sinonSandbox . stub ( window . Whisper . events , 'trigger' ) ;
2022-02-11 21:38:52 +00:00
createGroupStub = sinon . stub ( ) ;
2021-02-23 20:34:28 +00:00
} ) ;
afterEach ( ( ) = > {
sinonSandbox . restore ( ) ;
} ) ;
2020-12-04 20:41:40 +00:00
describe ( 'helpers' , ( ) = > {
describe ( 'getConversationCallMode' , ( ) = > {
2021-05-07 22:21:10 +00:00
const fakeConversation : ConversationType = getDefaultConversation ( ) ;
2022-11-19 08:31:18 +00:00
const fakeGroup : ConversationType = getDefaultGroup ( ) ;
2020-12-04 20:41:40 +00:00
it ( "returns CallMode.None if you've left the conversation" , ( ) = > {
assert . strictEqual (
getConversationCallMode ( {
. . . fakeConversation ,
left : true ,
} ) ,
CallMode . None
) ;
} ) ;
it ( "returns CallMode.None if you've blocked the other person" , ( ) = > {
assert . strictEqual (
getConversationCallMode ( {
. . . fakeConversation ,
isBlocked : true ,
} ) ,
CallMode . None
) ;
} ) ;
it ( "returns CallMode.None if you haven't accepted message requests" , ( ) = > {
assert . strictEqual (
getConversationCallMode ( {
. . . fakeConversation ,
acceptedMessageRequest : false ,
} ) ,
CallMode . None
) ;
} ) ;
it ( 'returns CallMode.None if the conversation is Note to Self' , ( ) = > {
assert . strictEqual (
getConversationCallMode ( {
. . . fakeConversation ,
isMe : true ,
} ) ,
CallMode . None
) ;
} ) ;
it ( 'returns CallMode.None for v1 groups' , ( ) = > {
assert . strictEqual (
getConversationCallMode ( {
2022-11-19 08:31:18 +00:00
. . . fakeGroup ,
2020-12-04 20:41:40 +00:00
groupVersion : 1 ,
} ) ,
CallMode . None
) ;
assert . strictEqual (
getConversationCallMode ( {
2022-11-19 08:31:18 +00:00
. . . fakeGroup ,
groupVersion : undefined ,
2020-12-04 20:41:40 +00:00
} ) ,
CallMode . None
) ;
} ) ;
it ( 'returns CallMode.Direct if the conversation is a normal direct conversation' , ( ) = > {
assert . strictEqual (
getConversationCallMode ( fakeConversation ) ,
CallMode . Direct
) ;
} ) ;
it ( 'returns CallMode.Group if the conversation is a v2 group' , ( ) = > {
assert . strictEqual (
getConversationCallMode ( {
2022-11-19 08:31:18 +00:00
. . . fakeGroup ,
2020-12-04 20:41:40 +00:00
groupVersion : 2 ,
} ) ,
CallMode . Group
) ;
} ) ;
} ) ;
2021-01-06 15:41:43 +00:00
describe ( 'updateConversationLookups' , ( ) = > {
it ( 'does not change lookups if no conversations provided' , ( ) = > {
const state = getEmptyState ( ) ;
const result = updateConversationLookups ( undefined , undefined , state ) ;
assert . strictEqual (
state . conversationsByE164 ,
result . conversationsByE164
) ;
assert . strictEqual (
state . conversationsByUuid ,
result . conversationsByUuid
) ;
assert . strictEqual (
state . conversationsByGroupId ,
result . conversationsByGroupId
) ;
} ) ;
it ( 'adds and removes e164-only contact' , ( ) = > {
2021-05-07 22:21:10 +00:00
const removed = getDefaultConversation ( {
id : 'id-removed' ,
2021-01-06 15:41:43 +00:00
e164 : 'e164-removed' ,
2021-05-07 22:21:10 +00:00
uuid : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const state = {
. . . getEmptyState ( ) ,
conversationsByE164 : {
2021-05-07 22:21:10 +00:00
'e164-removed' : removed ,
2021-01-06 15:41:43 +00:00
} ,
} ;
2021-05-07 22:21:10 +00:00
const added = getDefaultConversation ( {
id : 'id-added' ,
2021-01-06 15:41:43 +00:00
e164 : 'e164-added' ,
2021-05-07 22:21:10 +00:00
uuid : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const expected = {
2021-05-07 22:21:10 +00:00
'e164-added' : added ,
2021-01-06 15:41:43 +00:00
} ;
const actual = updateConversationLookups ( added , removed , state ) ;
assert . deepEqual ( actual . conversationsByE164 , expected ) ;
assert . strictEqual (
state . conversationsByUuid ,
actual . conversationsByUuid
) ;
assert . strictEqual (
state . conversationsByGroupId ,
actual . conversationsByGroupId
) ;
} ) ;
it ( 'adds and removes uuid-only contact' , ( ) = > {
2021-10-26 22:59:08 +00:00
const removed = getDefaultConversationWithUuid ( {
2021-05-07 22:21:10 +00:00
id : 'id-removed' ,
e164 : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const state = {
. . . getEmptyState ( ) ,
conversationsByuuid : {
2021-10-26 22:59:08 +00:00
[ removed . uuid ] : removed ,
2021-01-06 15:41:43 +00:00
} ,
} ;
2021-10-26 22:59:08 +00:00
const added = getDefaultConversationWithUuid ( {
2021-05-07 22:21:10 +00:00
id : 'id-added' ,
e164 : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const expected = {
2021-10-26 22:59:08 +00:00
[ added . uuid ] : added ,
2021-01-06 15:41:43 +00:00
} ;
const actual = updateConversationLookups ( added , removed , state ) ;
assert . strictEqual (
state . conversationsByE164 ,
actual . conversationsByE164
) ;
assert . deepEqual ( actual . conversationsByUuid , expected ) ;
assert . strictEqual (
state . conversationsByGroupId ,
actual . conversationsByGroupId
) ;
} ) ;
it ( 'adds and removes groupId-only contact' , ( ) = > {
2021-05-07 22:21:10 +00:00
const removed = getDefaultConversation ( {
id : 'id-removed' ,
2021-01-06 15:41:43 +00:00
groupId : 'groupId-removed' ,
2021-05-07 22:21:10 +00:00
e164 : undefined ,
uuid : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const state = {
. . . getEmptyState ( ) ,
conversationsBygroupId : {
2021-05-07 22:21:10 +00:00
'groupId-removed' : removed ,
2021-01-06 15:41:43 +00:00
} ,
} ;
2021-05-07 22:21:10 +00:00
const added = getDefaultConversation ( {
id : 'id-added' ,
2021-01-06 15:41:43 +00:00
groupId : 'groupId-added' ,
2021-05-07 22:21:10 +00:00
e164 : undefined ,
uuid : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const expected = {
2021-05-07 22:21:10 +00:00
'groupId-added' : added ,
2021-01-06 15:41:43 +00:00
} ;
const actual = updateConversationLookups ( added , removed , state ) ;
assert . strictEqual (
state . conversationsByE164 ,
actual . conversationsByE164
) ;
assert . strictEqual (
state . conversationsByUuid ,
actual . conversationsByUuid
) ;
assert . deepEqual ( actual . conversationsByGroupId , expected ) ;
} ) ;
} ) ;
2020-12-04 20:41:40 +00:00
} ) ;
describe ( 'reducer' , ( ) = > {
const time = Date . now ( ) ;
2021-06-17 17:15:10 +00:00
const previousTime = time - 1 ;
2020-12-04 20:41:40 +00:00
const conversationId = 'conversation-guid-1' ;
const messageId = 'message-guid-1' ;
const messageIdTwo = 'message-guid-2' ;
const messageIdThree = 'message-guid-3' ;
2021-10-26 22:59:08 +00:00
const sourceUuid = UUID . generate ( ) . toString ( ) ;
2020-12-04 20:41:40 +00:00
function getDefaultMessage ( id : string ) : MessageType {
return {
2021-06-17 17:15:10 +00:00
attachments : [ ] ,
2022-05-11 22:41:45 +00:00
conversationId ,
2021-06-17 17:15:10 +00:00
id ,
received_at : previousTime ,
sent_at : previousTime ,
2020-12-04 20:41:40 +00:00
source : 'source' ,
2021-10-26 22:59:08 +00:00
sourceUuid ,
2021-06-17 17:15:10 +00:00
timestamp : previousTime ,
2020-12-04 20:41:40 +00:00
type : 'incoming' as const ,
2021-08-12 18:15:55 +00:00
readStatus : ReadStatus.Read ,
2020-12-04 20:41:40 +00:00
} ;
}
function getDefaultConversationMessage ( ) : ConversationMessageType {
return {
2022-05-11 22:41:45 +00:00
messageChangeCounter : 0 ,
2020-12-04 20:41:40 +00:00
messageIds : [ ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
} ,
scrollToMessageCounter : 0 ,
} ;
}
2022-06-16 19:12:50 +00:00
describe ( 'showConversation' , ( ) = > {
it ( 'selects a conversation id' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
} ;
2023-01-05 23:34:47 +00:00
const dispatch = sinon . spy ( ) ;
showConversation ( { conversationId : 'abc123' } ) (
dispatch ,
getEmptyRootState ,
null
) ;
const action = dispatch . getCall ( 0 ) . args [ 0 ] ;
2022-06-16 19:12:50 +00:00
const nextState = reducer ( state , action ) ;
2021-02-23 20:34:28 +00:00
2022-06-16 19:12:50 +00:00
assert . equal ( nextState . selectedConversationId , 'abc123' ) ;
assert . isUndefined ( nextState . selectedMessage ) ;
2021-02-23 20:34:28 +00:00
} ) ;
2022-06-16 19:12:50 +00:00
it ( 'selects a conversation and a message' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
} ;
2023-01-05 23:34:47 +00:00
const dispatch = sinon . spy ( ) ;
showConversation ( {
2021-02-23 20:34:28 +00:00
conversationId : 'abc123' ,
messageId : 'xyz987' ,
2023-01-05 23:34:47 +00:00
} ) ( dispatch , getEmptyRootState , null ) ;
const action = dispatch . getCall ( 0 ) . args [ 0 ] ;
2022-06-16 19:12:50 +00:00
const nextState = reducer ( state , action ) ;
assert . equal ( nextState . selectedConversationId , 'abc123' ) ;
assert . equal ( nextState . selectedMessage , 'xyz987' ) ;
2021-02-23 20:34:28 +00:00
} ) ;
2022-06-16 19:12:50 +00:00
describe ( 'showConversation switchToAssociatedView=true' , ( ) = > {
let action : SelectedConversationChangedActionType ;
2021-02-23 20:34:28 +00:00
beforeEach ( ( ) = > {
2023-01-05 23:34:47 +00:00
const dispatch = sinon . spy ( ) ;
showConversation ( {
2021-02-23 20:34:28 +00:00
conversationId : 'fake-conversation-id' ,
switchToAssociatedView : true ,
2023-01-05 23:34:47 +00:00
} ) ( dispatch , getEmptyRootState , null ) ;
[ action ] = dispatch . getCall ( 0 ) . args ;
2021-02-23 20:34:28 +00:00
} ) ;
it ( 'shows the inbox if the conversation is not archived' , ( ) = > {
2021-05-07 22:21:10 +00:00
const conversation = getDefaultConversation ( {
id : 'fake-conversation-id' ,
} ) ;
2021-02-23 20:34:28 +00:00
const state = {
. . . getEmptyState ( ) ,
conversationLookup : {
2021-05-07 22:21:10 +00:00
[ conversation . id ] : conversation ,
2021-02-23 20:34:28 +00:00
} ,
} ;
const result = reducer ( state , action ) ;
assert . isUndefined ( result . composer ) ;
assert . isFalse ( result . showArchived ) ;
} ) ;
it ( 'shows the archive if the conversation is archived' , ( ) = > {
2021-05-07 22:21:10 +00:00
const conversation = getDefaultConversation ( {
id : 'fake-conversation-id' ,
isArchived : true ,
} ) ;
2021-02-23 20:34:28 +00:00
const state = {
. . . getEmptyState ( ) ,
conversationLookup : {
2021-05-07 22:21:10 +00:00
[ conversation . id ] : conversation ,
2021-02-23 20:34:28 +00:00
} ,
} ;
const result = reducer ( state , action ) ;
assert . isUndefined ( result . composer ) ;
assert . isTrue ( result . showArchived ) ;
} ) ;
} ) ;
} ) ;
2021-03-03 20:09:58 +00:00
describe ( 'CLEAR_GROUP_CREATION_ERROR' , ( ) = > {
it ( 'clears the group creation error' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
hasError : true as const ,
} ,
} ;
const action = clearGroupCreationError ( ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
result . composer . hasError === false
) ;
} ) ;
} ) ;
2021-10-26 22:59:08 +00:00
describe ( 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP' , ( ) = > {
it ( 'clears the list of invited conversation UUIDs' , ( ) = > {
2021-03-03 20:09:58 +00:00
const state = {
. . . getEmptyState ( ) ,
2021-10-26 22:59:08 +00:00
invitedUuidsForNewlyCreatedGroup : [
UUID . generate ( ) . toString ( ) ,
UUID . generate ( ) . toString ( ) ,
] ,
2021-03-03 20:09:58 +00:00
} ;
2021-10-26 22:59:08 +00:00
const action = clearInvitedUuidsForNewlyCreatedGroup ( ) ;
2021-03-03 20:09:58 +00:00
const result = reducer ( state , action ) ;
2021-10-26 22:59:08 +00:00
assert . isUndefined ( result . invitedUuidsForNewlyCreatedGroup ) ;
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
2021-04-21 16:31:12 +00:00
describe ( 'CLOSE_CONTACT_SPOOFING_REVIEW' , ( ) = > {
it ( 'closes the contact spoofing review modal if it was open' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
contactSpoofingReview : {
2021-06-01 23:30:25 +00:00
type : ContactSpoofingType . DirectConversationWithSameTitle as const ,
2021-04-21 16:31:12 +00:00
safeConversationId : 'abc123' ,
} ,
} ;
const action = closeContactSpoofingReview ( ) ;
const actual = reducer ( state , action ) ;
assert . isUndefined ( actual . contactSpoofingReview ) ;
} ) ;
it ( "does nothing if the modal wasn't already open" , ( ) = > {
const state = getEmptyState ( ) ;
const action = closeContactSpoofingReview ( ) ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual , state ) ;
} ) ;
} ) ;
2021-03-03 20:09:58 +00:00
describe ( 'CLOSE_MAXIMUM_GROUP_SIZE_MODAL' , ( ) = > {
it ( 'closes the maximum group size modal if it was open' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
maximumGroupSizeModalState : OneTimeModalState.Showing ,
} ,
} ;
const action = closeMaximumGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . ChooseGroupMembers &&
result . composer . maximumGroupSizeModalState ===
OneTimeModalState . Shown ,
'Expected the modal to be closed'
) ;
} ) ;
it ( 'does nothing if the maximum group size modal was never shown' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = closeMaximumGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
it ( 'does nothing if the maximum group size modal already closed' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
maximumGroupSizeModalState : OneTimeModalState.Shown ,
} ,
} ;
const action = closeMaximumGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
} ) ;
describe ( 'CLOSE_RECOMMENDED_GROUP_SIZE_MODAL' , ( ) = > {
it ( 'closes the recommended group size modal if it was open' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
recommendedGroupSizeModalState : OneTimeModalState.Showing ,
} ,
} ;
const action = closeRecommendedGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . ChooseGroupMembers &&
result . composer . recommendedGroupSizeModalState ===
OneTimeModalState . Shown ,
'Expected the modal to be closed'
) ;
} ) ;
it ( 'does nothing if the recommended group size modal was never shown' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = closeRecommendedGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
it ( 'does nothing if the recommended group size modal already closed' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
} ,
} ;
const action = closeRecommendedGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
} ) ;
describe ( 'createGroup' , ( ) = > {
const conversationsState = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc123' ] ,
groupName : 'Foo Bar Group' ,
2021-09-24 00:49:05 +00:00
groupAvatar : new Uint8Array ( [ 1 , 2 , 3 ] ) ,
2021-03-03 20:09:58 +00:00
} ,
} ;
it ( 'immediately dispatches a CREATE_GROUP_PENDING action, which puts the composer in a loading state' , ( ) = > {
const dispatch = sinon . spy ( ) ;
createGroup ( ) (
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
sinon . assert . calledOnce ( dispatch ) ;
sinon . assert . calledWith ( dispatch , { type : 'CREATE_GROUP_PENDING' } ) ;
const action = dispatch . getCall ( 0 ) . args [ 0 ] ;
const result = reducer ( conversationsState , action ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
result . composer . isCreating &&
! result . composer . hasError
) ;
} ) ;
it ( 'calls groups.createGroupV2' , async ( ) = > {
2022-02-11 21:38:52 +00:00
await createGroup ( createGroupStub ) (
2021-03-03 20:09:58 +00:00
sinon . spy ( ) ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
sinon . assert . calledOnce ( createGroupStub ) ;
sinon . assert . calledWith ( createGroupStub , {
name : 'Foo Bar Group' ,
2021-09-24 00:49:05 +00:00
avatar : new Uint8Array ( [ 1 , 2 , 3 ] ) ,
2021-08-06 00:17:05 +00:00
avatars : [ ] ,
2021-06-25 23:52:56 +00:00
expireTimer : 0 ,
2021-03-03 20:09:58 +00:00
conversationIds : [ 'abc123' ] ,
} ) ;
} ) ;
2021-03-11 01:54:13 +00:00
it ( "trims the group's title before calling groups.createGroupV2" , async ( ) = > {
2022-02-11 21:38:52 +00:00
await createGroup ( createGroupStub ) (
2021-03-11 01:54:13 +00:00
sinon . spy ( ) ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : {
. . . conversationsState ,
composer : {
. . . conversationsState . composer ,
groupName : ' To Trim \t' ,
} ,
} ,
} ) ,
null
) ;
sinon . assert . calledWith (
createGroupStub ,
sinon . match ( { name : 'To Trim' } )
) ;
} ) ;
2021-03-03 20:09:58 +00:00
it ( 'dispatches a CREATE_GROUP_REJECTED action if group creation fails, which marks the state with an error' , async ( ) = > {
createGroupStub . rejects ( new Error ( 'uh oh' ) ) ;
const dispatch = sinon . spy ( ) ;
2022-02-11 21:38:52 +00:00
const createGroupPromise = createGroup ( createGroupStub ) (
2021-03-03 20:09:58 +00:00
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
const pendingAction = dispatch . getCall ( 0 ) . args [ 0 ] ;
const stateAfterPending = reducer ( conversationsState , pendingAction ) ;
await createGroupPromise ;
sinon . assert . calledTwice ( dispatch ) ;
sinon . assert . calledWith ( dispatch , { type : 'CREATE_GROUP_REJECTED' } ) ;
const rejectedAction = dispatch . getCall ( 1 ) . args [ 0 ] ;
const result = reducer ( stateAfterPending , rejectedAction ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
! result . composer . isCreating &&
result . composer . hasError
) ;
} ) ;
it ( "when rejecting, does nothing to the left pane if it's no longer in this composer state" , async ( ) = > {
createGroupStub . rejects ( new Error ( 'uh oh' ) ) ;
const dispatch = sinon . spy ( ) ;
2022-02-11 21:38:52 +00:00
const createGroupPromise = createGroup ( createGroupStub ) (
2021-03-03 20:09:58 +00:00
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
await createGroupPromise ;
const state = getEmptyState ( ) ;
const rejectedAction = dispatch . getCall ( 1 ) . args [ 0 ] ;
const result = reducer ( state , rejectedAction ) ;
assert . strictEqual ( result , state ) ;
} ) ;
it ( 'dispatches a CREATE_GROUP_FULFILLED event (which updates the newly-created conversation IDs), triggers a showConversation event and switches to the associated conversation on success' , async ( ) = > {
2021-10-26 22:59:08 +00:00
const abc = UUID . fromPrefix ( 'abc' ) . toString ( ) ;
2021-03-03 20:09:58 +00:00
createGroupStub . resolves ( {
id : '9876' ,
get : ( key : string ) = > {
if ( key !== 'pendingMembersV2' ) {
throw new Error ( 'This getter is not set up for this test' ) ;
}
2021-10-26 22:59:08 +00:00
return [ { uuid : abc } ] ;
2021-03-03 20:09:58 +00:00
} ,
} ) ;
const dispatch = sinon . spy ( ) ;
2022-02-11 21:38:52 +00:00
await createGroup ( createGroupStub ) (
2021-03-03 20:09:58 +00:00
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
sinon . assert . calledWith ( dispatch , {
type : 'CREATE_GROUP_FULFILLED' ,
2021-10-26 22:59:08 +00:00
payload : { invitedUuids : [ abc ] } ,
2021-03-03 20:09:58 +00:00
} ) ;
2022-06-16 19:12:50 +00:00
sinon . assert . calledWith ( dispatch , {
type : SELECTED_CONVERSATION_CHANGED ,
payload : {
2023-01-05 00:22:36 +00:00
conversationId : '9876' ,
2022-06-16 19:12:50 +00:00
messageId : undefined ,
switchToAssociatedView : true ,
} ,
} ) ;
2021-03-03 20:09:58 +00:00
const fulfilledAction = dispatch . getCall ( 1 ) . args [ 0 ] ;
const result = reducer ( conversationsState , fulfilledAction ) ;
2021-10-26 22:59:08 +00:00
assert . deepEqual ( result . invitedUuidsForNewlyCreatedGroup , [ abc ] ) ;
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
2022-02-16 18:36:21 +00:00
describe ( 'CONVERSATION_STOPPED_BY_MISSING_VERIFICATION' , ( ) = > {
it ( 'adds to state, removing duplicates' , ( ) = > {
2021-08-31 20:58:39 +00:00
const first = reducer (
getEmptyState ( ) ,
2022-02-16 18:36:21 +00:00
conversationStoppedByMissingVerification ( {
conversationId : 'convo A' ,
2022-11-11 04:10:30 +00:00
untrustedUuids : [ UUID_1 ] ,
2022-02-16 18:36:21 +00:00
} )
2021-08-31 20:58:39 +00:00
) ;
const second = reducer (
first ,
2022-02-16 18:36:21 +00:00
conversationStoppedByMissingVerification ( {
conversationId : 'convo A' ,
2022-11-11 04:10:30 +00:00
untrustedUuids : [ UUID_2 ] ,
2022-02-16 18:36:21 +00:00
} )
2021-08-31 20:58:39 +00:00
) ;
const third = reducer (
second ,
2022-02-16 18:36:21 +00:00
conversationStoppedByMissingVerification ( {
conversationId : 'convo A' ,
2022-11-11 04:10:30 +00:00
untrustedUuids : [ UUID_1 , UUID_3 ] ,
2022-02-16 18:36:21 +00:00
} )
) ;
assert . deepStrictEqual ( third . verificationDataByConversation , {
'convo A' : {
type : ConversationVerificationState . PendingVerification ,
2022-11-11 04:10:30 +00:00
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
2022-02-16 18:36:21 +00:00
} ,
} ) ;
} ) ;
it ( 'stomps on VerificationCancelled state' , ( ) = > {
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : Date.now ( ) ,
} ,
} ,
} ;
const actual = reducer (
state ,
conversationStoppedByMissingVerification ( {
conversationId : 'convo A' ,
2022-11-11 04:10:30 +00:00
untrustedUuids : [ UUID_1 , UUID_2 ] ,
2022-02-16 18:36:21 +00:00
} )
) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , {
'convo A' : {
type : ConversationVerificationState . PendingVerification ,
2022-11-11 04:10:30 +00:00
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
} ,
} ) ;
} ) ;
} ) ;
describe ( 'SHOW_SEND_ANYWAY_DIALOG' , ( ) = > {
it ( 'adds nothing to existing empty state' , ( ) = > {
const state = getEmptyState ( ) ;
const action : ShowSendAnywayDialogActionType = {
type : SHOW_SEND_ANYWAY_DIALOG ,
payload : {
untrustedByConversation : { } ,
promiseUuid : UUID.generate ( ) . toString ( ) ,
source : undefined ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , { } ) ;
} ) ;
it ( 'adds multiple conversations and distribution lists to empty list' , ( ) = > {
const state = getEmptyState ( ) ;
const action : ShowSendAnywayDialogActionType = {
type : SHOW_SEND_ANYWAY_DIALOG ,
payload : {
untrustedByConversation : {
abc : {
uuids : [ UUID_1 , UUID_2 ] ,
byDistributionId : {
abc : {
uuids : [ UUID_1 , UUID_3 ] ,
} ,
def : {
uuids : [ UUID_2 , UUID_4 ] ,
} ,
} ,
} ,
} ,
promiseUuid : UUID.generate ( ) . toString ( ) ,
source : undefined ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , {
abc : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_3 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_2 , UUID_4 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
it ( 'adds and de-dupes in multiple conversations and distribution lists' , ( ) = > {
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
abc : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ UUID_1 ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 ] ,
} ,
} ,
} ,
} ,
} ;
const action : ShowSendAnywayDialogActionType = {
type : SHOW_SEND_ANYWAY_DIALOG ,
payload : {
untrustedByConversation : {
abc : {
uuids : [ UUID_1 , UUID_2 ] ,
byDistributionId : {
abc : {
uuids : [ UUID_1 , UUID_3 ] ,
} ,
def : {
uuids : [ UUID_2 , UUID_4 ] ,
} ,
} ,
} ,
} ,
promiseUuid : UUID.generate ( ) . toString ( ) ,
source : undefined ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , {
abc : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_3 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_2 , UUID_4 ] ,
} ,
} ,
2022-02-16 18:36:21 +00:00
} ,
} ) ;
} ) ;
} ) ;
describe ( 'CANCEL_CONVERSATION_PENDING_VERIFICATION' , ( ) = > {
function getAction (
timestamp : number ,
conversationsState : ConversationsStateType
) : CancelVerificationDataByConversationActionType {
const dispatch = sinon . spy ( ) ;
cancelConversationVerification ( timestamp ) (
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
return dispatch . getCall ( 0 ) . args [ 0 ] ;
}
it ( 'replaces existing PendingVerification state' , ( ) = > {
const now = Date . now ( ) ;
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
'convo A' : {
type : ConversationVerificationState . PendingVerification ,
2022-11-11 04:10:30 +00:00
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
2022-02-16 18:36:21 +00:00
} ,
} ,
} ;
const action = getAction ( now , state ) ;
const actual = reducer ( state , action ) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : now ,
} ,
} ) ;
} ) ;
it ( 'updates timestamp for existing VerificationCancelled state' , ( ) = > {
const now = Date . now ( ) ;
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : now - 1 ,
} ,
} ,
} ;
const action = getAction ( now , state ) ;
const actual = reducer ( state , action ) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : now ,
} ,
} ) ;
} ) ;
it ( 'uses newest timestamp when updating existing VerificationCancelled state' , ( ) = > {
const now = Date . now ( ) ;
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : now ,
} ,
} ,
} ;
const action = getAction ( now , state ) ;
const actual = reducer ( state , action ) ;
assert . deepStrictEqual ( actual . verificationDataByConversation , {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : now ,
} ,
} ) ;
} ) ;
it ( 'does nothing if no existing state' , ( ) = > {
const state : ConversationsStateType = getEmptyState ( ) ;
const action = getAction ( Date . now ( ) , state ) ;
const actual = reducer ( state , action ) ;
assert . strictEqual ( actual , state ) ;
} ) ;
} ) ;
describe ( 'CANCEL_CONVERSATION_PENDING_VERIFICATION' , ( ) = > {
it ( 'removes existing VerificationCancelled state' , ( ) = > {
const now = Date . now ( ) ;
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
'convo A' : {
type : ConversationVerificationState . VerificationCancelled ,
canceledAt : now ,
} ,
} ,
} ;
const actual = reducer (
state ,
clearCancelledConversationVerification ( 'convo A' )
2021-08-31 20:58:39 +00:00
) ;
2022-02-16 18:36:21 +00:00
assert . deepStrictEqual ( actual . verificationDataByConversation , { } ) ;
} ) ;
it ( 'leaves existing PendingVerification state' , ( ) = > {
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
'convo A' : {
type : ConversationVerificationState . PendingVerification ,
2022-11-11 04:10:30 +00:00
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
2022-02-16 18:36:21 +00:00
} ,
} ,
} ;
const actual = reducer (
state ,
clearCancelledConversationVerification ( 'convo A' )
2021-08-31 20:58:39 +00:00
) ;
2022-02-16 18:36:21 +00:00
assert . deepStrictEqual ( actual , state ) ;
} ) ;
it ( 'does nothing with empty state' , ( ) = > {
const state : ConversationsStateType = getEmptyState ( ) ;
const actual = reducer (
state ,
clearCancelledConversationVerification ( 'convo A' )
) ;
assert . deepStrictEqual ( actual , state ) ;
2021-08-31 20:58:39 +00:00
} ) ;
} ) ;
2020-12-04 20:41:40 +00:00
describe ( 'REPAIR_NEWEST_MESSAGE' , ( ) = > {
it ( 'updates newest' , ( ) = > {
const action = repairNewestMessage ( conversationId ) ;
const state : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
2021-06-17 17:15:10 +00:00
sent_at : time ,
2020-12-04 20:41:40 +00:00
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ messageIdThree , messageIdTwo , messageId ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
} ,
} ,
} ,
} ;
const expected : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
2021-06-17 17:15:10 +00:00
sent_at : time ,
2020-12-04 20:41:40 +00:00
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ messageIdThree , messageIdTwo , messageId ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
newest : {
id : messageId ,
received_at : time ,
2021-06-17 17:15:10 +00:00
sent_at : time ,
2020-12-04 20:41:40 +00:00
} ,
} ,
} ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual , expected ) ;
} ) ;
it ( 'clears newest' , ( ) = > {
const action = repairNewestMessage ( conversationId ) ;
const state : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
newest : {
id : messageId ,
received_at : time ,
} ,
} ,
} ,
} ,
} ;
const expected : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ ] ,
metrics : {
newest : undefined ,
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
} ,
} ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual , expected ) ;
} ) ;
it ( 'returns state if conversation not present' , ( ) = > {
const action = repairNewestMessage ( conversationId ) ;
2021-01-06 15:41:43 +00:00
const state : ConversationsStateType = getEmptyState ( ) ;
2020-12-04 20:41:40 +00:00
const actual = reducer ( state , action ) ;
assert . equal ( actual , state ) ;
} ) ;
} ) ;
describe ( 'REPAIR_OLDEST_MESSAGE' , ( ) = > {
it ( 'updates oldest' , ( ) = > {
const action = repairOldestMessage ( conversationId ) ;
const state : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
2021-06-17 17:15:10 +00:00
sent_at : time ,
2020-12-04 20:41:40 +00:00
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ messageId , messageIdTwo , messageIdThree ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
} ,
} ,
} ,
} ;
const expected : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
2021-06-17 17:15:10 +00:00
sent_at : time ,
2020-12-04 20:41:40 +00:00
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ messageId , messageIdTwo , messageIdThree ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
oldest : {
id : messageId ,
received_at : time ,
2021-06-17 17:15:10 +00:00
sent_at : time ,
2020-12-04 20:41:40 +00:00
} ,
} ,
} ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual , expected ) ;
} ) ;
it ( 'clears oldest' , ( ) = > {
const action = repairOldestMessage ( conversationId ) ;
const state : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ ] ,
metrics : {
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
oldest : {
id : messageId ,
received_at : time ,
} ,
} ,
} ,
} ,
} ;
const expected : ConversationsStateType = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-04 20:41:40 +00:00
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
received_at : time ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ ] ,
metrics : {
oldest : undefined ,
2022-04-22 18:35:14 +00:00
totalUnseen : 0 ,
2020-12-04 20:41:40 +00:00
} ,
} ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual , expected ) ;
} ) ;
it ( 'returns state if conversation not present' , ( ) = > {
const action = repairOldestMessage ( conversationId ) ;
2021-01-06 15:41:43 +00:00
const state : ConversationsStateType = getEmptyState ( ) ;
2020-12-04 20:41:40 +00:00
const actual = reducer ( state , action ) ;
assert . equal ( actual , state ) ;
} ) ;
} ) ;
2021-01-29 22:16:48 +00:00
2021-06-01 23:30:25 +00:00
describe ( 'REVIEW_GROUP_MEMBER_NAME_COLLISION' , ( ) = > {
it ( 'starts reviewing a group member name collision' , ( ) = > {
const state = getEmptyState ( ) ;
const action = reviewGroupMemberNameCollision ( 'abc123' ) ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . contactSpoofingReview , {
type : ContactSpoofingType . MultipleGroupMembersWithSameTitle as const ,
groupConversationId : 'abc123' ,
} ) ;
} ) ;
} ) ;
2021-04-21 16:31:12 +00:00
describe ( 'REVIEW_MESSAGE_REQUEST_NAME_COLLISION' , ( ) = > {
it ( 'starts reviewing a message request name collision' , ( ) = > {
const state = getEmptyState ( ) ;
const action = reviewMessageRequestNameCollision ( {
safeConversationId : 'def' ,
} ) ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . contactSpoofingReview , {
2021-06-01 23:30:25 +00:00
type : ContactSpoofingType . DirectConversationWithSameTitle as const ,
2021-04-21 16:31:12 +00:00
safeConversationId : 'def' ,
} ) ;
} ) ;
} ) ;
2021-03-03 20:09:58 +00:00
describe ( 'SET_COMPOSE_GROUP_AVATAR' , ( ) = > {
it ( "can clear the composer's group avatar" , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultSetGroupMetadataComposerState ,
2021-09-24 00:49:05 +00:00
groupAvatar : new Uint8Array ( 2 ) ,
2021-03-03 20:09:58 +00:00
} ,
} ;
const action = setComposeGroupAvatar ( undefined ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
result . composer . groupAvatar === undefined
) ;
} ) ;
it ( "can set the composer's group avatar" , ( ) = > {
2021-09-24 00:49:05 +00:00
const avatar = new Uint8Array ( [ 1 , 2 , 3 ] ) ;
2021-03-03 20:09:58 +00:00
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = setComposeGroupAvatar ( avatar ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
result . composer . groupAvatar === avatar
) ;
} ) ;
} ) ;
describe ( 'SET_COMPOSE_GROUP_NAME' , ( ) = > {
it ( "can set the composer's group name" , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = setComposeGroupName ( 'bing bong' ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
result . composer . groupName === 'bing bong'
) ;
} ) ;
} ) ;
2021-02-23 20:34:28 +00:00
describe ( 'SET_COMPOSE_SEARCH_TERM' , ( ) = > {
it ( 'updates the contact search term' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultStartDirectConversationComposerState ,
2021-02-23 20:34:28 +00:00
} ;
2021-11-12 01:17:29 +00:00
const result = reducer ( state , setComposeSearchTerm ( 'foo bar' ) ) ;
2021-02-23 20:34:28 +00:00
2021-03-03 20:09:58 +00:00
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultStartDirectConversationComposerState ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
2021-03-03 20:09:58 +00:00
} ) ;
2021-02-23 20:34:28 +00:00
} ) ;
} ) ;
2022-05-10 20:19:58 +00:00
describe ( 'DISCARD_MESSAGES' , ( ) = > {
const startState : ConversationsStateType = {
. . . getEmptyState ( ) ,
messagesLookup : {
[ messageId ] : getDefaultMessage ( messageId ) ,
[ messageIdTwo ] : getDefaultMessage ( messageIdTwo ) ,
[ messageIdThree ] : getDefaultMessage ( messageIdThree ) ,
} ,
messagesByConversation : {
[ conversationId ] : {
2022-05-11 22:41:45 +00:00
messageChangeCounter : 0 ,
2022-05-10 20:19:58 +00:00
metrics : {
totalUnseen : 0 ,
} ,
scrollToMessageCounter : 0 ,
messageIds : [ messageId , messageIdTwo , messageIdThree ] ,
} ,
} ,
} ;
it ( 'eliminates older messages' , ( ) = > {
const toDiscard = {
conversationId ,
numberToKeepAtBottom : 2 ,
} ;
const state = reducer ( startState , discardMessages ( toDiscard ) ) ;
assert . deepEqual (
state . messagesByConversation [ conversationId ] ? . messageIds ,
[ messageIdTwo , messageIdThree ]
) ;
} ) ;
it ( 'eliminates newer messages' , ( ) = > {
const toDiscard = {
conversationId ,
numberToKeepAtTop : 2 ,
} ;
const state = reducer ( startState , discardMessages ( toDiscard ) ) ;
assert . deepEqual (
state . messagesByConversation [ conversationId ] ? . messageIds ,
[ messageId , messageIdTwo ]
) ;
} ) ;
} ) ;
2021-01-29 22:16:48 +00:00
describe ( 'SET_PRE_JOIN_CONVERSATION' , ( ) = > {
const startState = {
. . . getEmptyState ( ) ,
} ;
it ( 'starts with empty value' , ( ) = > {
assert . isUndefined ( startState . preJoinConversation ) ;
} ) ;
it ( 'sets value as provided' , ( ) = > {
const preJoinConversation = {
title : 'Pre-join group!' ,
memberCount : 4 ,
approvalRequired : false ,
} ;
const stateWithData = reducer (
startState ,
setPreJoinConversation ( preJoinConversation )
) ;
assert . deepEqual (
stateWithData . preJoinConversation ,
preJoinConversation
) ;
const resetState = reducer (
stateWithData ,
setPreJoinConversation ( undefined )
) ;
assert . isUndefined ( resetState . preJoinConversation ) ;
} ) ;
} ) ;
2021-02-23 20:34:28 +00:00
2022-05-11 22:41:45 +00:00
describe ( 'MESSAGE_CHANGED' , ( ) = > {
const startState : ConversationsStateType = {
. . . getEmptyState ( ) ,
conversationLookup : {
[ conversationId ] : {
. . . getDefaultConversation ( ) ,
id : conversationId ,
groupVersion : 2 ,
groupId : 'dGhpc2lzYWdyb3VwaWR0aGlzaXNhZ3JvdXBpZHRoaXM=' ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
messageChangeCounter : 0 ,
messageIds : [ messageId , messageIdTwo , messageIdThree ] ,
metrics : {
totalUnseen : 0 ,
} ,
scrollToMessageCounter : 0 ,
} ,
} ,
messagesLookup : {
[ messageId ] : {
. . . getDefaultMessage ( messageId ) ,
displayLimit : undefined ,
} ,
[ messageIdTwo ] : {
. . . getDefaultMessage ( messageIdTwo ) ,
displayLimit : undefined ,
} ,
[ messageIdThree ] : {
. . . getDefaultMessage ( messageIdThree ) ,
displayLimit : undefined ,
} ,
} ,
} ;
const changedMessage = {
. . . getDefaultMessage ( messageId ) ,
body : 'changed' ,
displayLimit : undefined ,
} ;
it ( 'updates message data' , ( ) = > {
const state = reducer (
startState ,
messageChanged ( messageId , conversationId , changedMessage )
) ;
assert . deepEqual ( state . messagesLookup [ messageId ] , changedMessage ) ;
assert . strictEqual (
state . messagesByConversation [ conversationId ] ? . messageChangeCounter ,
0
) ;
} ) ;
it ( 'does not update lookup if it is a story reply' , ( ) = > {
const state = reducer (
startState ,
messageChanged ( messageId , conversationId , {
. . . changedMessage ,
storyId : 'story-id' ,
} )
) ;
assert . deepEqual (
state . messagesLookup [ messageId ] ,
startState . messagesLookup [ messageId ]
) ;
assert . strictEqual (
state . messagesByConversation [ conversationId ] ? . messageChangeCounter ,
0
) ;
} ) ;
it ( 'increments message change counter if new message has reactions' , ( ) = > {
const changedMessageWithReaction : MessageType = {
. . . changedMessage ,
reactions : [
{
2023-01-23 20:42:40 +00:00
emoji : '✨' ,
2022-05-11 22:41:45 +00:00
fromId : 'some-other-id' ,
timestamp : 2222 ,
targetTimestamp : 1111 ,
targetAuthorUuid : 'author-uuid' ,
} ,
] ,
} ;
const state = reducer (
startState ,
messageChanged ( messageId , conversationId , changedMessageWithReaction )
) ;
assert . deepEqual (
state . messagesLookup [ messageId ] ,
changedMessageWithReaction
) ;
assert . strictEqual (
state . messagesByConversation [ conversationId ] ? . messageChangeCounter ,
1
) ;
} ) ;
it ( 'does not increment message change counter if only old message had reactions' , ( ) = > {
const updatedStartState = {
. . . startState ,
messagesLookup : {
[ messageId ] : {
. . . startState . messagesLookup [ messageId ] ,
reactions : [
{
2023-01-23 20:42:40 +00:00
emoji : '✨' ,
2022-05-11 22:41:45 +00:00
fromId : 'some-other-id' ,
timestamp : 2222 ,
targetTimestamp : 1111 ,
targetAuthorUuid : 'author-uuid' ,
} ,
] ,
} ,
} ,
} ;
const state = reducer (
updatedStartState ,
messageChanged ( messageId , conversationId , changedMessage )
) ;
assert . deepEqual ( state . messagesLookup [ messageId ] , changedMessage ) ;
assert . strictEqual (
state . messagesByConversation [ conversationId ] ? . messageChangeCounter ,
0
) ;
} ) ;
} ) ;
2021-02-23 20:34:28 +00:00
describe ( 'SHOW_ARCHIVED_CONVERSATIONS' , ( ) = > {
it ( 'is a no-op when already at the archive' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
showArchived : true ,
} ;
const action = showArchivedConversations ( ) ;
const result = reducer ( state , action ) ;
assert . isTrue ( result . showArchived ) ;
assert . isUndefined ( result . composer ) ;
} ) ;
it ( 'switches from the inbox to the archive' , ( ) = > {
const state = getEmptyState ( ) ;
const action = showArchivedConversations ( ) ;
const result = reducer ( state , action ) ;
assert . isTrue ( result . showArchived ) ;
assert . isUndefined ( result . composer ) ;
} ) ;
it ( 'switches from the composer to the archive' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultStartDirectConversationComposerState ,
2021-02-23 20:34:28 +00:00
} ;
const action = showArchivedConversations ( ) ;
const result = reducer ( state , action ) ;
assert . isTrue ( result . showArchived ) ;
assert . isUndefined ( result . composer ) ;
} ) ;
} ) ;
describe ( 'SHOW_INBOX' , ( ) = > {
it ( 'is a no-op when already at the inbox' , ( ) = > {
const state = getEmptyState ( ) ;
const action = showInbox ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . isUndefined ( result . composer ) ;
} ) ;
it ( 'switches from the archive to the inbox' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
showArchived : true ,
} ;
const action = showInbox ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . isUndefined ( result . composer ) ;
} ) ;
it ( 'switches from the composer to the inbox' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultStartDirectConversationComposerState ,
2021-02-23 20:34:28 +00:00
} ;
const action = showInbox ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . isUndefined ( result . composer ) ;
} ) ;
} ) ;
describe ( 'START_COMPOSING' , ( ) = > {
2021-03-03 20:09:58 +00:00
it ( 'does nothing if on the first step of the composer' , ( ) = > {
2021-02-23 20:34:28 +00:00
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultStartDirectConversationComposerState ,
2021-02-23 20:34:28 +00:00
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
2021-08-16 14:33:27 +00:00
assert . deepEqual (
result . composer ,
defaultStartDirectConversationComposerState
) ;
2021-03-03 20:09:58 +00:00
} ) ;
it ( 'if on the second step of the composer, goes back to the first step, clearing the search term' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-04-20 23:16:49 +00:00
searchTerm : 'to be cleared' ,
2021-03-03 20:09:58 +00:00
} ,
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
2021-08-16 14:33:27 +00:00
assert . deepEqual (
result . composer ,
defaultStartDirectConversationComposerState
) ;
2021-03-03 20:09:58 +00:00
} ) ;
it ( 'if on the third step of the composer, goes back to the first step, clearing everything' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
2021-08-16 14:33:27 +00:00
assert . deepEqual (
result . composer ,
defaultStartDirectConversationComposerState
) ;
2021-02-23 20:34:28 +00:00
} ) ;
it ( 'switches from the inbox to the composer' , ( ) = > {
const state = getEmptyState ( ) ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
2021-08-16 14:33:27 +00:00
assert . deepEqual (
result . composer ,
defaultStartDirectConversationComposerState
) ;
2021-02-23 20:34:28 +00:00
} ) ;
it ( 'switches from the archive to the inbox' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
showArchived : true ,
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
2021-08-16 14:33:27 +00:00
assert . deepEqual (
result . composer ,
defaultStartDirectConversationComposerState
) ;
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
describe ( 'SHOW_CHOOSE_GROUP_MEMBERS' , ( ) = > {
it ( 'switches to the second step of the composer if on the first step' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultStartDirectConversationComposerState ,
2021-04-20 23:16:49 +00:00
searchTerm : 'to be cleared' ,
2021-03-03 20:09:58 +00:00
} ,
} ;
const action = showChooseGroupMembers ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-08-06 00:17:05 +00:00
userAvatarData : getDefaultAvatars ( true ) ,
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
it ( 'does nothing if already on the second step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = showChooseGroupMembers ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
it ( 'returns to the second step if on the third step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
groupName : 'Foo Bar Group' ,
2021-09-24 00:49:05 +00:00
groupAvatar : new Uint8Array ( [ 4 , 2 ] ) ,
2021-03-03 20:09:58 +00:00
} ,
} ;
const action = showChooseGroupMembers ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
groupName : 'Foo Bar Group' ,
2021-09-24 00:49:05 +00:00
groupAvatar : new Uint8Array ( [ 4 , 2 ] ) ,
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
it ( 'switches from the inbox to the second step of the composer' , ( ) = > {
const state = getEmptyState ( ) ;
const action = showChooseGroupMembers ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-08-06 00:17:05 +00:00
userAvatarData : getDefaultAvatars ( true ) ,
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
it ( 'switches from the archive to the second step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
showArchived : true ,
} ;
const action = showChooseGroupMembers ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-08-06 00:17:05 +00:00
userAvatarData : getDefaultAvatars ( true ) ,
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
} ) ;
describe ( 'START_SETTING_GROUP_METADATA' , ( ) = > {
it ( 'moves from the second to the third step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
} ,
} ;
const action = startSettingGroupMetadata ( ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
} ) ;
} ) ;
it ( 'maintains state when going from the second to third steps of the composer, if the second step already had some data (likely from a previous visit)' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
groupName : 'Foo Bar Group' ,
2021-09-24 00:49:05 +00:00
groupAvatar : new Uint8Array ( [ 6 , 9 ] ) ,
2021-03-03 20:09:58 +00:00
} ,
} ;
const action = startSettingGroupMetadata ( ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
groupName : 'Foo Bar Group' ,
2021-09-24 00:49:05 +00:00
groupAvatar : new Uint8Array ( [ 6 , 9 ] ) ,
2021-03-03 20:09:58 +00:00
} ) ;
} ) ;
it ( 'does nothing if already on the third step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultSetGroupMetadataComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = startSettingGroupMetadata ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
} ) ;
describe ( 'TOGGLE_CONVERSATION_IN_CHOOSE_MEMBERS' , ( ) = > {
function getAction (
id : string ,
conversationsState : ConversationsStateType
) : ToggleConversationInChooseMembersActionType {
const dispatch = sinon . spy ( ) ;
toggleConversationInChooseMembers ( id ) (
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
return dispatch . getCall ( 0 ) . args [ 0 ] ;
}
2022-02-11 21:38:52 +00:00
beforeEach ( async ( ) = > {
await updateRemoteConfig ( [
{ name : 'global.groupsv2.maxGroupSize' , value : '22' , enabled : true } ,
{
name : 'global.groupsv2.groupSizeHardLimit' ,
value : '33' ,
enabled : true ,
} ,
] ) ;
2021-03-03 20:09:58 +00:00
} ) ;
it ( 'adds conversation IDs to the list' , ( ) = > {
const zero = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const one = reducer ( zero , getAction ( 'abc' , zero ) ) ;
const two = reducer ( one , getAction ( 'def' , one ) ) ;
assert . deepEqual ( two . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
} ) ;
} ) ;
it ( 'removes conversation IDs from the list' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
} ,
} ;
const action = getAction ( 'abc' , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'def' ] ,
} ) ;
} ) ;
it ( 'shows the recommended group size modal when first crossing the maximum recommended group size' , ( ) = > {
const oldSelectedConversationIds = times ( 21 , ( ) = > uuid ( ) ) ;
const newUuid = uuid ( ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
recommendedGroupSizeModalState : OneTimeModalState.Showing ,
} ) ;
} ) ;
it ( "doesn't show the recommended group size modal twice" , ( ) = > {
const oldSelectedConversationIds = times ( 21 , ( ) = > uuid ( ) ) ;
const newUuid = uuid ( ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
} ) ;
} ) ;
2022-02-11 21:38:52 +00:00
it ( 'defaults the maximum recommended size to 151' , async ( ) = > {
for ( const value of [ null , 'xyz' ] ) {
// eslint-disable-next-line no-await-in-loop
await updateRemoteConfig ( [
{
name : 'global.groupsv2.maxGroupSize' ,
value ,
enabled : true ,
} ,
{
name : 'global.groupsv2.groupSizeHardLimit' ,
value : '33' ,
enabled : true ,
} ,
] ) ;
2021-03-03 20:09:58 +00:00
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = getAction ( uuid ( ) , state ) ;
assert . strictEqual ( action . payload . maxRecommendedGroupSize , 151 ) ;
2022-02-11 21:38:52 +00:00
}
2021-03-03 20:09:58 +00:00
} ) ;
it ( 'shows the maximum group size modal when first reaching the maximum group size' , ( ) = > {
const oldSelectedConversationIds = times ( 31 , ( ) = > uuid ( ) ) ;
const newUuid = uuid ( ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.Showing ,
} ) ;
} ) ;
it ( "doesn't show the maximum group size modal twice" , ( ) = > {
const oldSelectedConversationIds = times ( 31 , ( ) = > uuid ( ) ) ;
const newUuid = uuid ( ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.Shown ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.Shown ,
} ) ;
} ) ;
it ( 'cannot select more than the maximum number of conversations' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
2021-08-16 14:33:27 +00:00
. . . defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : times ( 1000 , ( ) = > uuid ( ) ) ,
} ,
} ;
const action = getAction ( uuid ( ) , state ) ;
const result = reducer ( state , action ) ;
2021-03-11 21:29:31 +00:00
assert . deepEqual ( result , state ) ;
2021-03-03 20:09:58 +00:00
} ) ;
2022-02-11 21:38:52 +00:00
it ( 'defaults the maximum group size to 1001 if the recommended maximum is smaller' , async ( ) = > {
for ( const value of [ null , 'xyz' ] ) {
// eslint-disable-next-line no-await-in-loop
await updateRemoteConfig ( [
{ name : 'global.groupsv2.maxGroupSize' , value : '2' , enabled : true } ,
{
name : 'global.groupsv2.groupSizeHardLimit' ,
value ,
enabled : true ,
} ,
] ) ;
2021-03-03 20:09:58 +00:00
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = getAction ( uuid ( ) , state ) ;
assert . strictEqual ( action . payload . maxGroupSize , 1001 ) ;
2022-02-11 21:38:52 +00:00
}
2021-03-03 20:09:58 +00:00
} ) ;
2022-02-11 21:38:52 +00:00
it ( 'defaults the maximum group size to (recommended maximum + 1) if the recommended maximum is more than 1001' , async ( ) = > {
await updateRemoteConfig ( [
{
name : 'global.groupsv2.maxGroupSize' ,
value : '1234' ,
enabled : true ,
} ,
{
name : 'global.groupsv2.groupSizeHardLimit' ,
value : '2' ,
enabled : true ,
} ,
] ) ;
2021-03-03 20:09:58 +00:00
const state = {
. . . getEmptyState ( ) ,
2021-08-16 14:33:27 +00:00
composer : defaultChooseGroupMembersComposerState ,
2021-03-03 20:09:58 +00:00
} ;
const action = getAction ( uuid ( ) , state ) ;
assert . strictEqual ( action . payload . maxGroupSize , 1235 ) ;
2021-02-23 20:34:28 +00:00
} ) ;
} ) ;
2021-05-28 16:15:17 +00:00
2022-11-11 04:10:30 +00:00
describe ( 'COLORS_CHANGED' , ( ) = > {
const abc = getDefaultConversationWithUuid ( {
id : 'abc' ,
conversationColor : 'wintergreen' ,
} ) ;
const def = getDefaultConversationWithUuid ( {
id : 'def' ,
conversationColor : 'infrared' ,
} ) ;
const ghi = getDefaultConversation ( {
id : 'ghi' ,
e164 : 'ghi' ,
conversationColor : 'ember' ,
} ) ;
const jkl = getDefaultConversation ( {
id : 'jkl' ,
groupId : 'jkl' ,
conversationColor : 'plum' ,
} ) ;
const getState = ( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : {
. . . getEmptyState ( ) ,
conversationLookup : {
abc ,
def ,
ghi ,
jkl ,
} ,
conversationsByUuid : {
abc ,
def ,
} ,
conversationsByE164 : {
ghi ,
} ,
conversationsByGroupId : {
jkl ,
} ,
} ,
} ) ;
it ( 'resetAllChatColors' , async ( ) = > {
const dispatch = sinon . spy ( ) ;
await resetAllChatColors ( ) ( dispatch , getState , null ) ;
const [ action ] = dispatch . getCall ( 0 ) . args ;
const nextState = reducer ( getState ( ) . conversations , action ) ;
sinon . assert . calledOnce ( dispatch ) ;
assert . isUndefined ( nextState . conversationLookup . abc . conversationColor ) ;
assert . isUndefined ( nextState . conversationLookup . def . conversationColor ) ;
assert . isUndefined ( nextState . conversationLookup . ghi . conversationColor ) ;
assert . isUndefined ( nextState . conversationLookup . jkl . conversationColor ) ;
assert . isUndefined (
nextState . conversationsByUuid [ abc . uuid ] . conversationColor
) ;
assert . isUndefined (
nextState . conversationsByUuid [ def . uuid ] . conversationColor
) ;
assert . isUndefined ( nextState . conversationsByE164 . ghi . conversationColor ) ;
assert . isUndefined (
nextState . conversationsByGroupId . jkl . conversationColor
) ;
2022-12-21 18:41:48 +00:00
await window . storage . remove ( 'defaultConversationColor' ) ;
2022-11-11 04:10:30 +00:00
} ) ;
2021-05-28 16:15:17 +00:00
} ) ;
2022-11-11 04:10:30 +00:00
// When distribution lists change
describe ( 'VIEWERS_CHANGED' , ( ) = > {
const state : ConversationsStateType = {
2021-05-28 16:15:17 +00:00
. . . getEmptyState ( ) ,
2022-11-11 04:10:30 +00:00
verificationDataByConversation : {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
2021-05-28 16:15:17 +00:00
} ,
2022-11-11 04:10:30 +00:00
} ;
it ( 'removes uuids now missing from the list' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : VIEWERS_CHANGED ,
payload : {
listId : 'abc' ,
memberUuids : [ UUID_1 , UUID_2 ] ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
it ( 'removes now-empty list' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : VIEWERS_CHANGED ,
payload : {
listId : 'abc' ,
memberUuids : [ ] ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
} ) ;
describe ( 'HIDE_MY_STORIES_FROM' , ( ) = > {
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
[ MY_STORY_ID ] : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
2021-05-28 16:15:17 +00:00
} ,
2022-11-11 04:10:30 +00:00
} ;
it ( 'removes now hidden uuids' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : HIDE_MY_STORIES_FROM ,
payload : [ UUID_1 , UUID_2 ] ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
[ MY_STORY_ID ] : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
it ( 'eliminates list if all items removed' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : HIDE_MY_STORIES_FROM ,
payload : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
} ) ;
describe ( 'DELETE_LIST' , ( ) = > {
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ,
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
2021-05-28 16:15:17 +00:00
} ,
2022-11-11 04:10:30 +00:00
} ;
it ( 'eliminates deleted list entirely' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : DELETE_LIST ,
payload : {
deletedAtTimestamp : Date.now ( ) ,
listId : 'abc' ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
def : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
it ( 'deletes parent conversation if no other lists, no top-level uuids' , async ( ) = > {
const starting : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ,
} ,
} ,
} ,
} ;
const action : StoryDistributionListsActionType = {
type : DELETE_LIST ,
payload : {
deletedAtTimestamp : Date.now ( ) ,
listId : 'abc' ,
} ,
} ;
const actual = reducer ( starting , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , { } ) ;
} ) ;
it ( 'deletes byDistributionId if top-level list does have uuids' , async ( ) = > {
const starting : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ UUID_1 ] ,
byDistributionId : {
abc : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ,
} ,
} ,
} ,
} ;
const action : StoryDistributionListsActionType = {
type : DELETE_LIST ,
payload : {
deletedAtTimestamp : Date.now ( ) ,
listId : 'abc' ,
} ,
} ;
const actual = reducer ( starting , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ UUID_1 ] ,
} ,
} ) ;
} ) ;
2021-05-28 16:15:17 +00:00
} ) ;
2022-11-11 04:10:30 +00:00
describe ( 'MODIFY_LIST' , ( ) = > {
const state : ConversationsStateType = {
. . . getEmptyState ( ) ,
verificationDataByConversation : {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
[ UUID_1 ] : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 , UUID_3 ] ,
} ,
[ UUID_2 ] : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ,
} ;
it ( 'removes toRemove uuids for isBlockList = false' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : MODIFY_LIST ,
payload : {
id : UUID_1 ,
name : 'list1' ,
allowsReplies : true ,
isBlockList : false ,
membersToAdd : [ UUID_2 , UUID_4 ] ,
membersToRemove : [ UUID_3 ] ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
[ UUID_1 ] : {
uuidsNeedingVerification : [ UUID_1 , UUID_2 ] ,
} ,
[ UUID_2 ] : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
2021-05-28 16:15:17 +00:00
2022-11-11 04:10:30 +00:00
it ( 'removes toAdd uuids for isBlocklist = true' , async ( ) = > {
const action : StoryDistributionListsActionType = {
type : MODIFY_LIST ,
payload : {
id : UUID_1 ,
name : 'list1' ,
allowsReplies : true ,
isBlockList : true ,
membersToAdd : [ UUID_2 , UUID_1 ] ,
membersToRemove : [ UUID_3 ] ,
} ,
} ;
const actual = reducer ( state , action ) ;
assert . deepEqual ( actual . verificationDataByConversation , {
convo1 : {
type : ConversationVerificationState . PendingVerification ,
uuidsNeedingVerification : [ ] ,
byDistributionId : {
[ UUID_1 ] : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
[ UUID_2 ] : {
uuidsNeedingVerification : [ UUID_3 ] ,
} ,
} ,
} ,
} ) ;
} ) ;
2021-05-28 16:15:17 +00:00
} ) ;
} ) ;
2020-12-04 20:41:40 +00:00
} ) ;