* Initial setup for new keyboard * fix the offset calculation onMove by adding isKeyboardFullyOpened * Done with the keyboard handling implementation * Handled keyboard focus and blured state using context * Added default height for input container * Android support * Tablet state handling * Fix for refreshing offset in list * Created a default context for mention post list * Fix linter errors * Fix tests * Minor * Fix the height issue for tablet view * Review comments * Dependency fix * Reveiw comment * keyboard animation only enabled with screen on top navigation * added tests * Added extra keyboard component for emoji picker (#9328) * Added extra keyboard component * handled swipe geature for extra keyboard * scroll to bottom visible on emoji picker * Check for stale event for keyboard geature area * fix keyboard stale event for mid-gesture change swipe direction * Closing emoji picker when message priority is opened * changing to thread view closes emoji picker * Close emoij picker and keyboard when attachment icons are clicked * handle android emoji picker behaviour * Remove emoji picker code * fix tests * Address reviev comments * Test fixes * usePreventDoubleTab for race condition and corrected comment on isInputAccessoryViewMode flag * File attachments option in bottom sheet quick action (#9331) * Added extra keyboard component * handled swipe geature for extra keyboard * File attachments option in bottom sheet quick action * Updated tests * i18n * fix test * Added tests * Review comment * fix tests * Integrated Emoji picker to Extra keyboard component. (#9339) * Added extra keyboard component * handled swipe geature for extra keyboard * scroll to bottom visible on emoji picker * Fix the post input container height change issue * Added emoji picker with search functionality * keyboard height not recorded fallback to default height, set search to false closing emoji picker * fix search funcitonality in android * scroll to end bottom dismissed with pressed * Fixed the scroll issue for android * fix the flickering post input issue * Wired up the emoji picker * Added keyboard and spaceback in emoji picker * intl extract * initial cursor position and simple calculation review comment * separated grapheme to utils file * Review comments * nitpick * Fix the bottom safe area and navigation stack restore fix * Fix test * disabled emoji picker in edit post screen * change the name of the variable to name it clarier * fix the tab gap issue in andriod * disabled the emoji skin tone change on andriod * fix the input container jump issue * fix the failing test * UX review * added white space after the attachment icon * When @ or / is press open keyboard on andriod also fix the scroll issue when keyboard is open * back bottom closes emoji picker and opening keyboard jump fix * reverted code * fixed the flickering issue of input and scroll position fix * Added BoR button * Fix the gap issue in thread * fix the warning reaminated issue when opening a channel * Fix the extra space issue between input and emoij picker * Minor fix for extra space between input and emoji picker * WIP * Fix thread view bottom safe area and gap between keyboard and searched emojis * WIP * refactor: make borConfig optional in BoRAction component * refactor: remove optional borConfig and simplify state initialization * Fix the keyboard issue in tablet * Fix the warning issue react-native-keyboard-controller * Fix tests * Fix the tablet search hight issue * Replaced the ExtraKeyboardProvider with KeyboardProvider from rnkc for permalink * WIP * Successfully toggeled bor status * Displayed bor chip in draft editor * Send bor post * Displayed bor chip in draft editor with wrapping * Added read receipt * Bottom sheet jump issue resolved * Search do not close after selecting emoji in search list * Added the bottom inset height in search emoji for android * integrated bor receipt count * fix buid issue * Added margin * Handled updating receipts on sync * test: add comprehensive tests for updateDraftBoRConfig * test: add initial test file for burn on read label component * test: add tests for BoRLabel component * test: update BoRLabel tests to use toBeVisible and remove formatTime mock * test: update BoR label tests to use renderWithIntl * refactor: remove getBaseProps and specify props inline in BoRLabel tests * refactor: improve test component formatting for readability * test: add validation for BoRLabel time display formats * test: add comprehensive tests for Burst on Read (BoR) functionality * WIP * test: add unit tests for BOR quick action component * test: add comprehensive tests for BoR quick action component * test: add missing props to QuickActions test setup * test: add tests for BoR quick action rendering * test: mock BoRQuickAction component for testing * test: add comprehensive tests for observeIsBoREnabled function * test: add initial test file for BOR read receipts screen * test: add comprehensive tests for BORReadReceipts component * test: add test cases for BOR read receipts display * test: add comprehensive tests for humanReadable format in formatTime * Added tests * lint fix * cleanup * cleanup * cleanup * cleanup * cleanup * i18n fix * Fix the cancel state to emoji picker * Prevented displaying BoR quick action in threads * Fixed a test * lint fix * review fixes * review fixes * i18n fix * fixed tests * review fixes * lint fix * restored podfile * Fixed WS event handling for receiving bor post receipnt * Fixed a test * fixed TS * Implemented burn post now for sender and receiver (#9401) * Implemented burn post now for sender and receiver * test: add tests for burnPostNow function * test: add test for delete post option * test: add comprehensive tests for DeletePostOption component * Added tests * Lint fix * minor improvemenmts * lint fix * reset unintended changes * reset unintended changes * Bor delete when read by all (#9407) * WIP * Updated for all read * cleanup * test: add comprehensive tests for handleBoRPostAllRevealed * test: add tests for BOR events in websocket event handler * feat: add comprehensive tests for shouldUpdateForBoRPost function * Added tests * Lint fix * fixed TS * fixed padding * UX fixes --------- Co-authored-by: Rajat Dabade <a-rajat.dabade@mattermost.com> Co-authored-by: Rajat Dabade <rajatdabade1997@gmail.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
445 lines
17 KiB
TypeScript
445 lines
17 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {removePost} from '@actions/local/post';
|
|
import {handleNewPostEvent, handlePostEdited} from '@actions/websocket/posts';
|
|
import {ActionType} from '@constants';
|
|
import {PostTypes} from '@constants/post';
|
|
import DatabaseManager from '@database/manager';
|
|
import {getPostById} from '@queries/servers/post';
|
|
import {getCurrentUser} from '@queries/servers/user';
|
|
import TestHelper from '@test/test_helper';
|
|
|
|
import {handleBoRPostRevealedEvent, handleBoRPostBurnedEvent, handleBoRPostAllRevealed} from './burn_on_read';
|
|
|
|
import type {ServerDatabase} from '@typings/database/database';
|
|
|
|
jest.mock('@actions/websocket/posts');
|
|
jest.mock('@queries/servers/post');
|
|
jest.mock('@actions/local/post');
|
|
jest.mock('@queries/servers/user');
|
|
|
|
const serverUrl = 'burnOnRead.test.com';
|
|
|
|
describe('WebSocket Burn on Read Actions', () => {
|
|
const post = TestHelper.fakePost({id: 'post1', channel_id: 'channel1', user_id: 'user1', create_at: 12345, message: 'hello'});
|
|
|
|
const mockedGetPostById = jest.mocked(getPostById);
|
|
const mockedHandleNewPostEvent = jest.mocked(handleNewPostEvent);
|
|
const mockedHandlePostEdited = jest.mocked(handlePostEdited);
|
|
const mockedRemovePost = jest.mocked(removePost);
|
|
const mockedGetCurrentUser = jest.mocked(getCurrentUser);
|
|
|
|
beforeEach(async () => {
|
|
await DatabaseManager.init([serverUrl]);
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await DatabaseManager.deleteServerDatabase(serverUrl);
|
|
});
|
|
|
|
describe('handleBoRPostRevealedEvent', () => {
|
|
const msg = {
|
|
data: {
|
|
post: JSON.stringify(post),
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
it('should handle new post when post does not exist locally', async () => {
|
|
mockedGetPostById.mockResolvedValue(undefined);
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedHandleNewPostEvent).toHaveBeenCalledWith(serverUrl, msg);
|
|
expect(mockedHandlePostEdited).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle post edited when post exists locally', async () => {
|
|
const existingPost = TestHelper.fakePostModel({id: 'post1'});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedHandleNewPostEvent).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should merge recipients from message with existing post recipients', async () => {
|
|
const existingPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
metadata: {recipients: ['user1', 'user2']},
|
|
});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
const msgWithRecipients = {
|
|
data: {
|
|
post: JSON.stringify(post),
|
|
recipients: ['user3', 'user4'],
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msgWithRecipients);
|
|
|
|
expect(mockedHandlePostEdited).toHaveBeenCalled();
|
|
const calledMsg = mockedHandlePostEdited.mock.calls[0][1];
|
|
const updatedPost = JSON.parse(calledMsg.data.post);
|
|
expect(updatedPost.metadata.recipients).toEqual(['user1', 'user2', 'user3', 'user4']);
|
|
});
|
|
|
|
it('should deduplicate recipients when merging', async () => {
|
|
const existingPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
metadata: {recipients: ['user1', 'user2']},
|
|
});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
const msgWithDuplicates = {
|
|
data: {
|
|
post: JSON.stringify(post),
|
|
recipients: ['user2', 'user3'],
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msgWithDuplicates);
|
|
|
|
expect(mockedHandlePostEdited).toHaveBeenCalled();
|
|
const calledMsg = mockedHandlePostEdited.mock.calls[0][1];
|
|
const updatedPost = JSON.parse(calledMsg.data.post);
|
|
expect(updatedPost.metadata.recipients).toEqual(['user1', 'user2', 'user3']);
|
|
});
|
|
|
|
it('should handle existing post without recipients metadata', async () => {
|
|
const existingPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
metadata: {},
|
|
});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
const msgWithRecipients = {
|
|
data: {
|
|
post: JSON.stringify(post),
|
|
recipients: ['user1', 'user2'],
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msgWithRecipients);
|
|
|
|
expect(mockedHandlePostEdited).toHaveBeenCalled();
|
|
const calledMsg = mockedHandlePostEdited.mock.calls[0][1];
|
|
const updatedPost = JSON.parse(calledMsg.data.post);
|
|
expect(updatedPost.metadata.recipients).toEqual(['user1', 'user2']);
|
|
});
|
|
|
|
it('should preserve existing post metadata when adding recipients', async () => {
|
|
const postWithMetadata = TestHelper.fakePost({
|
|
id: 'post1',
|
|
channel_id: 'channel1',
|
|
user_id: 'user1',
|
|
create_at: 12345,
|
|
message: 'hello',
|
|
metadata: {embeds: [{type: 'permalink'} as PostEmbed]},
|
|
});
|
|
const existingPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
metadata: {recipients: ['user1']},
|
|
});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
const msgWithMetadata = {
|
|
data: {
|
|
post: JSON.stringify(postWithMetadata),
|
|
recipients: ['user2'],
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msgWithMetadata);
|
|
|
|
expect(mockedHandlePostEdited).toHaveBeenCalled();
|
|
const calledMsg = mockedHandlePostEdited.mock.calls[0][1];
|
|
const updatedPost = JSON.parse(calledMsg.data.post);
|
|
expect(updatedPost.metadata.embeds).toEqual([{type: 'permalink'}]);
|
|
expect(updatedPost.metadata.recipients).toEqual(['user1', 'user2']);
|
|
});
|
|
|
|
it('should handle empty recipients array in websocket message', async () => {
|
|
const existingPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
metadata: {recipients: ['user1', 'user2']},
|
|
});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
const msgWithEmptyRecipients = {
|
|
data: {
|
|
post: JSON.stringify(post),
|
|
recipients: [],
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msgWithEmptyRecipients);
|
|
|
|
expect(mockedHandlePostEdited).toHaveBeenCalled();
|
|
const calledMsg = mockedHandlePostEdited.mock.calls[0][1];
|
|
const updatedPost = JSON.parse(calledMsg.data.post);
|
|
expect(updatedPost.metadata.recipients).toEqual(['user1', 'user2']);
|
|
});
|
|
|
|
it('should handle undefined recipients in websocket message', async () => {
|
|
const existingPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
metadata: {recipients: ['user1', 'user2']},
|
|
});
|
|
mockedGetPostById.mockResolvedValue(existingPost);
|
|
|
|
const msgWithoutRecipients = {
|
|
data: {
|
|
post: JSON.stringify(post),
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msgWithoutRecipients);
|
|
|
|
expect(mockedHandlePostEdited).toHaveBeenCalled();
|
|
const calledMsg = mockedHandlePostEdited.mock.calls[0][1];
|
|
const updatedPost = JSON.parse(calledMsg.data.post);
|
|
expect(updatedPost.metadata.recipients).toEqual(['user1', 'user2']);
|
|
});
|
|
|
|
it('should handle malformed post data gracefully', async () => {
|
|
const malformedMsg = {
|
|
data: {
|
|
post: 'invalid json',
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, malformedMsg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedHandleNewPostEvent).not.toHaveBeenCalled();
|
|
expect(mockedHandlePostEdited).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle missing server database gracefully', async () => {
|
|
await handleBoRPostRevealedEvent('invalid-server-url', msg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedHandleNewPostEvent).not.toHaveBeenCalled();
|
|
expect(mockedHandlePostEdited).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle missing operator gracefully', async () => {
|
|
// Mock a server database without an operator
|
|
DatabaseManager.serverDatabases[serverUrl] = {} as any;
|
|
|
|
await handleBoRPostRevealedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedHandleNewPostEvent).not.toHaveBeenCalled();
|
|
expect(mockedHandlePostEdited).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle JSON parse error gracefully', async () => {
|
|
const invalidJsonMsg = {
|
|
data: {
|
|
post: '{"invalid": json}',
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
const result = await handleBoRPostRevealedEvent(serverUrl, invalidJsonMsg);
|
|
|
|
expect(result).toEqual({});
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedHandleNewPostEvent).not.toHaveBeenCalled();
|
|
expect(mockedHandlePostEdited).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('handleBoRPostBurnedEvent', () => {
|
|
const burnOnReadPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
type: PostTypes.BURN_ON_READ,
|
|
});
|
|
|
|
const regularPost = TestHelper.fakePostModel({
|
|
id: 'post2',
|
|
type: '',
|
|
});
|
|
|
|
const msg = {
|
|
data: {
|
|
post_id: 'post1',
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
it('should remove burn-on-read post when it exists locally', async () => {
|
|
mockedGetPostById.mockResolvedValue(burnOnReadPost);
|
|
|
|
const result = await handleBoRPostBurnedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedRemovePost).toHaveBeenCalledWith(serverUrl, burnOnReadPost);
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('should not remove post when post does not exist locally', async () => {
|
|
mockedGetPostById.mockResolvedValue(undefined);
|
|
|
|
const result = await handleBoRPostBurnedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedRemovePost).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should not remove post when post is not burn-on-read type', async () => {
|
|
mockedGetPostById.mockResolvedValue(regularPost);
|
|
|
|
const result = await handleBoRPostBurnedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedRemovePost).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle missing server database gracefully', async () => {
|
|
const result = await handleBoRPostBurnedEvent('invalid-server-url', msg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedRemovePost).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle missing operator gracefully', async () => {
|
|
// Mock a server database without an operator
|
|
DatabaseManager.serverDatabases[serverUrl] = {} as any;
|
|
|
|
const result = await handleBoRPostBurnedEvent(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedRemovePost).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle errors gracefully and return error object', async () => {
|
|
mockedGetPostById.mockRejectedValue(new Error('Database error'));
|
|
|
|
const result = await handleBoRPostBurnedEvent(serverUrl, msg);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result!.error).toBeInstanceOf(Error);
|
|
expect(mockedRemovePost).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('handleBoRPostAllRevealed', () => {
|
|
const currentUser = TestHelper.fakeUserModel({id: 'user1'});
|
|
const burnOnReadPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
type: PostTypes.BURN_ON_READ,
|
|
userId: 'user1',
|
|
});
|
|
|
|
const msg = {
|
|
data: {
|
|
post_id: 'post1',
|
|
sender_expire_at: 67890,
|
|
},
|
|
} as WebSocketMessage;
|
|
|
|
beforeEach(() => {
|
|
const mockOperator = {
|
|
database: {},
|
|
handlePosts: jest.fn().mockResolvedValue({}),
|
|
};
|
|
DatabaseManager.serverDatabases[serverUrl] = {operator: mockOperator} as unknown as ServerDatabase;
|
|
});
|
|
|
|
it('should update post with expire_at when user owns the burn-on-read post', async () => {
|
|
const mockToApi = jest.fn().mockResolvedValue({
|
|
id: 'post1',
|
|
type: PostTypes.BURN_ON_READ,
|
|
user_id: 'user1',
|
|
metadata: {},
|
|
});
|
|
burnOnReadPost.toApi = mockToApi;
|
|
|
|
mockedGetPostById.mockResolvedValue(burnOnReadPost);
|
|
mockedGetCurrentUser.mockResolvedValue(currentUser);
|
|
|
|
const result = await handleBoRPostAllRevealed(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedGetCurrentUser).toHaveBeenCalledWith(expect.any(Object));
|
|
expect(mockToApi).toHaveBeenCalled();
|
|
expect(DatabaseManager.serverDatabases[serverUrl]?.operator?.handlePosts).toHaveBeenCalledWith({
|
|
actionType: ActionType.POSTS.RECEIVED_NEW,
|
|
order: ['post1'],
|
|
posts: [{
|
|
id: 'post1',
|
|
type: PostTypes.BURN_ON_READ,
|
|
user_id: 'user1',
|
|
metadata: {expire_at: 67890},
|
|
}],
|
|
prepareRecordsOnly: false,
|
|
});
|
|
expect(result).toHaveProperty('post');
|
|
expect(result!.post!.metadata.expire_at).toBe(67890);
|
|
});
|
|
|
|
it('should return null when post does not exist locally', async () => {
|
|
mockedGetPostById.mockResolvedValue(undefined);
|
|
|
|
const result = await handleBoRPostAllRevealed(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedGetCurrentUser).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null when user does not own the post', async () => {
|
|
const otherUsersBoRPost = TestHelper.fakePostModel({
|
|
id: 'post1',
|
|
type: PostTypes.BURN_ON_READ,
|
|
userId: 'otheruser1',
|
|
});
|
|
mockedGetPostById.mockResolvedValue(otherUsersBoRPost);
|
|
mockedGetCurrentUser.mockResolvedValue(currentUser);
|
|
|
|
const result = await handleBoRPostAllRevealed(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1');
|
|
expect(mockedGetCurrentUser).toHaveBeenCalledWith(expect.any(Object));
|
|
expect(DatabaseManager.serverDatabases[serverUrl]?.operator?.handlePosts).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle missing server database gracefully', async () => {
|
|
const result = await handleBoRPostAllRevealed('invalid-server-url', msg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedGetCurrentUser).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle missing operator gracefully', async () => {
|
|
DatabaseManager.serverDatabases[serverUrl] = {} as any;
|
|
|
|
const result = await handleBoRPostAllRevealed(serverUrl, msg);
|
|
|
|
expect(mockedGetPostById).not.toHaveBeenCalled();
|
|
expect(mockedGetCurrentUser).not.toHaveBeenCalled();
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle errors gracefully and return error object', async () => {
|
|
mockedGetPostById.mockRejectedValue(new Error('Database error'));
|
|
|
|
const result = await handleBoRPostAllRevealed(serverUrl, msg);
|
|
|
|
expect(result).toHaveProperty('error');
|
|
expect(result!.error).toBeInstanceOf(Error);
|
|
expect(mockedGetCurrentUser).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|