mattermost-mobile/app/client/rest/posts.test.ts
Harshil Sharma 04de7b9745
Bor create post (#9393)
* 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>
2026-02-09 17:00:54 +05:30

330 lines
11 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import TestHelper from '@test/test_helper';
import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
import type {ClientPostsMix} from './posts';
describe('ClientPosts', () => {
let client: ClientPostsMix & ClientBase;
beforeEach(() => {
client = TestHelper.createClient();
client.doFetch = jest.fn();
});
test('createPost', async () => {
const post = {id: 'post_id', message: 'message'} as Post;
await client.createPost(post);
expect(client.doFetch).toHaveBeenCalledWith(
client.getPostsRoute(),
{method: 'post', body: post, noRetry: true},
);
});
test('updatePost', async () => {
const post = {id: 'post_id', message: 'updated message'} as Post;
await client.updatePost(post);
expect(client.doFetch).toHaveBeenCalledWith(
client.getPostRoute(post.id),
{method: 'put', body: post},
);
});
test('getPost', async () => {
const postId = 'post_id';
await client.getPost(postId);
expect(client.doFetch).toHaveBeenCalledWith(
client.getPostRoute(postId),
{method: 'get', groupLabel: undefined},
);
});
test('patchPost', async () => {
const postPatch = {id: 'post_id', message: 'patched message'};
await client.patchPost(postPatch);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postPatch.id)}/patch`,
{method: 'put', body: postPatch},
);
});
test('deletePost', async () => {
const postId = 'post_id';
await client.deletePost(postId);
expect(client.doFetch).toHaveBeenCalledWith(
client.getPostRoute(postId),
{method: 'delete'},
);
});
test('burnPostNow', async () => {
const postId = 'post_id';
await client.burnPostNow(postId);
expect(client.doFetch).toHaveBeenCalledWith(
client.getPostRoute(postId) + '/burn',
{method: 'delete'},
);
});
test('getPostThread', async () => {
const postId = 'post_id';
const options = {fetchThreads: true, collapsedThreads: false, collapsedThreadsExtended: false, direction: 'up'} as FetchPaginatedThreadOptions;
await client.getPostThread(postId, options);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/thread${buildQueryString({skipFetchThreads: !options.fetchThreads, collapsedThreads: options.collapsedThreads, collapsedThreadsExtended: options.collapsedThreadsExtended, direction: options.direction, perPage: PER_PAGE_DEFAULT})}`,
{method: 'get', groupLabel: undefined},
);
// Test with default options
await client.getPostThread(postId, {fetchAll: true});
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/thread${buildQueryString({skipFetchThreads: false, collapsedThreads: false, collapsedThreadsExtended: false, direction: 'up', perPage: PER_PAGE_DEFAULT})}`,
{method: 'get', groupLabel: undefined},
);
});
test('getPosts', async () => {
const channelId = 'channel_id';
await client.getPosts(channelId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getChannelRoute(channelId)}/posts${buildQueryString({page: 0, per_page: PER_PAGE_DEFAULT, collapsedThreads: false, collapsedThreadsExtended: false})}`,
{method: 'get', groupLabel: undefined},
);
});
test('getPostsSince', async () => {
const channelId = 'channel_id';
const since = 123456789;
await client.getPostsSince(channelId, since);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getChannelRoute(channelId)}/posts${buildQueryString({since, collapsedThreads: false, collapsedThreadsExtended: false})}`,
{method: 'get', groupLabel: undefined},
);
});
test('getPostsBefore', async () => {
const channelId = 'channel_id';
const postId = 'post_id';
await client.getPostsBefore(channelId, postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getChannelRoute(channelId)}/posts${buildQueryString({before: postId, page: 0, per_page: PER_PAGE_DEFAULT, collapsedThreads: false, collapsedThreadsExtended: false})}`,
{method: 'get'},
);
});
test('getPostsAfter', async () => {
const channelId = 'channel_id';
const postId = 'post_id';
await client.getPostsAfter(channelId, postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getChannelRoute(channelId)}/posts${buildQueryString({after: postId, page: 0, per_page: PER_PAGE_DEFAULT, collapsedThreads: false, collapsedThreadsExtended: false})}`,
{method: 'get'},
);
});
test('getFileInfosForPost', async () => {
const postId = 'post_id';
await client.getFileInfosForPost(postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/files/info`,
{method: 'get'},
);
});
test('getSavedPosts', async () => {
const userId = 'user_id';
await client.getSavedPosts(userId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getUserRoute(userId)}/posts/flagged${buildQueryString({channel_id: '', team_id: '', page: 0, per_page: PER_PAGE_DEFAULT})}`,
{method: 'get'},
);
});
test('getPinnedPosts', async () => {
const channelId = 'channel_id';
await client.getPinnedPosts(channelId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getChannelRoute(channelId)}/pinned`,
{method: 'get'},
);
});
test('markPostAsUnread', async () => {
const userId = 'user_id';
const postId = 'post_id';
await client.markPostAsUnread(userId, postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getUserRoute(userId)}/posts/${postId}/set_unread`,
{method: 'post', body: JSON.stringify({collapsed_threads_supported: true})},
);
});
test('pinPost', async () => {
const postId = 'post_id';
await client.pinPost(postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/pin`,
{method: 'post'},
);
});
test('unpinPost', async () => {
const postId = 'post_id';
await client.unpinPost(postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/unpin`,
{method: 'post'},
);
});
test('addReaction', async () => {
const userId = 'user_id';
const postId = 'post_id';
const emojiName = 'emoji_name';
await client.addReaction(userId, postId, emojiName);
expect(client.doFetch).toHaveBeenCalledWith(
client.getReactionsRoute(),
{method: 'post', body: {user_id: userId, post_id: postId, emoji_name: emojiName}},
);
});
test('removeReaction', async () => {
const userId = 'user_id';
const postId = 'post_id';
const emojiName = 'emoji_name';
await client.removeReaction(userId, postId, emojiName);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getUserRoute(userId)}/posts/${postId}/reactions/${emojiName}`,
{method: 'delete'},
);
});
test('getReactionsForPost', async () => {
const postId = 'post_id';
await client.getReactionsForPost(postId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/reactions`,
{method: 'get'},
);
});
test('searchPostsWithParams', async () => {
const teamId = 'team_id';
const params = {terms: 'search terms', is_or_search: false};
// Test with teamId
await client.searchPostsWithParams(teamId, params);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getTeamRoute(teamId)}/posts/search`,
{method: 'post', body: params},
);
// Test without teamId
await client.searchPostsWithParams('', params);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostsRoute()}/search`,
{method: 'post', body: params},
);
});
test('searchPosts', async () => {
const teamId = 'team_id';
const terms = 'search terms';
const isOrSearch = false;
const spy = jest.spyOn(client, 'searchPostsWithParams').mockResolvedValue({} as SearchPostResponse);
await client.searchPosts(teamId, terms, isOrSearch);
expect(spy).toHaveBeenCalledWith(teamId, {terms, is_or_search: isOrSearch});
});
test('doPostAction', async () => {
const postId = 'post_id';
const actionId = 'action_id';
const selectedOption = 'selected_option';
const spy = jest.spyOn(client, 'doPostActionWithCookie').mockResolvedValue({});
await client.doPostAction(postId, actionId, selectedOption);
expect(spy).toHaveBeenCalledWith(postId, actionId, '', selectedOption);
});
test('doPostActionWithCookie', async () => {
const postId = 'post_id';
const actionId = 'action_id';
const actionCookie = 'action_cookie';
const selectedOption = 'selected_option';
await client.doPostActionWithCookie(postId, actionId, actionCookie, selectedOption);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute(postId)}/actions/${encodeURIComponent(actionId)}`,
{method: 'post', body: {selected_option: selectedOption, cookie: actionCookie}},
);
});
test('acknowledgePost', async () => {
const postId = 'post_id';
const userId = 'user_id';
await client.acknowledgePost(postId, userId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getUserRoute(userId)}/posts/${postId}/ack`,
{method: 'post'},
);
});
test('unacknowledgePost', async () => {
const postId = 'post_id';
const userId = 'user_id';
await client.unacknowledgePost(postId, userId);
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getUserRoute(userId)}/posts/${postId}/ack`,
{method: 'delete'},
);
});
test('sendTestNotification', async () => {
await client.sendTestNotification();
expect(client.doFetch).toHaveBeenCalledWith(
`${client.urlVersion}/notifications/test`,
{method: 'post'},
);
});
test('revealBoRPost', async () => {
await client.revealBoRPost('post_id');
expect(client.doFetch).toHaveBeenCalledWith(
`${client.getPostRoute('post_id')}/reveal`,
{method: 'get'},
);
});
});