Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions ts/state/ducks/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ export type ConversationsStateType = {
messageInfoId: string | undefined;
showRightPanel: boolean;
selectedMessageIds: Array<string>;
quotedMessage?: ReplyingToMessageProps;
quotedMessageByConversation: Partial<Record<string, ReplyingToMessageProps>>;
areMoreMessagesBeingFetched: boolean;

/**
Expand Down Expand Up @@ -545,6 +545,7 @@ export function getEmptyConversationState(): ConversationsStateType {
messageInfoId: undefined,
showRightPanel: false,
selectedMessageIds: [],
quotedMessageByConversation: {},
areMoreMessagesBeingFetched: false, // top or bottom
showScrollButton: false,
mentionMembers: [],
Expand Down Expand Up @@ -943,7 +944,7 @@ const conversationsSlice = createSlice({
selectedMessageIds: [],

messageInfoId: undefined,
quotedMessage: undefined,
quotedMessageByConversation: state.quotedMessageByConversation,

nextMessageToPlay: undefined,
showScrollButton,
Expand Down Expand Up @@ -1022,7 +1023,17 @@ const conversationsSlice = createSlice({
state: ConversationsStateType,
action: PayloadAction<ReplyingToMessageProps | undefined>
) {
state.quotedMessage = action.payload;
const conversationId = action.payload?.convoId || state.selectedConversation;
if (!conversationId) {
return state;
}

if (action.payload) {
state.quotedMessageByConversation[conversationId] = action.payload;
return state;
}

delete state.quotedMessageByConversation[conversationId];
return state;
},
quotedMessageToAnimate(
Expand Down
15 changes: 13 additions & 2 deletions ts/state/selectors/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,19 @@ export const getReactionBarTriggerPosition = (state: StateType): PopoverTriggerP
export const getIsCompositionTextAreaFocused = (state: StateType): boolean =>
state.conversations.isCompositionTextAreaFocused;

export const getQuotedMessage = (state: StateType): ReplyingToMessageProps | undefined =>
state.conversations.quotedMessage;
export const getQuotedMessage = createSelector(
[getSelectedConversationKey, getConversations],
(
selectedConversationKey: string | undefined,
state: ConversationsStateType
): ReplyingToMessageProps | undefined => {
if (!selectedConversationKey) {
return undefined;
}

return state.quotedMessageByConversation[selectedConversationKey];
}
);

export const areMoreMessagesBeingFetched = (state: StateType): boolean =>
state.conversations.areMoreMessagesBeingFetched || false;
Expand Down
74 changes: 73 additions & 1 deletion ts/test/session/unit/selectors/conversations_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ import { assert } from 'chai';

import Sinon from 'sinon';
import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../../../../models/types';
import { ConversationLookupType } from '../../../../state/ducks/conversations';
import type { ReplyingToMessageProps } from '../../../../components/conversation/composition/CompositionBox';
import {
actions,
ConversationLookupType,
getEmptyConversationState,
quoteMessage,
reducer as conversationsReducer,
} from '../../../../state/ducks/conversations';
import type { StateType } from '../../../../state/reducer';
import {
_getConversationComparator,
_getSortedConversations,
getQuotedMessage,
} from '../../../../state/selectors/conversations';
import { TestUtils } from '../../../test-utils';

Expand All @@ -17,6 +26,69 @@ describe('state/selectors/conversations', () => {
afterEach(() => {
Sinon.restore();
});

describe('#getQuotedMessage', () => {
const openConversation = (
state: ReturnType<typeof getEmptyConversationState>,
conversationKey: string
) =>
conversationsReducer(
state,
actions.openConversationExternal({
conversationKey,
initialMessages: [],
initialQuotes: [],
firstUnreadMessageId: null,
mostRecentMessageId: null,
oldestMessageId: null,
})
);

const buildState = (conversations: ReturnType<typeof getEmptyConversationState>) =>
({ conversations }) as StateType;

const firstQuote: ReplyingToMessageProps = {
convoId: 'conversation-1',
id: 'message-1',
author: 'author-1',
referencedMessageSentAt: 111,
quotedAt: 1,
text: 'first quote',
};

const secondQuote: ReplyingToMessageProps = {
convoId: 'conversation-2',
id: 'message-2',
author: 'author-2',
referencedMessageSentAt: 222,
quotedAt: 2,
text: 'second quote',
};

it('keeps draft reply quotes scoped to their conversation', () => {
let state = getEmptyConversationState();

state = openConversation(state, 'conversation-1');
state = conversationsReducer(state, quoteMessage(firstQuote));
assert.deepEqual(getQuotedMessage(buildState(state)), firstQuote);

state = openConversation(state, 'conversation-2');
assert.isUndefined(getQuotedMessage(buildState(state)));

state = conversationsReducer(state, quoteMessage(secondQuote));
assert.deepEqual(getQuotedMessage(buildState(state)), secondQuote);

state = openConversation(state, 'conversation-1');
assert.deepEqual(getQuotedMessage(buildState(state)), firstQuote);

state = conversationsReducer(state, quoteMessage(undefined));
assert.isUndefined(getQuotedMessage(buildState(state)));

state = openConversation(state, 'conversation-2');
assert.deepEqual(getQuotedMessage(buildState(state)), secondQuote);
});
});

describe('#getSortedConversationsList', () => {
it('sorts conversations based on timestamp then by intl-friendly title', () => {
const data: ConversationLookupType = {
Expand Down