2021-02-23 20:34:28 +00:00
// Copyright 2020-2021 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' ;
2020-12-07 20:43:19 +00:00
import { set } from 'lodash/fp' ;
2021-02-23 20:34:28 +00:00
import { reducer as rootReducer } from '../../../state/reducer' ;
import { noopAction } from '../../../state/ducks/noop' ;
2020-12-04 20:41:40 +00:00
import {
actions ,
2021-03-03 20:09:58 +00:00
OneTimeModalState ,
ComposerStep ,
2020-12-04 20:41:40 +00:00
ConversationMessageType ,
ConversationType ,
2021-03-03 20:09:58 +00:00
ConversationsStateType ,
MessageType ,
SwitchToAssociatedViewActionType ,
ToggleConversationInChooseMembersActionType ,
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' ;
import { CallMode } from '../../../types/Calling' ;
2021-03-03 20:09:58 +00:00
import * as groups from '../../../groups' ;
2021-05-07 22:21:10 +00:00
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation' ;
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
cantAddContactToGroup ,
clearGroupCreationError ,
clearInvitedConversationsForNewlyCreatedGroup ,
closeCantAddContactToGroupModal ,
2021-04-21 16:31:12 +00:00
closeContactSpoofingReview ,
2021-03-03 20:09:58 +00:00
closeMaximumGroupSizeModal ,
closeRecommendedGroupSizeModal ,
createGroup ,
2020-12-07 20:43:19 +00:00
messageSizeChanged ,
2021-02-23 20:34:28 +00:00
openConversationInternal ,
2020-12-07 20:43:19 +00:00
repairNewestMessage ,
repairOldestMessage ,
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 ,
showInbox ,
startComposing ,
2021-03-03 20:09:58 +00:00
showChooseGroupMembers ,
startSettingGroupMetadata ,
2021-04-21 16:31:12 +00:00
reviewMessageRequestNameCollision ,
2021-03-03 20:09:58 +00:00
toggleConversationInChooseMembers ,
2020-12-07 20:43:19 +00:00
} = actions ;
2020-12-04 20:41:40 +00:00
describe ( 'both/state/ducks/conversations' , ( ) = > {
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' ) ;
createGroupStub = sinonSandbox . stub ( groups , 'createGroupV2' ) ;
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 ( ) ;
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 ( {
. . . fakeConversation ,
type : 'group' ,
groupVersion : 1 ,
2021-05-07 22:21:10 +00:00
sharedGroupNames : [ ] ,
2020-12-04 20:41:40 +00:00
} ) ,
CallMode . None
) ;
assert . strictEqual (
getConversationCallMode ( {
. . . fakeConversation ,
type : 'group' ,
2021-05-07 22:21:10 +00:00
sharedGroupNames : [ ] ,
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 ( {
. . . fakeConversation ,
type : 'group' ,
groupVersion : 2 ,
2021-05-07 22:21:10 +00:00
sharedGroupNames : [ ] ,
2020-12-04 20:41:40 +00:00
} ) ,
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-05-07 22:21:10 +00:00
const removed = getDefaultConversation ( {
id : 'id-removed' ,
2021-01-06 15:41:43 +00:00
uuid : 'uuid-removed' ,
2021-05-07 22:21:10 +00:00
e164 : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const state = {
. . . getEmptyState ( ) ,
conversationsByuuid : {
2021-05-07 22:21:10 +00:00
'uuid-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
uuid : 'uuid-added' ,
2021-05-07 22:21:10 +00:00
e164 : undefined ,
} ) ;
2021-01-06 15:41:43 +00:00
const expected = {
2021-05-07 22:21:10 +00:00
'uuid-added' : 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 ( ) ;
const conversationId = 'conversation-guid-1' ;
const messageId = 'message-guid-1' ;
const messageIdTwo = 'message-guid-2' ;
const messageIdThree = 'message-guid-3' ;
function getDefaultMessage ( id : string ) : MessageType {
return {
id ,
conversationId : 'conversationId' ,
source : 'source' ,
2021-01-06 15:41:43 +00:00
sourceUuid : 'sourceUuid' ,
2020-12-04 20:41:40 +00:00
type : 'incoming' as const ,
received_at : Date.now ( ) ,
attachments : [ ] ,
sticker : { } ,
unread : false ,
} ;
}
function getDefaultConversationMessage ( ) : ConversationMessageType {
return {
heightChangeMessageIds : [ ] ,
isLoadingMessages : false ,
messageIds : [ ] ,
metrics : {
totalUnread : 0 ,
} ,
resetCounter : 0 ,
scrollToMessageCounter : 0 ,
} ;
}
2021-02-23 20:34:28 +00:00
describe ( 'openConversationInternal' , ( ) = > {
it ( "returns a thunk that triggers a 'showConversation' event when passed a conversation ID" , ( ) = > {
const dispatch = sinon . spy ( ) ;
openConversationInternal ( { conversationId : 'abc123' } ) (
dispatch ,
getEmptyRootState ,
null
) ;
sinon . assert . calledOnce (
window . Whisper . events . trigger as sinon . SinonSpy
) ;
sinon . assert . calledWith (
window . Whisper . events . trigger as sinon . SinonSpy ,
'showConversation' ,
'abc123' ,
undefined
) ;
} ) ;
it ( "returns a thunk that triggers a 'showConversation' event when passed a conversation ID and message ID" , ( ) = > {
const dispatch = sinon . spy ( ) ;
openConversationInternal ( {
conversationId : 'abc123' ,
messageId : 'xyz987' ,
} ) ( dispatch , getEmptyRootState , null ) ;
sinon . assert . calledOnce (
window . Whisper . events . trigger as sinon . SinonSpy
) ;
sinon . assert . calledWith (
window . Whisper . events . trigger as sinon . SinonSpy ,
'showConversation' ,
'abc123' ,
'xyz987'
) ;
} ) ;
it ( "returns a thunk that doesn't dispatch any actions by default" , ( ) = > {
const dispatch = sinon . spy ( ) ;
openConversationInternal ( { conversationId : 'abc123' } ) (
dispatch ,
getEmptyRootState ,
null
) ;
sinon . assert . notCalled ( dispatch ) ;
} ) ;
it ( 'dispatches a SWITCH_TO_ASSOCIATED_VIEW action if called with a flag' , ( ) = > {
const dispatch = sinon . spy ( ) ;
openConversationInternal ( {
conversationId : 'abc123' ,
switchToAssociatedView : true ,
} ) ( dispatch , getEmptyRootState , null ) ;
sinon . assert . calledWith ( dispatch , {
type : 'SWITCH_TO_ASSOCIATED_VIEW' ,
payload : { conversationId : 'abc123' } ,
} ) ;
} ) ;
describe ( 'SWITCH_TO_ASSOCIATED_VIEW' , ( ) = > {
let action : SwitchToAssociatedViewActionType ;
beforeEach ( ( ) = > {
const dispatch = sinon . spy ( ) ;
openConversationInternal ( {
conversationId : 'fake-conversation-id' ,
switchToAssociatedView : true ,
} ) ( dispatch , getEmptyRootState , null ) ;
[ action ] = dispatch . getCall ( 0 ) . args ;
} ) ;
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 ) ;
} ) ;
it ( 'does nothing if the conversation is not found' , ( ) = > {
const state = getEmptyState ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
} ) ;
} ) ;
2021-03-03 20:09:58 +00:00
describe ( 'CANT_ADD_CONTACT_TO_GROUP' , ( ) = > {
it ( 'marks the conversation ID as "cannot add"' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
cantAddContactIdForModal : undefined ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
const action = cantAddContactToGroup ( 'abc123' ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . ChooseGroupMembers &&
result . composer . cantAddContactIdForModal === 'abc123'
) ;
} ) ;
} ) ;
describe ( 'CLEAR_GROUP_CREATION_ERROR' , ( ) = > {
it ( 'clears the group creation error' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
isCreating : false as const ,
hasError : true as const ,
} ,
} ;
const action = clearGroupCreationError ( ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . SetGroupMetadata &&
result . composer . hasError === false
) ;
} ) ;
} ) ;
describe ( 'CLEAR_INVITED_CONVERSATIONS_FOR_NEWLY_CREATED_GROUP' , ( ) = > {
it ( 'clears the list of invited conversation IDs' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
invitedConversationIdsForNewlyCreatedGroup : [ 'abc123' , 'def456' ] ,
} ;
const action = clearInvitedConversationsForNewlyCreatedGroup ( ) ;
const result = reducer ( state , action ) ;
assert . isUndefined ( result . invitedConversationIdsForNewlyCreatedGroup ) ;
} ) ;
} ) ;
describe ( 'CLOSE_CANT_ADD_CONTACT_TO_GROUP_MODAL' , ( ) = > {
it ( 'closes the "cannot add contact" modal"' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
const action = closeCantAddContactToGroupModal ( ) ;
const result = reducer ( state , action ) ;
assert (
result . composer ? . step === ComposerStep . ChooseGroupMembers &&
result . composer . cantAddContactIdForModal === undefined ,
'Expected the contact ID to be cleared'
) ;
} ) ;
} ) ;
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 : {
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 : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.Showing ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
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 ( ) ,
composer : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
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 : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.Shown ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
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 : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.Showing ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
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 ( ) ,
composer : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
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 : {
cantAddContactIdForModal : 'abc123' ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
const action = closeRecommendedGroupSizeModal ( ) ;
const result = reducer ( state , action ) ;
assert . strictEqual ( result , state ) ;
} ) ;
} ) ;
describe ( 'createGroup' , ( ) = > {
const conversationsState = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ 'abc123' ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'Foo Bar Group' ,
groupAvatar : new Uint8Array ( [ 1 , 2 , 3 ] ) . buffer ,
isCreating : false as const ,
hasError : true as const ,
} ,
} ;
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 ( ) = > {
await createGroup ( ) (
sinon . spy ( ) ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
sinon . assert . calledOnce ( createGroupStub ) ;
sinon . assert . calledWith ( createGroupStub , {
name : 'Foo Bar Group' ,
avatar : new Uint8Array ( [ 1 , 2 , 3 ] ) . buffer ,
conversationIds : [ 'abc123' ] ,
} ) ;
} ) ;
2021-03-11 01:54:13 +00:00
it ( "trims the group's title before calling groups.createGroupV2" , async ( ) = > {
await createGroup ( ) (
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 ( ) ;
const createGroupPromise = createGroup ( ) (
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 ( ) ;
const createGroupPromise = createGroup ( ) (
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 ( ) = > {
createGroupStub . resolves ( {
id : '9876' ,
get : ( key : string ) = > {
if ( key !== 'pendingMembersV2' ) {
throw new Error ( 'This getter is not set up for this test' ) ;
}
return [ { conversationId : 'xyz999' } ] ;
} ,
} ) ;
const dispatch = sinon . spy ( ) ;
await createGroup ( ) (
dispatch ,
( ) = > ( {
. . . getEmptyRootState ( ) ,
conversations : conversationsState ,
} ) ,
null
) ;
sinon . assert . calledWith (
window . Whisper . events . trigger as sinon . SinonSpy ,
'showConversation' ,
'9876' ,
undefined
) ;
sinon . assert . calledWith ( dispatch , {
type : 'CREATE_GROUP_FULFILLED' ,
payload : { invitedConversationIds : [ 'xyz999' ] } ,
} ) ;
const fulfilledAction = dispatch . getCall ( 1 ) . args [ 0 ] ;
const result = reducer ( conversationsState , fulfilledAction ) ;
assert . deepEqual ( result . invitedConversationIdsForNewlyCreatedGroup , [
'xyz999' ,
] ) ;
sinon . assert . calledWith ( dispatch , {
type : 'SWITCH_TO_ASSOCIATED_VIEW' ,
payload : { conversationId : '9876' } ,
} ) ;
} ) ;
} ) ;
2020-12-07 20:43:19 +00:00
describe ( 'MESSAGE_SIZE_CHANGED' , ( ) = > {
const stateWithActiveConversation = {
2021-01-06 15:41:43 +00:00
. . . getEmptyState ( ) ,
2020-12-07 20:43:19 +00:00
messagesByConversation : {
[ conversationId ] : {
heightChangeMessageIds : [ ] ,
isLoadingMessages : false ,
isNearBottom : true ,
messageIds : [ messageId ] ,
metrics : { totalUnread : 0 } ,
resetCounter : 0 ,
scrollToMessageCounter : 0 ,
} ,
} ,
messagesLookup : {
[ messageId ] : getDefaultMessage ( messageId ) ,
} ,
} ;
it ( 'does nothing if no conversation is active' , ( ) = > {
2021-01-06 15:41:43 +00:00
const state = getEmptyState ( ) ;
2020-12-07 20:43:19 +00:00
assert . strictEqual (
reducer ( state , messageSizeChanged ( 'messageId' , 'convoId' ) ) ,
state
) ;
} ) ;
it ( 'does nothing if a different conversation is active' , ( ) = > {
assert . deepEqual (
reducer (
stateWithActiveConversation ,
messageSizeChanged ( messageId , 'another-conversation-guid' )
) ,
stateWithActiveConversation
) ;
} ) ;
it ( 'adds the message ID to the list of messages with changed heights' , ( ) = > {
const result = reducer (
stateWithActiveConversation ,
messageSizeChanged ( messageId , conversationId )
) ;
assert . sameMembers (
result . messagesByConversation [ conversationId ]
? . heightChangeMessageIds || [ ] ,
[ messageId ]
) ;
} ) ;
it ( "doesn't add duplicates to the list of changed-heights messages" , ( ) = > {
const state = set (
[ 'messagesByConversation' , conversationId , 'heightChangeMessageIds' ] ,
[ messageId ] ,
stateWithActiveConversation
) ;
const result = reducer (
state ,
messageSizeChanged ( messageId , conversationId )
) ;
assert . sameMembers (
result . messagesByConversation [ conversationId ]
? . heightChangeMessageIds || [ ] ,
[ messageId ]
) ;
} ) ;
} ) ;
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 ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ messageIdThree , messageIdTwo , messageId ] ,
metrics : {
totalUnread : 0 ,
} ,
} ,
} ,
} ;
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 : [ messageIdThree , messageIdTwo , messageId ] ,
metrics : {
totalUnread : 0 ,
newest : {
id : messageId ,
received_at : time ,
} ,
} ,
} ,
} ,
} ;
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 : {
totalUnread : 0 ,
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 ,
totalUnread : 0 ,
} ,
} ,
} ,
} ;
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 ,
} ,
} ,
messagesByConversation : {
[ conversationId ] : {
. . . getDefaultConversationMessage ( ) ,
messageIds : [ messageId , messageIdTwo , messageIdThree ] ,
metrics : {
totalUnread : 0 ,
} ,
} ,
} ,
} ;
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 : [ messageId , messageIdTwo , messageIdThree ] ,
metrics : {
totalUnread : 0 ,
oldest : {
id : messageId ,
received_at : time ,
} ,
} ,
} ,
} ,
} ;
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 : {
totalUnread : 0 ,
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 ,
totalUnread : 0 ,
} ,
} ,
} ,
} ;
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-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 , {
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 : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'foo' ,
groupAvatar : new ArrayBuffer ( 2 ) ,
isCreating : false as const ,
hasError : false as const ,
} ,
} ;
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" , ( ) = > {
const avatar = new Uint8Array ( [ 1 , 2 , 3 ] ) . buffer ;
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'foo' ,
groupAvatar : undefined ,
isCreating : false as const ,
hasError : false as const ,
} ,
} ;
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 ( ) ,
composer : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
isCreating : false as const ,
hasError : false as const ,
} ,
} ;
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 ( ) ,
composer : {
2021-03-03 20:09:58 +00:00
step : ComposerStep.StartDirectConversation as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-02-23 20:34:28 +00:00
} ,
} ;
const action = setComposeSearchTerm ( 'foo bar' ) ;
const result = reducer ( state , action ) ;
2021-03-03 20:09:58 +00:00
assert . deepEqual ( result . composer , {
step : ComposerStep.StartDirectConversation ,
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
} ) ;
} ) ;
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
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 ( ) ,
composer : {
2021-03-03 20:09:58 +00:00
step : ComposerStep.StartDirectConversation as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
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 ( ) ,
composer : {
2021-03-03 20:09:58 +00:00
step : ComposerStep.StartDirectConversation as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
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 ( ) ,
composer : {
2021-03-03 20:09:58 +00:00
step : ComposerStep.StartDirectConversation as const ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
2021-02-23 20:34:28 +00:00
} ,
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
2021-03-03 20:09:58 +00:00
assert . deepEqual ( result . composer , {
step : ComposerStep.StartDirectConversation ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
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 : {
cantAddContactIdForModal : undefined ,
2021-04-20 23:16:49 +00:00
searchTerm : 'to be cleared' ,
2021-03-03 20:09:58 +00:00
groupAvatar : undefined ,
groupName : '' ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
selectedConversationIds : [ ] ,
step : ComposerStep.ChooseGroupMembers as const ,
} ,
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.StartDirectConversation ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
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 ( ) ,
composer : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
isCreating : false ,
hasError : false as const ,
} ,
} ;
const action = startComposing ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.StartDirectConversation ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
} ) ;
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-03-03 20:09:58 +00:00
assert . deepEqual ( result . composer , {
step : ComposerStep.StartDirectConversation ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
} ) ;
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-03-03 20:09:58 +00:00
assert . deepEqual ( result . composer , {
step : ComposerStep.StartDirectConversation ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
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 : {
step : ComposerStep.StartDirectConversation as const ,
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 , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
it ( 'does nothing if already on the second step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
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 : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'Foo Bar Group' ,
groupAvatar : new Uint8Array ( [ 4 , 2 ] ) . buffer ,
isCreating : false ,
hasError : false as const ,
} ,
} ;
const action = showChooseGroupMembers ( ) ;
const result = reducer ( state , action ) ;
assert . isFalse ( result . showArchived ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'Foo Bar Group' ,
groupAvatar : new Uint8Array ( [ 4 , 2 ] ) . buffer ,
} ) ;
} ) ;
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 , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
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 , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
} ) ;
describe ( 'START_SETTING_GROUP_METADATA' , ( ) = > {
it ( 'moves from the second to the third step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = startSettingGroupMetadata ( ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.SetGroupMetadata ,
selectedConversationIds : [ 'abc' , 'def' ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
isCreating : false ,
hasError : false ,
} ) ;
} ) ;
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 : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : 'foo bar' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'Foo Bar Group' ,
groupAvatar : new Uint8Array ( [ 6 , 9 ] ) . buffer ,
} ,
} ;
const action = startSettingGroupMetadata ( ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.SetGroupMetadata ,
selectedConversationIds : [ 'abc' , 'def' ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'Foo Bar Group' ,
groupAvatar : new Uint8Array ( [ 6 , 9 ] ) . buffer ,
isCreating : false ,
hasError : false as const ,
} ) ;
} ) ;
it ( 'does nothing if already on the third step of the composer' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.SetGroupMetadata as const ,
selectedConversationIds : [ ] ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : 'Foo Bar Group' ,
groupAvatar : new Uint8Array ( [ 4 , 2 ] ) . buffer ,
isCreating : false ,
hasError : false as const ,
} ,
} ;
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 ] ;
}
let remoteConfigGetValueStub : sinon.SinonStub ;
beforeEach ( ( ) = > {
remoteConfigGetValueStub = sinonSandbox
. stub ( window . Signal . RemoteConfig , 'getValue' )
. withArgs ( 'global.groupsv2.maxGroupSize' )
. returns ( '22' )
. withArgs ( 'global.groupsv2.groupSizeHardLimit' )
. returns ( '33' ) ;
} ) ;
it ( 'adds conversation IDs to the list' , ( ) = > {
const zero = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const one = reducer ( zero , getAction ( 'abc' , zero ) ) ;
const two = reducer ( one , getAction ( 'def' , one ) ) ;
assert . deepEqual ( two . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
it ( 'removes conversation IDs from the list' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'abc' , 'def' ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( 'abc' , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ 'def' ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
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 : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Showing ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
it ( "doesn't show the recommended group size modal twice" , ( ) = > {
const oldSelectedConversationIds = times ( 21 , ( ) = > uuid ( ) ) ;
const newUuid = uuid ( ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
it ( 'defaults the maximum recommended size to 151' , ( ) = > {
[ undefined , 'xyz' ] . forEach ( value = > {
remoteConfigGetValueStub
. withArgs ( 'global.groupsv2.maxGroupSize' )
. returns ( value ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( uuid ( ) , state ) ;
assert . strictEqual ( action . payload . maxRecommendedGroupSize , 151 ) ;
} ) ;
} ) ;
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 : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.Showing ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
it ( "doesn't show the maximum group size modal twice" , ( ) = > {
const oldSelectedConversationIds = times ( 31 , ( ) = > uuid ( ) ) ;
const newUuid = uuid ( ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : oldSelectedConversationIds ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.Shown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( newUuid , state ) ;
const result = reducer ( state , action ) ;
assert . deepEqual ( result . composer , {
step : ComposerStep.ChooseGroupMembers ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ . . . oldSelectedConversationIds , newUuid ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.Shown ,
maximumGroupSizeModalState : OneTimeModalState.Shown ,
groupName : '' ,
groupAvatar : undefined ,
} ) ;
} ) ;
it ( 'cannot select more than the maximum number of conversations' , ( ) = > {
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : times ( 1000 , ( ) = > uuid ( ) ) ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
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
} ) ;
it ( 'defaults the maximum group size to 1001 if the recommended maximum is smaller' , ( ) = > {
[ undefined , 'xyz' ] . forEach ( value = > {
remoteConfigGetValueStub
. withArgs ( 'global.groupsv2.maxGroupSize' )
. returns ( '2' )
. withArgs ( 'global.groupsv2.groupSizeHardLimit' )
. returns ( value ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( uuid ( ) , state ) ;
assert . strictEqual ( action . payload . maxGroupSize , 1001 ) ;
} ) ;
} ) ;
it ( 'defaults the maximum group size to (recommended maximum + 1) if the recommended maximum is more than 1001' , ( ) = > {
remoteConfigGetValueStub
. withArgs ( 'global.groupsv2.maxGroupSize' )
. returns ( '1234' )
. withArgs ( 'global.groupsv2.groupSizeHardLimit' )
. returns ( '2' ) ;
const state = {
. . . getEmptyState ( ) ,
composer : {
step : ComposerStep.ChooseGroupMembers as const ,
2021-04-20 23:16:49 +00:00
searchTerm : '' ,
2021-03-03 20:09:58 +00:00
selectedConversationIds : [ ] ,
cantAddContactIdForModal : undefined ,
recommendedGroupSizeModalState : OneTimeModalState.NeverShown ,
maximumGroupSizeModalState : OneTimeModalState.NeverShown ,
groupName : '' ,
groupAvatar : undefined ,
} ,
} ;
const action = getAction ( uuid ( ) , state ) ;
assert . strictEqual ( action . payload . maxGroupSize , 1235 ) ;
2021-02-23 20:34:28 +00:00
} ) ;
} ) ;
2020-12-04 20:41:40 +00:00
} ) ;
} ) ;