Allow multiple reactions to stories
This commit is contained in:
parent
42554ebaf0
commit
6d576ed901
9 changed files with 211 additions and 119 deletions
|
@ -40,7 +40,6 @@ export type StoryViewType = {
|
|||
isHidden?: boolean;
|
||||
isUnread?: boolean;
|
||||
messageId: string;
|
||||
selectedReaction?: string;
|
||||
sender: Pick<
|
||||
ConversationType,
|
||||
| 'acceptedMessageRequest'
|
||||
|
|
|
@ -313,7 +313,8 @@ export async function sendReaction(
|
|||
const newReactions = reactionUtil.markOutgoingReactionSent(
|
||||
getReactions(message),
|
||||
pendingReaction,
|
||||
successfulConversationIds
|
||||
successfulConversationIds,
|
||||
message.attributes
|
||||
);
|
||||
setReactions(message, newReactions);
|
||||
|
||||
|
|
|
@ -152,6 +152,7 @@ import { shouldDownloadStory } from '../util/shouldDownloadStory';
|
|||
import { shouldShowStoriesView } from '../state/selectors/stories';
|
||||
import type { ContactWithHydratedAvatar } from '../textsecure/SendMessage';
|
||||
import { SeenStatus } from '../MessageSeenStatus';
|
||||
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -233,12 +234,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
const { storyChanged } = window.reduxActions.stories;
|
||||
|
||||
if (isStory(this.attributes)) {
|
||||
const ourConversationId =
|
||||
window.ConversationController.getOurConversationIdOrThrow();
|
||||
const storyData = getStoryDataFromMessageAttributes(
|
||||
this.attributes,
|
||||
ourConversationId
|
||||
);
|
||||
const storyData = getStoryDataFromMessageAttributes(this.attributes);
|
||||
|
||||
if (!storyData) {
|
||||
return;
|
||||
|
@ -2892,14 +2888,15 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
const reactions = reactionUtil.addOutgoingReaction(
|
||||
this.get('reactions') || [],
|
||||
newReaction
|
||||
newReaction,
|
||||
isStory(this.attributes)
|
||||
);
|
||||
this.set({ reactions });
|
||||
} else {
|
||||
const oldReactions = this.get('reactions') || [];
|
||||
let reactions: Array<MessageReactionType>;
|
||||
const oldReaction = oldReactions.find(
|
||||
re => re.fromId === reaction.get('fromId')
|
||||
const oldReaction = oldReactions.find(re =>
|
||||
isNewReactionReplacingPrevious(re, reaction.attributes, this.attributes)
|
||||
);
|
||||
if (oldReaction) {
|
||||
this.clearNotifications(oldReaction);
|
||||
|
@ -2914,12 +2911,20 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
if (reaction.get('source') === ReactionSource.FromSync) {
|
||||
reactions = oldReactions.filter(
|
||||
re =>
|
||||
re.fromId !== reaction.get('fromId') ||
|
||||
re.timestamp > reaction.get('timestamp')
|
||||
!isNewReactionReplacingPrevious(
|
||||
re,
|
||||
reaction.attributes,
|
||||
this.attributes
|
||||
) || re.timestamp > reaction.get('timestamp')
|
||||
);
|
||||
} else {
|
||||
reactions = oldReactions.filter(
|
||||
re => re.fromId !== reaction.get('fromId')
|
||||
re =>
|
||||
!isNewReactionReplacingPrevious(
|
||||
re,
|
||||
reaction.attributes,
|
||||
this.attributes
|
||||
)
|
||||
);
|
||||
}
|
||||
this.set({ reactions });
|
||||
|
@ -2948,7 +2953,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
reactions = oldReactions.filter(
|
||||
re => re.fromId !== reaction.get('fromId')
|
||||
re =>
|
||||
!isNewReactionReplacingPrevious(
|
||||
re,
|
||||
reaction.attributes,
|
||||
this.attributes
|
||||
)
|
||||
);
|
||||
reactions.push(reactionToAdd);
|
||||
this.set({ reactions });
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { findLastIndex, has, identity, omit, negate } from 'lodash';
|
||||
import type { MessageReactionType } from '../model-types.d';
|
||||
import type {
|
||||
MessageAttributesType,
|
||||
MessageReactionType,
|
||||
} from '../model-types.d';
|
||||
import { areObjectEntriesEqual } from '../util/areObjectEntriesEqual';
|
||||
import { isStory } from '../state/selectors/message';
|
||||
|
||||
const isReactionEqual = (
|
||||
a: undefined | Readonly<MessageReactionType>,
|
||||
|
@ -31,8 +35,13 @@ const isOutgoingReactionCompletelyUnsent = ({
|
|||
|
||||
export function addOutgoingReaction(
|
||||
oldReactions: ReadonlyArray<MessageReactionType>,
|
||||
newReaction: Readonly<MessageReactionType>
|
||||
newReaction: Readonly<MessageReactionType>,
|
||||
isStoryMessage = false
|
||||
): Array<MessageReactionType> {
|
||||
if (isStoryMessage) {
|
||||
return [...oldReactions, newReaction];
|
||||
}
|
||||
|
||||
const pendingOutgoingReactions = new Set(
|
||||
oldReactions.filter(isOutgoingReactionPending)
|
||||
);
|
||||
|
@ -101,6 +110,17 @@ export function* getUnsentConversationIds({
|
|||
}
|
||||
}
|
||||
|
||||
// This function is used when filtering reactions so that we can limit normal
|
||||
// messages to a single reactions but allow multiple reactions from the same
|
||||
// sender for stories.
|
||||
export function isNewReactionReplacingPrevious(
|
||||
reaction: MessageReactionType,
|
||||
newReaction: MessageReactionType,
|
||||
messageAttributes: MessageAttributesType
|
||||
): boolean {
|
||||
return !isStory(messageAttributes) && reaction.fromId === newReaction.fromId;
|
||||
}
|
||||
|
||||
export const markOutgoingReactionFailed = (
|
||||
reactions: Array<MessageReactionType>,
|
||||
reaction: Readonly<MessageReactionType>
|
||||
|
@ -116,7 +136,8 @@ export const markOutgoingReactionFailed = (
|
|||
export const markOutgoingReactionSent = (
|
||||
reactions: ReadonlyArray<MessageReactionType>,
|
||||
reaction: Readonly<MessageReactionType>,
|
||||
conversationIdsSentTo: Iterable<string>
|
||||
conversationIdsSentTo: Iterable<string>,
|
||||
messageAttributes: MessageAttributesType
|
||||
): Array<MessageReactionType> => {
|
||||
const result: Array<MessageReactionType> = [];
|
||||
|
||||
|
@ -135,7 +156,8 @@ export const markOutgoingReactionSent = (
|
|||
if (!isReactionEqual(re, reaction)) {
|
||||
const shouldKeep = !isFullySent
|
||||
? true
|
||||
: re.fromId !== reaction.fromId || re.timestamp > reaction.timestamp;
|
||||
: !isNewReactionReplacingPrevious(re, reaction, messageAttributes) ||
|
||||
re.timestamp > reaction.timestamp;
|
||||
if (shouldKeep) {
|
||||
result.push(re);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ export async function loadStories(): Promise<void> {
|
|||
}
|
||||
|
||||
export function getStoryDataFromMessageAttributes(
|
||||
message: MessageAttributesType,
|
||||
ourConversationId?: string
|
||||
message: MessageAttributesType
|
||||
): StoryDataType | undefined {
|
||||
const { attachments } = message;
|
||||
const unresolvedAttachment = attachments ? attachments[0] : undefined;
|
||||
|
@ -33,17 +32,13 @@ export function getStoryDataFromMessageAttributes(
|
|||
? getAttachmentsForMessage(message)
|
||||
: [unresolvedAttachment];
|
||||
|
||||
const selectedReaction = (
|
||||
(message.reactions || []).find(re => re.fromId === ourConversationId) || {}
|
||||
).emoji;
|
||||
|
||||
return {
|
||||
attachment,
|
||||
messageId: message.id,
|
||||
selectedReaction,
|
||||
...pick(message, [
|
||||
'conversationId',
|
||||
'deletedForEveryone',
|
||||
'reactions',
|
||||
'readStatus',
|
||||
'sendStateByConversationId',
|
||||
'source',
|
||||
|
@ -57,11 +52,8 @@ export function getStoryDataFromMessageAttributes(
|
|||
export function getStoriesForRedux(): Array<StoryDataType> {
|
||||
strictAssert(storyData, 'storyData has not been loaded');
|
||||
|
||||
const ourConversationId =
|
||||
window.ConversationController.getOurConversationId();
|
||||
|
||||
const stories = storyData
|
||||
.map(story => getStoryDataFromMessageAttributes(story, ourConversationId))
|
||||
.map(getStoryDataFromMessageAttributes)
|
||||
.filter(isNotNil);
|
||||
|
||||
storyData = undefined;
|
||||
|
|
|
@ -37,11 +37,11 @@ import { viewedReceiptsJobQueue } from '../../jobs/viewedReceiptsJobQueue';
|
|||
export type StoryDataType = {
|
||||
attachment?: AttachmentType;
|
||||
messageId: string;
|
||||
selectedReaction?: string;
|
||||
} & Pick<
|
||||
MessageAttributesType,
|
||||
| 'conversationId'
|
||||
| 'deletedForEveryone'
|
||||
| 'reactions'
|
||||
| 'readStatus'
|
||||
| 'sendStateByConversationId'
|
||||
| 'source'
|
||||
|
@ -65,7 +65,6 @@ export type StoriesStateType = {
|
|||
|
||||
const LOAD_STORY_REPLIES = 'stories/LOAD_STORY_REPLIES';
|
||||
const MARK_STORY_READ = 'stories/MARK_STORY_READ';
|
||||
const REACT_TO_STORY = 'stories/REACT_TO_STORY';
|
||||
const REPLY_TO_STORY = 'stories/REPLY_TO_STORY';
|
||||
export const RESOLVE_ATTACHMENT_URL = 'stories/RESOLVE_ATTACHMENT_URL';
|
||||
const STORY_CHANGED = 'stories/STORY_CHANGED';
|
||||
|
@ -84,14 +83,6 @@ type MarkStoryReadActionType = {
|
|||
payload: string;
|
||||
};
|
||||
|
||||
type ReactToStoryActionType = {
|
||||
type: typeof REACT_TO_STORY;
|
||||
payload: {
|
||||
messageId: string;
|
||||
selectedReaction: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ReplyToStoryActionType = {
|
||||
type: typeof REPLY_TO_STORY;
|
||||
payload: MessageAttributesType;
|
||||
|
@ -119,7 +110,6 @@ export type StoriesActionType =
|
|||
| MarkStoryReadActionType
|
||||
| MessageChangedActionType
|
||||
| MessageDeletedActionType
|
||||
| ReactToStoryActionType
|
||||
| ReplyToStoryActionType
|
||||
| ResolveAttachmentUrlActionType
|
||||
| StoryChangedActionType
|
||||
|
@ -286,27 +276,24 @@ function queueStoryDownload(
|
|||
|
||||
function reactToStory(
|
||||
nextReaction: string,
|
||||
messageId: string,
|
||||
previousReaction?: string
|
||||
): ThunkAction<void, RootStateType, unknown, ReactToStoryActionType> {
|
||||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
try {
|
||||
await enqueueReactionForSend({
|
||||
messageId,
|
||||
emoji: nextReaction,
|
||||
remove: nextReaction === previousReaction,
|
||||
});
|
||||
dispatch({
|
||||
type: REACT_TO_STORY,
|
||||
payload: {
|
||||
messageId,
|
||||
selectedReaction: nextReaction,
|
||||
},
|
||||
remove: false,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error('Error enqueuing reaction', error, messageId, nextReaction);
|
||||
showToast(ToastReactionFailed);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'NOOP',
|
||||
payload: null,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -403,8 +390,8 @@ export function reducer(
|
|||
'conversationId',
|
||||
'deletedForEveryone',
|
||||
'messageId',
|
||||
'reactions',
|
||||
'readStatus',
|
||||
'selectedReaction',
|
||||
'sendStateByConversationId',
|
||||
'source',
|
||||
'sourceUuid',
|
||||
|
@ -424,9 +411,14 @@ export function reducer(
|
|||
!isDownloaded(prevStory.attachment) &&
|
||||
isDownloaded(newStory.attachment);
|
||||
const readStatusChanged = prevStory.readStatus !== newStory.readStatus;
|
||||
const reactionsChanged =
|
||||
prevStory.reactions?.length !== newStory.reactions?.length;
|
||||
|
||||
const shouldReplace =
|
||||
isDownloadingAttachment || hasAttachmentDownloaded || readStatusChanged;
|
||||
isDownloadingAttachment ||
|
||||
hasAttachmentDownloaded ||
|
||||
readStatusChanged ||
|
||||
reactionsChanged;
|
||||
if (!shouldReplace) {
|
||||
return state;
|
||||
}
|
||||
|
@ -448,22 +440,6 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === REACT_TO_STORY) {
|
||||
return {
|
||||
...state,
|
||||
stories: state.stories.map(story => {
|
||||
if (story.messageId === action.payload.messageId) {
|
||||
return {
|
||||
...story,
|
||||
selectedReaction: action.payload.selectedReaction,
|
||||
};
|
||||
}
|
||||
|
||||
return story;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === MARK_STORY_READ) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -5,10 +5,12 @@ import { createSelector } from 'reselect';
|
|||
import { pick } from 'lodash';
|
||||
|
||||
import type { GetConversationByIdType } from './conversations';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
import type {
|
||||
ConversationStoryType,
|
||||
StoryViewType,
|
||||
} from '../../components/StoryListItem';
|
||||
import type { MessageReactionType } from '../../model-types.d';
|
||||
import type { ReplyStateType } from '../../types/Stories';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { StoryDataType, StoriesStateType } from '../ducks/stories';
|
||||
|
@ -55,6 +57,35 @@ function sortByRecencyAndUnread(
|
|||
return storyA.timestamp > storyB.timestamp ? -1 : 1;
|
||||
}
|
||||
|
||||
function getReactionUniqueId(reaction: MessageReactionType): string {
|
||||
return `${reaction.fromId}:${reaction.targetAuthorUuid}:${reaction.timestamp}`;
|
||||
}
|
||||
|
||||
function getAvatarData(
|
||||
conversation: ConversationType
|
||||
): Pick<
|
||||
ConversationType,
|
||||
| 'acceptedMessageRequest'
|
||||
| 'avatarPath'
|
||||
| 'color'
|
||||
| 'isMe'
|
||||
| 'name'
|
||||
| 'profileName'
|
||||
| 'sharedGroupNames'
|
||||
| 'title'
|
||||
> {
|
||||
return pick(conversation, [
|
||||
'acceptedMessageRequest',
|
||||
'avatarPath',
|
||||
'color',
|
||||
'isMe',
|
||||
'name',
|
||||
'profileName',
|
||||
'sharedGroupNames',
|
||||
'title',
|
||||
]);
|
||||
}
|
||||
|
||||
function getConversationStory(
|
||||
conversationSelector: GetConversationByIdType,
|
||||
story: StoryDataType,
|
||||
|
@ -92,7 +123,6 @@ function getConversationStory(
|
|||
canReply: canReply(story, ourConversationId, conversationSelector),
|
||||
isUnread: story.readStatus === ReadStatus.Unread,
|
||||
messageId: story.messageId,
|
||||
selectedReaction: story.selectedReaction,
|
||||
sender,
|
||||
timestamp,
|
||||
};
|
||||
|
@ -153,38 +183,56 @@ export const getStoryReplies = createSelector(
|
|||
conversationSelector,
|
||||
contactNameColorSelector,
|
||||
me,
|
||||
{ replyState }: Readonly<StoriesStateType>
|
||||
{ stories, replyState }: Readonly<StoriesStateType>
|
||||
): ReplyStateType | undefined => {
|
||||
if (!replyState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const foundStory = stories.find(
|
||||
story => story.messageId === replyState.messageId
|
||||
);
|
||||
|
||||
const reactions = foundStory
|
||||
? (foundStory.reactions || []).map(reaction => {
|
||||
const conversation = conversationSelector(reaction.fromId);
|
||||
|
||||
return {
|
||||
...getAvatarData(conversation),
|
||||
contactNameColor: contactNameColorSelector(
|
||||
foundStory.conversationId,
|
||||
conversation.id
|
||||
),
|
||||
id: getReactionUniqueId(reaction),
|
||||
reactionEmoji: reaction.emoji,
|
||||
timestamp: reaction.timestamp,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
const replies = replyState.replies.map(reply => {
|
||||
const conversation =
|
||||
reply.type === 'outgoing'
|
||||
? me
|
||||
: conversationSelector(reply.sourceUuid || reply.source);
|
||||
|
||||
return {
|
||||
...getAvatarData(conversation),
|
||||
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
||||
contactNameColor: contactNameColorSelector(
|
||||
reply.conversationId,
|
||||
conversation.id
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const combined = [...replies, ...reactions].sort((a, b) =>
|
||||
a.timestamp > b.timestamp ? 1 : -1
|
||||
);
|
||||
|
||||
return {
|
||||
messageId: replyState.messageId,
|
||||
replies: replyState.replies.map(reply => {
|
||||
const conversation =
|
||||
reply.type === 'outgoing'
|
||||
? me
|
||||
: conversationSelector(reply.sourceUuid || reply.source);
|
||||
|
||||
return {
|
||||
...pick(conversation, [
|
||||
'acceptedMessageRequest',
|
||||
'avatarPath',
|
||||
'color',
|
||||
'isMe',
|
||||
'name',
|
||||
'profileName',
|
||||
'sharedGroupNames',
|
||||
'title',
|
||||
]),
|
||||
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
||||
contactNameColor: contactNameColorSelector(
|
||||
reply.conversationId,
|
||||
conversation.id
|
||||
),
|
||||
};
|
||||
}),
|
||||
replies: combined,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -69,8 +69,8 @@ export function SmartStoryViewer({
|
|||
onNextUserStories={onNextUserStories}
|
||||
onPrevUserStories={onPrevUserStories}
|
||||
onReactToStory={async (emoji, story) => {
|
||||
const { messageId, selectedReaction: previousReaction } = story;
|
||||
storiesActions.reactToStory(emoji, messageId, previousReaction);
|
||||
const { messageId } = story;
|
||||
storiesActions.reactToStory(emoji, messageId);
|
||||
}}
|
||||
onReplyToStory={(message, mentions, timestamp, story) => {
|
||||
storiesActions.replyToStory(
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
import { assert } from 'chai';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { omit } from 'lodash';
|
||||
import type { MessageReactionType } from '../../model-types.d';
|
||||
import type {
|
||||
MessageAttributesType,
|
||||
MessageReactionType,
|
||||
} from '../../model-types.d';
|
||||
import { isEmpty } from '../../util/iterables';
|
||||
|
||||
import {
|
||||
|
@ -48,6 +51,18 @@ describe('reaction utilities', () => {
|
|||
const newReactions = addOutgoingReaction(oldReactions, reaction);
|
||||
assert.deepStrictEqual(newReactions, [oldReactions[1], reaction]);
|
||||
});
|
||||
|
||||
it('does not remove any pending reactions if its a story', () => {
|
||||
const oldReactions = [
|
||||
{ ...rxn('😭', { isPending: true }), timestamp: 3 },
|
||||
{ ...rxn('💬'), fromId: uuid() },
|
||||
{ ...rxn('🥀', { isPending: true }), timestamp: 1 },
|
||||
{ ...rxn('🌹', { isPending: true }), timestamp: 2 },
|
||||
];
|
||||
const reaction = rxn('😀');
|
||||
const newReactions = addOutgoingReaction(oldReactions, reaction, true);
|
||||
assert.deepStrictEqual(newReactions, [...oldReactions, reaction]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNewestPendingOutgoingReaction', () => {
|
||||
|
@ -199,21 +214,36 @@ describe('reaction utilities', () => {
|
|||
|
||||
const reactions = [star, none, { ...rxn('🔕'), timestamp: 1 }];
|
||||
|
||||
function getMessage(): MessageAttributesType {
|
||||
const now = Date.now();
|
||||
return {
|
||||
conversationId: uuid(),
|
||||
id: uuid(),
|
||||
received_at: now,
|
||||
sent_at: now,
|
||||
timestamp: now,
|
||||
type: 'incoming',
|
||||
};
|
||||
}
|
||||
|
||||
it("does nothing if the reaction isn't in the list", () => {
|
||||
const result = markOutgoingReactionSent(
|
||||
reactions,
|
||||
rxn('🥀', { isPending: true }),
|
||||
[uuid()]
|
||||
[uuid()],
|
||||
getMessage()
|
||||
);
|
||||
assert.deepStrictEqual(result, reactions);
|
||||
});
|
||||
|
||||
it('updates reactions to be partially sent', () => {
|
||||
[star, none].forEach(reaction => {
|
||||
const result = markOutgoingReactionSent(reactions, reaction, [
|
||||
uuid1,
|
||||
uuid2,
|
||||
]);
|
||||
const result = markOutgoingReactionSent(
|
||||
reactions,
|
||||
reaction,
|
||||
[uuid1, uuid2],
|
||||
getMessage()
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
result.find(re => re.emoji === reaction.emoji)
|
||||
?.isSentByConversationId,
|
||||
|
@ -227,11 +257,12 @@ describe('reaction utilities', () => {
|
|||
});
|
||||
|
||||
it('removes sent state if a reaction with emoji is fully sent', () => {
|
||||
const result = markOutgoingReactionSent(reactions, star, [
|
||||
uuid1,
|
||||
uuid2,
|
||||
uuid3,
|
||||
]);
|
||||
const result = markOutgoingReactionSent(
|
||||
reactions,
|
||||
star,
|
||||
[uuid1, uuid2, uuid3],
|
||||
getMessage()
|
||||
);
|
||||
|
||||
const newReaction = result.find(re => re.emoji === '⭐️');
|
||||
assert.isDefined(newReaction);
|
||||
|
@ -239,11 +270,12 @@ describe('reaction utilities', () => {
|
|||
});
|
||||
|
||||
it('removes a fully-sent reaction removal', () => {
|
||||
const result = markOutgoingReactionSent(reactions, none, [
|
||||
uuid1,
|
||||
uuid2,
|
||||
uuid3,
|
||||
]);
|
||||
const result = markOutgoingReactionSent(
|
||||
reactions,
|
||||
none,
|
||||
[uuid1, uuid2, uuid3],
|
||||
getMessage()
|
||||
);
|
||||
|
||||
assert(
|
||||
result.every(({ emoji }) => typeof emoji === 'string'),
|
||||
|
@ -252,13 +284,25 @@ describe('reaction utilities', () => {
|
|||
});
|
||||
|
||||
it('removes older reactions of mine', () => {
|
||||
const result = markOutgoingReactionSent(reactions, star, [
|
||||
uuid1,
|
||||
uuid2,
|
||||
uuid3,
|
||||
]);
|
||||
const result = markOutgoingReactionSent(
|
||||
reactions,
|
||||
star,
|
||||
[uuid1, uuid2, uuid3],
|
||||
getMessage()
|
||||
);
|
||||
|
||||
assert.isUndefined(result.find(re => re.emoji === '🔕'));
|
||||
});
|
||||
|
||||
it('does not remove my older reactions if they are on a story', () => {
|
||||
const result = markOutgoingReactionSent(
|
||||
reactions,
|
||||
star,
|
||||
[uuid1, uuid2, uuid3],
|
||||
{ ...getMessage(), type: 'story' }
|
||||
);
|
||||
|
||||
assert.isDefined(result.find(re => re.emoji === '🔕'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue