signal-desktop/ts/test-electron/state/ducks/stories_test.ts
Josh Perez 1f2cde6d04
Send edited messages support
Co-authored-by: Fedor Indutnyy <indutny@signal.org>
2023-04-20 09:31:59 -07:00

1016 lines
32 KiB
TypeScript

// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as sinon from 'sinon';
import casual from 'casual';
import type {
DispatchableViewStoryType,
StoryDataType,
} from '../../../state/ducks/stories';
import type { ConversationType } from '../../../state/ducks/conversations';
import type { MessageAttributesType } from '../../../model-types.d';
import type { StateType as RootStateType } from '../../../state/reducer';
import type { UUIDStringType } from '../../../types/UUID';
import { DurationInSeconds } from '../../../util/durations';
import { TEXT_ATTACHMENT, IMAGE_JPEG } from '../../../types/MIME';
import { ReadStatus } from '../../../messages/MessageReadStatus';
import {
StoryViewDirectionType,
StoryViewModeType,
} from '../../../types/Stories';
import { UUID } from '../../../types/UUID';
import { actions, getEmptyState } from '../../../state/ducks/stories';
import { noopAction } from '../../../state/ducks/noop';
import { reducer as rootReducer } from '../../../state/reducer';
import { dropNull } from '../../../util/dropNull';
describe('both/state/ducks/stories', () => {
const getEmptyRootState = () => ({
...rootReducer(undefined, noopAction()),
stories: getEmptyState(),
});
function getStoryMessage(id: string): MessageAttributesType {
const now = Date.now();
return {
conversationId: UUID.generate().toString(),
id,
received_at: now,
sent_at: now,
timestamp: now,
type: 'story',
};
}
describe('viewStory', () => {
function getMockConversation({
id: conversationId,
hideStory = false,
title,
}: Pick<ConversationType, 'id' | 'hideStory'> & {
title?: string;
}): ConversationType {
return {
acceptedMessageRequest: true,
badges: [],
hideStory,
id: conversationId,
isMe: false,
sharedGroupNames: [],
title: title || casual.username,
type: 'direct' as const,
};
}
function getStoryData(
messageId: string,
conversationId = UUID.generate().toString(),
timestampDelta = 0
): StoryDataType {
const now = Date.now();
return {
conversationId,
expirationStartTimestamp: now,
expireTimer: DurationInSeconds.DAY,
messageId,
readStatus: ReadStatus.Unread,
timestamp: now - timestampDelta,
type: 'story',
sourceDevice: 1,
};
}
function getStateFunction(
stories: Array<StoryDataType>,
conversationLookup: { [key: string]: ConversationType } = {},
unviewedStoryConversationIdsSorted: Array<string> = []
): () => RootStateType {
const rootState = getEmptyRootState();
return () => ({
...rootState,
conversations: {
...rootState.conversations,
conversationLookup,
},
stories: {
...rootState.stories,
selectedStoryData: {
currentIndex: 0,
messageId: '',
numStories: 0,
storyViewMode: StoryViewModeType.Unread,
unviewedStoryConversationIdsSorted,
},
stories,
},
});
}
const viewStory = actions.viewStory as DispatchableViewStoryType;
it('closes the viewer', () => {
const dispatch = sinon.spy();
viewStory({ closeViewer: true })(dispatch, getEmptyRootState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('closes the viewer when viewing a single story', () => {
const dispatch = sinon.spy();
viewStory({
storyId: UUID.generate().toString(),
storyViewMode: StoryViewModeType.Single,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('does not find a story', () => {
const dispatch = sinon.spy();
viewStory({
storyId: UUID.generate().toString(),
storyViewMode: StoryViewModeType.All,
})(dispatch, getEmptyRootState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('selects a specific story', () => {
const storyId = UUID.generate().toString();
const getState = getStateFunction([getStoryData(storyId)]);
const dispatch = sinon.spy();
viewStory({
storyId,
storyViewMode: StoryViewModeType.All,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId,
numStories: 1,
storyViewMode: StoryViewModeType.All,
unviewedStoryConversationIdsSorted: [],
viewTarget: undefined,
},
});
});
describe("navigating within a user's stories", () => {
it('selects the next story', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const conversationId = UUID.generate().toString();
const getState = getStateFunction([
getStoryData(storyId1, conversationId),
getStoryData(storyId2, conversationId),
getStoryData(storyId3, conversationId),
]);
const dispatch = sinon.spy();
viewStory({
storyId: storyId1,
storyViewMode: StoryViewModeType.User,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 1,
messageId: storyId2,
numStories: 3,
storyViewMode: StoryViewModeType.User,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('selects the prev story', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const conversationId = UUID.generate().toString();
const getState = getStateFunction([
getStoryData(storyId1, conversationId),
getStoryData(storyId2, conversationId),
getStoryData(storyId3, conversationId),
]);
const dispatch = sinon.spy();
viewStory({
storyId: storyId2,
storyViewMode: StoryViewModeType.User,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId1,
numStories: 3,
storyViewMode: StoryViewModeType.User,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('when in StoryViewModeType.User and we have reached the end, it closes the viewer', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const conversationId = UUID.generate().toString();
const getState = getStateFunction([
getStoryData(storyId1, conversationId),
getStoryData(storyId2, conversationId),
getStoryData(storyId3, conversationId),
]);
const dispatch = sinon.spy();
viewStory({
storyId: storyId3,
storyViewMode: StoryViewModeType.User,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
});
describe('unviewed stories', () => {
it('does not select hidden stories', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const conversationId = UUID.generate().toString();
const conversationIdHide = UUID.generate().toString();
const getState = getStateFunction(
[
{
...getStoryData(storyId1, conversationId),
readStatus: ReadStatus.Viewed,
},
// selector looks up conversation by sourceUuid
{
...getStoryData(storyId2, conversationIdHide),
sourceUuid: conversationIdHide,
},
{
...getStoryData(storyId3, conversationIdHide),
sourceUuid: conversationIdHide,
},
],
{
[conversationId]: getMockConversation({ id: conversationId }),
[conversationIdHide]: getMockConversation({
id: conversationIdHide,
hideStory: true,
}),
},
[conversationId]
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId1,
storyViewMode: StoryViewModeType.Unread,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('does not select stories that precede the currently viewed story', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const storyId4 = UUID.generate().toString();
const conversationId1 = UUID.generate().toString();
const conversationId2 = UUID.generate().toString();
const conversationId3 = UUID.generate().toString();
// conversationId3 - storyId4
// conversationId1 - storyId1, storyId3
// conversationId2 - storyId2
const getState = getStateFunction(
[
getStoryData(storyId1, conversationId1, 3),
{
...getStoryData(storyId2, conversationId2, 2),
readStatus: ReadStatus.Viewed,
},
getStoryData(storyId3, conversationId1, 1),
getStoryData(storyId4, conversationId3),
],
{
[conversationId1]: getMockConversation({ id: conversationId1 }),
[conversationId2]: getMockConversation({ id: conversationId2 }),
[conversationId3]: getMockConversation({ id: conversationId3 }),
},
[conversationId3, conversationId1, conversationId2]
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId2,
storyViewMode: StoryViewModeType.Unread,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('correctly goes to previous unviewed story', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const storyId4 = UUID.generate().toString();
const conversationId1 = UUID.generate().toString();
const conversationId2 = UUID.generate().toString();
const conversationId3 = UUID.generate().toString();
const unviewedStoryConversationIdsSorted = [
conversationId3,
conversationId1,
conversationId2,
];
const getState = getStateFunction(
[
getStoryData(storyId1, conversationId1, 3),
{
...getStoryData(storyId2, conversationId2, 2),
readStatus: ReadStatus.Viewed,
},
getStoryData(storyId3, conversationId1, 1),
getStoryData(storyId4, conversationId3),
],
{
[conversationId1]: getMockConversation({ id: conversationId1 }),
[conversationId2]: getMockConversation({ id: conversationId2 }),
[conversationId3]: getMockConversation({ id: conversationId3 }),
},
unviewedStoryConversationIdsSorted
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId2,
storyViewMode: StoryViewModeType.Unread,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId1,
numStories: 2,
storyViewMode: StoryViewModeType.Unread,
unviewedStoryConversationIdsSorted,
},
});
});
it('does not close the viewer when playing the next story', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const storyId4 = UUID.generate().toString();
const conversationId1 = UUID.generate().toString();
const conversationId2 = UUID.generate().toString();
const conversationId3 = UUID.generate().toString();
const unviewedStoryConversationIdsSorted = [
conversationId3,
conversationId2,
conversationId1,
];
const getState = getStateFunction(
[
getStoryData(storyId1, conversationId2, 3),
getStoryData(storyId2, conversationId1, 2),
getStoryData(storyId3, conversationId2, 1),
{
...getStoryData(storyId4, conversationId3),
readStatus: ReadStatus.Viewed,
},
],
{
[conversationId1]: getMockConversation({ id: conversationId1 }),
[conversationId2]: getMockConversation({ id: conversationId2 }),
[conversationId3]: getMockConversation({ id: conversationId3 }),
},
unviewedStoryConversationIdsSorted
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId4,
storyViewMode: StoryViewModeType.Unread,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId1,
numStories: 2,
storyViewMode: StoryViewModeType.Unread,
unviewedStoryConversationIdsSorted,
},
});
});
it('closes the viewer when there are no more unviewed stories', () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const conversationId1 = UUID.generate().toString();
const conversationId2 = UUID.generate().toString();
const getState = getStateFunction(
[
{
...getStoryData(storyId1, conversationId1),
readStatus: ReadStatus.Viewed,
},
{
...getStoryData(storyId2, conversationId2),
readStatus: ReadStatus.Viewed,
},
],
{
[conversationId1]: getMockConversation({ id: conversationId1 }),
[conversationId2]: getMockConversation({ id: conversationId2 }),
},
[conversationId1]
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId1,
storyViewMode: StoryViewModeType.Unread,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
});
describe('paging through sent stories', () => {
function getSentStoryReduxData() {
const distributionListId1 = UUID.generate().toString();
const distributionListId2 = UUID.generate().toString();
const storyDistributionLists = {
distributionLists: [
{
id: distributionListId1,
name: 'List 1',
allowsReplies: true,
isBlockList: false,
memberUuids: [
UUID.generate().toString(),
UUID.generate().toString(),
UUID.generate().toString(),
],
},
{
id: distributionListId2,
name: 'List 2',
allowsReplies: true,
isBlockList: false,
memberUuids: [
UUID.generate().toString(),
UUID.generate().toString(),
UUID.generate().toString(),
],
},
],
};
const ourConversationId = UUID.generate().toString();
const groupConversationId = UUID.generate().toString();
function getMyStoryData(
messageId: string,
storyDistributionListId?: string,
timestampDelta = 0
): StoryDataType {
const now = Date.now();
return {
conversationId: storyDistributionListId
? ourConversationId
: groupConversationId,
expirationStartTimestamp: now,
expireTimer: DurationInSeconds.DAY,
messageId,
readStatus: ReadStatus.Unread,
sendStateByConversationId: {},
storyDistributionListId,
timestamp: now - timestampDelta,
type: 'story',
sourceDevice: 1,
};
}
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const storyId4 = UUID.generate().toString();
const storyId5 = UUID.generate().toString();
const myStories = [
getMyStoryData(storyId1, distributionListId1, 5),
getMyStoryData(storyId2, distributionListId2, 4),
getMyStoryData(storyId3, distributionListId1, 3),
getMyStoryData(storyId4, undefined, 2), // group story
getMyStoryData(storyId5, distributionListId2, 1),
];
const rootState = getEmptyRootState();
return {
storyId1,
storyId2,
storyId3,
storyId4,
storyId5,
getState: () => ({
...rootState,
conversations: {
...rootState.conversations,
conversationLookup: {
[groupConversationId]: getMockConversation({
id: groupConversationId,
title: 'Group',
}),
},
},
storyDistributionLists,
stories: {
...rootState.stories,
stories: myStories,
},
}),
};
}
it('closes the viewer when hitting next at the last item', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId3 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId3,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('closes the viewer when hitting prev at the first item', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId2 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId2,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: undefined,
});
});
it('goes to next story within a distribution list', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId1, storyId3 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId1,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 1,
messageId: storyId3,
numStories: 2,
storyViewMode: StoryViewModeType.MyStories,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('goes to prev story within a distribution list', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId1, storyId3 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId3,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId1,
numStories: 2,
storyViewMode: StoryViewModeType.MyStories,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('goes to the next distribution list', () => {
const { getState, storyId4, storyId1 } = getSentStoryReduxData();
const dispatch = sinon.spy();
viewStory({
storyId: storyId4,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId1,
numStories: 2,
storyViewMode: StoryViewModeType.MyStories,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('goes to the prev distribution list', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId4, storyId5 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId4,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 1,
messageId: storyId5,
numStories: 2,
storyViewMode: StoryViewModeType.MyStories,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('goes next to a group story', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId4, storyId5 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId5,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId4,
numStories: 1,
storyViewMode: StoryViewModeType.MyStories,
unviewedStoryConversationIdsSorted: [],
},
});
});
it('goes prev to a group story', () => {
const { getState, ...reduxData } = getSentStoryReduxData();
const { storyId1, storyId4 } = reduxData;
const dispatch = sinon.spy();
viewStory({
storyId: storyId1,
storyViewMode: StoryViewModeType.MyStories,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId4,
numStories: 1,
storyViewMode: StoryViewModeType.MyStories,
unviewedStoryConversationIdsSorted: [],
},
});
});
});
describe('paging through collections of stories', () => {
function getViewedStoryData(
storyId: string,
conversationId?: UUIDStringType,
timestampDelta = 0
): StoryDataType {
return {
...getStoryData(storyId, conversationId, timestampDelta),
readStatus: ReadStatus.Viewed,
};
}
it("goes to the next user's stories", () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const conversationId2 = UUID.generate().toString();
const conversationId1 = UUID.generate().toString();
const getState = getStateFunction(
[
getViewedStoryData(storyId1, conversationId1, 0),
getViewedStoryData(storyId2, conversationId2, 1),
getViewedStoryData(storyId3, conversationId2, 2),
],
{
[conversationId1]: getMockConversation({ id: conversationId1 }),
[conversationId2]: getMockConversation({ id: conversationId2 }),
}
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId1,
storyViewMode: StoryViewModeType.All,
viewDirection: StoryViewDirectionType.Next,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId2,
numStories: 2,
storyViewMode: StoryViewModeType.All,
unviewedStoryConversationIdsSorted: [],
},
});
});
it("goes to the prev user's stories", () => {
const storyId1 = UUID.generate().toString();
const storyId2 = UUID.generate().toString();
const storyId3 = UUID.generate().toString();
const conversationId1 = UUID.generate().toString();
const conversationId2 = UUID.generate().toString();
const getState = getStateFunction(
[
getViewedStoryData(storyId1, conversationId2),
getViewedStoryData(storyId2, conversationId1),
getViewedStoryData(storyId3, conversationId2),
],
{
[conversationId1]: getMockConversation({ id: conversationId1 }),
[conversationId2]: getMockConversation({ id: conversationId2 }),
}
);
const dispatch = sinon.spy();
viewStory({
storyId: storyId2,
storyViewMode: StoryViewModeType.All,
viewDirection: StoryViewDirectionType.Previous,
})(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/VIEW_STORY',
payload: {
currentIndex: 0,
messageId: storyId1,
numStories: 2,
storyViewMode: StoryViewModeType.All,
unviewedStoryConversationIdsSorted: [],
},
});
});
});
});
describe('queueStoryDownload', () => {
const { queueStoryDownload } = actions;
it('no attachment, no dispatch', async function test() {
const storyId = UUID.generate().toString();
const messageAttributes = getStoryMessage(storyId);
window.MessageController.register(storyId, messageAttributes);
const dispatch = sinon.spy();
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
sinon.assert.notCalled(dispatch);
});
it('downloading, no dispatch', async function test() {
const storyId = UUID.generate().toString();
const messageAttributes = {
...getStoryMessage(storyId),
attachments: [
{
contentType: IMAGE_JPEG,
downloadJobId: UUID.generate().toString(),
pending: true,
size: 0,
},
],
};
window.MessageController.register(storyId, messageAttributes);
const dispatch = sinon.spy();
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
sinon.assert.notCalled(dispatch);
});
it('downloaded, no dispatch', async function test() {
const storyId = UUID.generate().toString();
const messageAttributes = {
...getStoryMessage(storyId),
attachments: [
{
contentType: IMAGE_JPEG,
path: 'image.jpg',
url: '/path/to/image.jpg',
size: 0,
},
],
};
window.MessageController.register(storyId, messageAttributes);
const dispatch = sinon.spy();
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
sinon.assert.notCalled(dispatch);
});
it('not downloaded, queued for download', async function test() {
const storyId = UUID.generate().toString();
const messageAttributes = {
...getStoryMessage(storyId),
attachments: [
{
contentType: IMAGE_JPEG,
digest: 'digest',
size: 0,
},
],
};
const rootState = getEmptyRootState();
const getState = () => ({
...rootState,
stories: {
...rootState.stories,
stories: [
{
...messageAttributes,
sourceDevice: 1,
attachment: messageAttributes.attachments[0],
messageId: messageAttributes.id,
expireTimer: messageAttributes.expireTimer,
expirationStartTimestamp: dropNull(
messageAttributes.expirationStartTimestamp
),
},
],
},
});
window.MessageController.register(storyId, messageAttributes);
const dispatch = sinon.spy();
await queueStoryDownload(storyId)(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/QUEUE_STORY_DOWNLOAD',
payload: storyId,
});
});
it('preview not downloaded, queued for download', async function test() {
const storyId = UUID.generate().toString();
const preview = {
url: 'https://signal.org',
image: {
contentType: IMAGE_JPEG,
digest: 'digest-1',
size: 0,
},
};
const messageAttributes = {
...getStoryMessage(storyId),
attachments: [
{
contentType: TEXT_ATTACHMENT,
digest: 'digest-2',
size: 0,
textAttachment: {
preview,
},
},
],
preview: [preview],
};
const rootState = getEmptyRootState();
const getState = () => ({
...rootState,
stories: {
...rootState.stories,
stories: [
{
...messageAttributes,
sourceDevice: 1,
attachment: messageAttributes.attachments[0],
messageId: messageAttributes.id,
expireTimer: messageAttributes.expireTimer,
expirationStartTimestamp: dropNull(
messageAttributes.expirationStartTimestamp
),
},
],
},
});
window.MessageController.register(storyId, messageAttributes);
const dispatch = sinon.spy();
await queueStoryDownload(storyId)(dispatch, getState, null);
sinon.assert.calledWith(dispatch, {
type: 'stories/QUEUE_STORY_DOWNLOAD',
payload: storyId,
});
});
});
});