mattermost-mobile/app/actions/websocket/posts.test.ts
Rajat Dabade 113f9d942c
Typescript support and view component for permalink with user and message (#9052)
* typescript and view component for permalink with user and message

* Old post edited handling in permalink

* Added test and update flag value to EnablePermalinkPreview

* Added test for permalink_preview component

* Added test for content/index.tsx for permalink

* Addressed review comments

* Unit test for missing file and review comments

* Added test to check handlePostEdited permalink sync only calls one time only

* Change TouchableOpacity to Pressable

* When user not found fetch the user from the server

* Removed the redundant test in the test for permalink_preview/index?

* ts to tsx

* Removed the circular dependency

* Address review comments

* displayname fallback

* remove permalink when permalink post is deleted

* UX review comments

* Linter fixes

* Test fixes

* Address review comments

* Minor

* Some more review comments

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
2025-09-10 17:12:16 +05:30

629 lines
28 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {DeviceEventEmitter} from 'react-native';
import {markChannelAsViewed, markChannelAsUnread, storeMyChannelsForTeam, updateLastPostAt} from '@actions/local/channel';
import {addPostAcknowledgement, markPostAsDeleted, removePostAcknowledgement} from '@actions/local/post';
import {updateThread} from '@actions/local/thread';
import {fetchChannelStats, fetchMyChannel} from '@actions/remote/channel';
import {fetchPostAuthors} from '@actions/remote/post';
import {fetchThread} from '@actions/remote/thread';
import {fetchMissingProfilesByIds} from '@actions/remote/user';
import {Events, Screens} from '@constants';
import {PostTypes} from '@constants/post';
import DatabaseManager from '@database/manager';
import {PostsInChannelModel} from '@database/models/server';
import {getChannelById, getMyChannel} from '@queries/servers/channel';
import {getPostById, syncPermalinkPreviewsForEditedPost} from '@queries/servers/post';
import {getCurrentUserId, getCurrentChannelId} from '@queries/servers/system';
import {getIsCRTEnabled} from '@queries/servers/thread';
import EphemeralStore from '@store/ephemeral_store';
import NavigationStore from '@store/navigation_store';
import TestHelper from '@test/test_helper';
import {isTablet} from '@utils/helpers';
import {shouldIgnorePost} from '@utils/post';
import {handleNewPostEvent, handlePostEdited, handlePostDeleted, handlePostUnread, handlePostAcknowledgementAdded, handlePostAcknowledgementRemoved} from './posts';
import type ServerDataOperator from '@database/operator/server_data_operator';
jest.mock('@queries/servers/post');
jest.mock('@queries/servers/channel');
jest.mock('@queries/servers/system');
jest.mock('@queries/servers/thread');
jest.mock('@actions/local/channel');
jest.mock('@actions/local/post');
jest.mock('@actions/local/thread');
jest.mock('@actions/remote/channel');
jest.mock('@actions/remote/post');
jest.mock('@actions/remote/thread');
jest.mock('@actions/remote/user');
jest.mock('@utils/helpers');
jest.mock('@utils/post', () => ({
...jest.requireActual('@utils/post'),
shouldIgnorePost: jest.fn(),
}));
jest.mock('@utils/permalink_sync');
const serverUrl = 'baseHandler.test.com';
describe('WebSocket Post Actions', () => {
let operator: ServerDataOperator;
const post = TestHelper.fakePost({id: 'post1', channel_id: 'channel1', user_id: 'user1', create_at: 12345, message: 'hello'});
const postModels = [TestHelper.fakePostModel({channelId: post.channel_id, userId: post.user_id, message: post.message, isPinned: true, createAt: 12345})];
const myChannelModel = TestHelper.fakeMyChannelModel({id: 'channel1', manuallyUnread: false, messageCount: 4, mentionsCount: 0, lastViewedAt: 1});
const mockedGetPostById = jest.mocked(getPostById);
const mockedUpdateLastPostAt = jest.mocked(updateLastPostAt);
const mockedMarkChannelAsViewed = jest.mocked(markChannelAsViewed);
const mockedGetCurrentUserId = jest.mocked(getCurrentUserId);
const mockedGetCurrentChannelId = jest.mocked(getCurrentChannelId);
const mockedFetchPostAuthors = jest.mocked(fetchPostAuthors);
const mockedIsTablet = jest.mocked(isTablet);
const mockedGetMyChannel = jest.mocked(getMyChannel);
const mockedGetIsCRTEnabled = jest.mocked(getIsCRTEnabled);
const mockedShouldIgnorePost = jest.mocked(shouldIgnorePost);
const mockedFetchMyChannel = jest.mocked(fetchMyChannel);
const mockedStoreMyChannelsForTeam = jest.mocked(storeMyChannelsForTeam);
const mockedFetchChannelStats = jest.mocked(fetchChannelStats);
const mockedGetChannelById = jest.mocked(getChannelById);
const mockedMarkPostAsDeleted = jest.mocked(markPostAsDeleted);
const mockedUpdateThread = jest.mocked(updateThread);
const mockedFetchThread = jest.mocked(fetchThread);
const mockedMarkChannelAsUnread = jest.mocked(markChannelAsUnread);
const mockedAddPostAcknowledgement = jest.mocked(addPostAcknowledgement);
const mockedFetchMissingProfilesByIds = jest.mocked(fetchMissingProfilesByIds);
const mockedRemovePostAcknowledgement = jest.mocked(removePostAcknowledgement);
const mockedSyncPermalinkPreviewsForEditedPost = jest.mocked(syncPermalinkPreviewsForEditedPost);
beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
jest.clearAllMocks();
});
afterEach(async () => {
await DatabaseManager.deleteServerDatabase(serverUrl);
});
describe('handleNewPostEvent', () => {
const msg = {
data: {
post: JSON.stringify(post),
mentions: ['user1'],
},
} as WebSocketMessage;
const mockedGetScreensInStack = jest.spyOn(NavigationStore, 'getScreensInStack');
mockedGetPostById.mockResolvedValue(undefined);
mockedUpdateLastPostAt.mockResolvedValue({member: myChannelModel});
mockedMarkChannelAsViewed.mockResolvedValue({member: myChannelModel});
mockedGetCurrentUserId.mockResolvedValue('user1');
mockedGetCurrentChannelId.mockResolvedValue('channel1');
mockedFetchPostAuthors.mockResolvedValue({authors: []});
mockedGetScreensInStack.mockReturnValue([Screens.CHANNEL]);
mockedIsTablet.mockReturnValue(false);
it('should handle new post event - channel membership present', async () => {
const userModel = TestHelper.fakeUserModel({id: 'user1', username: 'username1'});
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
jest.spyOn(operator, 'handleUsers').mockResolvedValue([userModel]);
mockedFetchPostAuthors.mockResolvedValueOnce({authors: [TestHelper.fakeUser({id: 'user1', username: 'username1'})]});
mockedGetMyChannel.mockResolvedValue(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
mockedShouldIgnorePost.mockReturnValue(false);
await handleNewPostEvent(serverUrl, msg);
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith([
userModel,
expect.objectContaining({
id: 'channel1',
lastViewedAt: 1,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 4,
}),
postModels[0],
], 'handleNewPostEvent');
});
it('should handle new post event - without channel membership present', async () => {
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
mockedGetMyChannel.mockResolvedValueOnce(undefined);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [TestHelper.fakeChannelMember({user_id: 'user1', channel_id: 'channel1'})]});
mockedStoreMyChannelsForTeam.mockResolvedValue({models: [myChannelModel], error: undefined});
mockedGetMyChannel.mockResolvedValueOnce(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
mockedShouldIgnorePost.mockReturnValue(true);
await handleNewPostEvent(serverUrl, msg);
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith(postModels, 'handleNewPostEvent');
});
it('should handle new post event - CRT enabled, manually unread', async () => {
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
mockedGetMyChannel.mockResolvedValue(TestHelper.fakeMyChannelModel({...myChannelModel, manuallyUnread: true}));
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedShouldIgnorePost.mockReturnValue(false);
mockedMarkChannelAsUnread.mockResolvedValue({member: myChannelModel});
await handleNewPostEvent(serverUrl, msg);
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith([expect.objectContaining({
id: 'channel1',
lastViewedAt: 1,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 4,
}), postModels[0]], 'handleNewPostEvent');
});
it('should handle new post event - out of order ws, CRT on, root id', async () => {
const newPost = TestHelper.fakePost({...post, root_id: 'post2'});
jest.spyOn(EphemeralStore, 'getLastPostWebsocketEvent').mockReturnValueOnce({deleted: true, post: newPost});
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
mockedGetMyChannel.mockResolvedValue(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedShouldIgnorePost.mockReturnValue(false);
await handleNewPostEvent(serverUrl, {data: {post: JSON.stringify(newPost), mentions: []}} as WebSocketMessage);
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: 'post2',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
it('should handle new post event - from webhook and is tablet', async () => {
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
mockedGetMyChannel.mockResolvedValue(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
mockedShouldIgnorePost.mockReturnValue(false);
mockedIsTablet.mockReturnValueOnce(true);
mockedGetScreensInStack.mockReturnValue([]);
await handleNewPostEvent(serverUrl, {data: {post: JSON.stringify({...post, props: {from_webhook: 'true'}}), mentions: []}} as WebSocketMessage);
expect(mockedGetScreensInStack).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith([expect.objectContaining({
id: 'channel1',
lastViewedAt: 1,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 4,
}), postModels[0]], 'handleNewPostEvent');
});
it('should handle new post event - no operator', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
await handleNewPostEvent('junk', msg);
expect(mockedGetCurrentUserId).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
it('should handle new post event - existing post', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
mockedGetPostById.mockResolvedValueOnce(postModels[0]);
await handleNewPostEvent(serverUrl, msg);
expect(mockedGetIsCRTEnabled).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
it('should handle new post event - malformed post data', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
mockedGetPostById.mockResolvedValueOnce(postModels[0]);
await handleNewPostEvent(serverUrl, {data: undefined} as WebSocketMessage);
expect(mockedGetCurrentUserId).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
});
describe('handlePostEdited', () => {
const editedPost = {id: 'post1', channel_id: 'channel1', user_id: 'user1', is_pinned: false, edit_at: 54321, message: 'edited message'};
const msg = {
data: {
post: JSON.stringify(editedPost),
},
} as WebSocketMessage;
const permalinkPostModel = TestHelper.fakePostModel({id: 'permalink_post', channelId: 'channel2'});
beforeEach(() => {
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([]);
mockedFetchPostAuthors.mockResolvedValue({authors: []});
mockedGetIsCRTEnabled.mockResolvedValue(false);
});
it('should handle post edited event - post exists locally', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([]);
await handlePostEdited(serverUrl, msg);
expect(mockedSyncPermalinkPreviewsForEditedPost).toHaveBeenCalledWith(expect.any(Object), editedPost);
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
expect(mockedFetchChannelStats).toHaveBeenCalledWith(serverUrl, 'channel1');
expect(batchRecordsSpy).toHaveBeenCalledWith([expect.any(PostsInChannelModel)], 'handlePostEdited');
});
it('should handle post edited event - post exists with permalink updates', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([permalinkPostModel]);
await handlePostEdited(serverUrl, msg);
expect(mockedSyncPermalinkPreviewsForEditedPost).toHaveBeenCalledWith(expect.any(Object), editedPost);
expect(batchRecordsSpy).toHaveBeenCalledWith(
expect.arrayContaining([
permalinkPostModel,
expect.any(PostsInChannelModel),
]),
'handlePostEdited',
);
});
it('should handle post edited event - post missing but has permalink updates (key fix)', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
const addEditingPostSpy = jest.spyOn(EphemeralStore, 'addEditingPost');
mockedGetPostById.mockResolvedValue(undefined);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([permalinkPostModel]);
await handlePostEdited(serverUrl, msg);
expect(mockedSyncPermalinkPreviewsForEditedPost).toHaveBeenCalledWith(expect.any(Object), editedPost);
expect(addEditingPostSpy).toHaveBeenCalledWith(serverUrl, editedPost);
expect(batchRecordsSpy).toHaveBeenCalledWith([permalinkPostModel], 'handlePostEdited - permalink sync only');
});
it('should handle post edited event - post missing and no permalink updates', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
const addEditingPostSpy = jest.spyOn(EphemeralStore, 'addEditingPost');
mockedGetPostById.mockResolvedValue(undefined);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([]);
await handlePostEdited(serverUrl, msg);
expect(mockedSyncPermalinkPreviewsForEditedPost).toHaveBeenCalledWith(expect.any(Object), editedPost);
expect(addEditingPostSpy).toHaveBeenCalledWith(serverUrl, editedPost);
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
it('should handle permalink sync errors gracefully', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
const logWarningSpy = jest.spyOn(require('@utils/log'), 'logWarning');
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedSyncPermalinkPreviewsForEditedPost.mockRejectedValue(new Error('Permalink sync failed'));
await handlePostEdited(serverUrl, msg);
expect(logWarningSpy).toHaveBeenCalledWith('Failed to sync permalink previews for edited post:', expect.any(Error));
expect(batchRecordsSpy).toHaveBeenCalledWith([expect.any(PostsInChannelModel)], 'handlePostEdited');
});
it('should handle post edited event - no operator', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
await handlePostEdited('junk', msg);
expect(mockedSyncPermalinkPreviewsForEditedPost).not.toHaveBeenCalled();
expect(mockedGetPostById).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
it('should handle post edited event - malformed post data', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
await handlePostEdited(serverUrl, {data: undefined} as WebSocketMessage);
expect(mockedSyncPermalinkPreviewsForEditedPost).not.toHaveBeenCalled();
expect(mockedGetPostById).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
it('should not update create_at for ephemeral messages', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
const ephemeralPost = TestHelper.fakePost({type: PostTypes.EPHEMERAL, create_at: 0});
const ephemeralMsg = {
data: {post: JSON.stringify(ephemeralPost)},
} as WebSocketMessage;
mockedGetPostById.mockResolvedValueOnce(postModels[0]);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([]);
await handlePostEdited(serverUrl, ephemeralMsg);
expect(batchRecordsSpy).toHaveBeenCalledWith(expect.arrayContaining([expect.objectContaining({createAt: 12345})]), 'handlePostEdited');
});
it('should not double batch permalink models in handlePostEdited', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([permalinkPostModel]);
await handlePostEdited(serverUrl, msg);
expect(batchRecordsSpy).toHaveBeenCalledTimes(1);
expect(batchRecordsSpy).toHaveBeenCalledWith(
expect.arrayContaining([
permalinkPostModel,
expect.any(PostsInChannelModel),
]),
'handlePostEdited',
);
expect(batchRecordsSpy).not.toHaveBeenCalledWith(
[permalinkPostModel],
'handlePostEdited - permalink sync only',
);
});
it('should batch only permalink models when post does not exist locally', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
mockedGetPostById.mockResolvedValue(undefined);
mockedSyncPermalinkPreviewsForEditedPost.mockResolvedValue([permalinkPostModel]);
await handlePostEdited(serverUrl, msg);
expect(batchRecordsSpy).toHaveBeenCalledTimes(1);
expect(batchRecordsSpy).toHaveBeenCalledWith(
[permalinkPostModel],
'handlePostEdited - permalink sync only',
);
expect(batchRecordsSpy).not.toHaveBeenCalledWith(
expect.anything(),
'handlePostEdited',
);
});
});
describe('handlePostDeleted', () => {
const deletedPost = {id: 'post1', channel_id: 'channel1', user_id: 'user1', root_id: 'root1', reply_count: 1};
const msg = {
data: {
post: JSON.stringify(deletedPost),
},
} as WebSocketMessage;
const threadModel = TestHelper.fakeThreadModel({viewedAt: 1, id: 'thread1'});
it('should handle post deleted event', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedMarkPostAsDeleted.mockResolvedValue({model: postModels[0]});
mockedUpdateThread.mockResolvedValue({model: threadModel});
mockedGetChannelById.mockResolvedValue(TestHelper.fakeChannelModel({id: 'channel1', teamId: 'team1'}));
await handlePostDeleted(serverUrl, msg);
expect(mockedMarkPostAsDeleted).toHaveBeenCalledWith(serverUrl, expect.any(Object), true);
expect(mockedUpdateThread).toHaveBeenCalledWith(serverUrl, 'root1', {reply_count: 0}, true);
expect(mockedFetchThread).toHaveBeenCalledWith(serverUrl, 'team1', 'root1');
expect(batchRecordsSpy).toHaveBeenCalledWith([postModels[0], threadModel], 'handlePostDeleted');
});
it('should handle post deleted event - missing thread and channel', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedMarkPostAsDeleted.mockResolvedValue({model: postModels[0]});
mockedUpdateThread.mockResolvedValue({error: 'not found'});
mockedGetChannelById.mockResolvedValue(undefined);
await handlePostDeleted(serverUrl, msg);
expect(mockedMarkPostAsDeleted).toHaveBeenCalledWith(serverUrl, expect.any(Object), true);
expect(mockedUpdateThread).toHaveBeenCalledWith(serverUrl, 'root1', {reply_count: 0}, true);
expect(mockedFetchThread).not.toHaveBeenCalled();
expect(batchRecordsSpy).toHaveBeenCalledWith(postModels, 'handlePostDeleted');
});
it('should handle post deleted event - missing delete model and no root id', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedMarkPostAsDeleted.mockResolvedValue({error: 'not found'});
await handlePostDeleted(serverUrl, {data: {post: JSON.stringify({...deletedPost, root_id: ''})}} as WebSocketMessage);
expect(mockedMarkPostAsDeleted).toHaveBeenCalledWith(serverUrl, expect.any(Object), true);
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
});
describe('handlePostUnread', () => {
const msg = {
broadcast: {
channel_id: 'channel1',
team_id: 'team1',
},
data: {
mention_count: 1,
mention_count_root: 1,
msg_count: 1,
msg_count_root: 1,
last_viewed_at: 12345,
},
} as WebSocketMessage;
it('should handle post unread event', async () => {
mockedGetMyChannel.mockResolvedValue(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [TestHelper.fakeChannelMember({user_id: 'user1', channel_id: 'channel1'})]});
mockedMarkChannelAsUnread.mockResolvedValue({member: myChannelModel});
await handlePostUnread(serverUrl, msg);
expect(mockedMarkChannelAsUnread).toHaveBeenCalledWith(serverUrl, 'channel1', 1, 1, 12345);
});
it('should handle post unread event - CRT enabled, manually marked read', async () => {
mockedGetMyChannel.mockResolvedValue(TestHelper.fakeMyChannelModel({...myChannelModel, manuallyUnread: true}));
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [TestHelper.fakeChannelMember({user_id: 'user1', channel_id: 'channel1'})]});
mockedMarkChannelAsUnread.mockResolvedValue({member: myChannelModel});
await handlePostUnread(serverUrl, msg);
expect(mockedMarkChannelAsUnread).not.toHaveBeenCalled();
});
it('should handle post unread event - no database', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
await handlePostUnread('junk', msg);
expect(mockedGetMyChannel).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
});
describe('handlePostAcknowledgementAdded', () => {
const msg = {
data: {
acknowledgement: JSON.stringify({user_id: 'user1', post_id: 'post1', acknowledged_at: 12345}),
},
} as WebSocketMessage;
mockedGetCurrentUserId.mockResolvedValue('user1');
it('should handle post acknowledgement added event', async () => {
jest.spyOn(EphemeralStore, 'isAcknowledgingPost').mockReturnValue(false);
await handlePostAcknowledgementAdded(serverUrl, msg);
expect(mockedAddPostAcknowledgement).toHaveBeenCalledWith(serverUrl, 'post1', 'user1', 12345);
expect(mockedFetchMissingProfilesByIds).toHaveBeenCalledWith(serverUrl, ['user1']);
});
it('should handle post acknowledgement added event - already acknowledging', async () => {
jest.spyOn(EphemeralStore, 'isAcknowledgingPost').mockReturnValue(true);
await handlePostAcknowledgementAdded(serverUrl, msg);
expect(mockedAddPostAcknowledgement).not.toHaveBeenCalled();
});
it('should handle post acknowledgement added event - no database', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
await handlePostAcknowledgementAdded('junk', msg);
expect(mockedGetCurrentUserId).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
});
describe('handlePostAcknowledgementRemoved', () => {
const msg = {
data: {
acknowledgement: JSON.stringify({user_id: 'user1', post_id: 'post1'}),
},
} as WebSocketMessage;
mockedGetCurrentUserId.mockResolvedValue('user1');
it('should handle post acknowledgement removed event', async () => {
jest.spyOn(EphemeralStore, 'isUnacknowledgingPost').mockReturnValue(false);
await handlePostAcknowledgementRemoved(serverUrl, msg);
expect(mockedRemovePostAcknowledgement).toHaveBeenCalledWith(serverUrl, 'post1', 'user1');
});
it('should handle post acknowledgement added event - already unacknowledging', async () => {
jest.spyOn(EphemeralStore, 'isUnacknowledgingPost').mockReturnValue(true);
await handlePostAcknowledgementRemoved(serverUrl, msg);
expect(mockedRemovePostAcknowledgement).not.toHaveBeenCalled();
});
it('should handle post acknowledgement removed event - no database', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
await handlePostAcknowledgementRemoved('junk', msg);
expect(mockedGetCurrentUserId).not.toHaveBeenCalled();
expect(batchRecordsSpy).not.toHaveBeenCalled();
});
});
});