mattermost-mobile/app/utils/post/index.test.ts
Daniel Espino García 23565b5135
Add autotranslations (#9324)
* Add autotranslations

* Develop latest changes and missing features

* i18n-extract

* Fix tests and update api behavior

* Fix minor bugs

* adjustments to shimmer animation, showtranslation modal style

* Update post.tsx

* Update show_translation.tsx

* adjust sizing and opacity of translate icon

* Add channel header translated icon

* Disable auto translation item if it is not a supported language

* Move channel info to the top

* Update edit channel to channel info

* Fix align of option items

* Fix i18n

* Add detox related changes for channel settings changes

* Add tests

* Address changes around the my channel column change

* Address feedback

* Fix test

* Fix bad import

* Fix set my channel autotranslation

* Fix test

* Address feedback

* Add missing files from last commit

* Remove unneeded change

---------

Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>
2026-02-10 17:50:39 +01:00

597 lines
21 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {createIntl} from 'react-intl';
import {Alert} from 'react-native';
import {getUsersCountFromMentions} from '@actions/local/post';
import {General, Post} from '@constants';
import {mockedPosts} from '@database/operator/utils/mock';
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
import {getUserById} from '@queries/servers/user';
import TestHelper from '@test/test_helper';
import {toMilliseconds} from '@utils/datetime';
import {
areConsecutivePosts,
isFromWebhook,
isEdited,
isPostEphemeral,
isPostFailed,
isPostPendingOrFailed,
isSystemMessage,
fromAutoResponder,
postUserDisplayName,
shouldIgnorePost,
processPostsFetched,
getLastFetchedAtFromPosts,
moreThan5minAgo,
hasSpecialMentions,
persistentNotificationsConfirmation,
scheduledPostFromPost,
} from '.';
jest.mock('@actions/local/post', () => ({
getUsersCountFromMentions: jest.fn(),
}));
jest.mock('@queries/servers/user', () => ({
getUserById: jest.fn(),
}));
jest.mock('@database/manager', () => ({
getServerDatabaseAndOperator: jest.fn().mockReturnValue({
database: {},
}),
}));
describe('post utils', () => {
describe('areConsecutivePosts', () => {
it('should return true for consecutive posts from the same user within the collapse timeout', () => {
const post = TestHelper.fakePostModel({
userId: 'user1',
createAt: 1000,
props: {},
});
const previousPost = TestHelper.fakePostModel({
userId: 'user1',
createAt: 500,
props: {},
});
const result = areConsecutivePosts(post, previousPost, 'en');
expect(result).toBe(true);
});
it('should return false for posts from different users', () => {
const post = TestHelper.fakePostModel({
userId: 'user1',
createAt: 1000,
props: {},
});
const previousPost = TestHelper.fakePostModel({
userId: 'user2',
createAt: 500,
props: {},
});
const result = areConsecutivePosts(post, previousPost, 'en');
expect(result).toBe(false);
});
});
describe('isFromWebhook', () => {
it('should return true for posts from a webhook', () => {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'true',
},
});
const result = isFromWebhook(post);
expect(result).toBe(true);
});
it('should return false for posts not from a webhook', () => {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'false',
},
});
const result = isFromWebhook(post);
expect(result).toBe(false);
});
});
describe('isEdited', () => {
it('should return true if the post is edited', () => {
const post = TestHelper.fakePostModel({
editAt: 1000,
});
const result = isEdited(post);
expect(result).toBe(true);
});
it('should return false if the post is not edited', () => {
const post = TestHelper.fakePostModel({
editAt: 0,
});
const result = isEdited(post);
expect(result).toBe(false);
});
});
describe('isPostEphemeral', () => {
it('should return true for an ephemeral post', () => {
const post = TestHelper.fakePostModel({
type: Post.POST_TYPES.EPHEMERAL,
});
const result = isPostEphemeral(post);
expect(result).toBe(true);
});
it('should return false for a non-ephemeral post', () => {
const post = TestHelper.fakePostModel({
type: '',
});
const result = isPostEphemeral(post);
expect(result).toBe(false);
});
});
describe('isPostFailed', () => {
it('should return true if the post has failed prop', () => {
const post = TestHelper.fakePostModel({
props: {
failed: true,
},
pendingPostId: 'id',
id: 'id',
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
});
const result = isPostFailed(post);
expect(result).toBe(true);
});
it('should return true if the post is pending and the update time has exceeded the failure time', () => {
const post = TestHelper.fakePostModel({
props: {},
pendingPostId: 'id',
id: 'id',
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
});
const result = isPostFailed(post);
expect(result).toBe(true);
});
it('should return false if the post is not failed', () => {
const post = TestHelper.fakePostModel({
props: {},
pendingPostId: 'id',
id: 'id',
updateAt: Date.now(),
});
const result = isPostFailed(post);
expect(result).toBe(false);
});
});
describe('isPostPendingOrFailed', () => {
it('should return true if the post is pending', () => {
const post = TestHelper.fakePostModel({
pendingPostId: 'id',
id: 'id',
props: {},
});
const result = isPostPendingOrFailed(post);
expect(result).toBe(true);
});
it('should return true if the post has failed', () => {
const post = TestHelper.fakePostModel({
pendingPostId: 'id',
id: 'id',
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
props: {},
});
const result = isPostPendingOrFailed(post);
expect(result).toBe(true);
});
it('should return false if the post is neither pending nor failed', () => {
const post = TestHelper.fakePostModel({
pendingPostId: 'differentId',
id: 'id',
props: {},
});
const result = isPostPendingOrFailed(post);
expect(result).toBe(false);
});
});
describe('isSystemMessage', () => {
it('should return true if the post is a system message', () => {
const post = TestHelper.fakePostModel({
type: `${Post.POST_TYPES.SYSTEM_MESSAGE_PREFIX}any_type` as PostType,
});
const result = isSystemMessage(post);
expect(result).toBe(true);
});
it('should return false if the post is not a system message', () => {
const post = TestHelper.fakePostModel({
type: 'add_bot_teams_channels',
});
const result = isSystemMessage(post);
expect(result).toBe(false);
});
});
describe('hasSpecialMentions', () => {
test.each([
['@here where is Jessica Hyde', true],
['@all where is Jessica Hyde', true],
['@channel where is Jessica Hyde', true],
['where is Jessica Hyde @here', true],
['where is Jessica Hyde @all', true],
['where is Jessica Hyde @channel', true],
['where is Jessica @here Hyde', true],
['where is Jessica @all Hyde', true],
['where is Jessica @channel Hyde', true],
['where is Jessica Hyde\n@here', true],
['where is Jessica Hyde\n@all', true],
['where is Jessica Hyde\n@channel', true],
['where is Jessica\n@here Hyde', true],
['where is Jessica\n@all Hyde', true],
['where is Jessica\n@channel Hyde', true],
['where is Jessica Hyde @her', false],
['where is Jessica Hyde @al', false],
['where is Jessica Hyde @chann', false],
['where is Jessica Hyde@here', false],
['where is Jessica Hyde@all', false],
['where is Jessica Hyde@channel', false],
['where is Jessica @hereHyde', false],
['where is Jessica @allHyde', false],
['where is Jessica @channelHyde', false],
['@herewhere is Jessica Hyde@here', false],
['@allwhere is Jessica Hyde@all', false],
['@channelwhere is Jessica Hyde@channel', false],
['where is Jessica Hyde here', false],
['where is Jessica Hyde all', false],
['where is Jessica Hyde channel', false],
['where is Jessica Hyde', false],
])('hasSpecialMentions: %s => %s', (message, expected) => {
expect(hasSpecialMentions(message)).toBe(expected);
});
});
describe('fromAutoResponder', () => {
it('should return true if the post is from an auto responder', () => {
const post = TestHelper.fakePostModel({
type: Post.POST_TYPES.SYSTEM_AUTO_RESPONDER,
});
const result = fromAutoResponder(post);
expect(result).toBe(true);
});
it('should return false if the post is not from an auto responder', () => {
const post = TestHelper.fakePostModel({
type: 'add_bot_teams_channels',
});
const result = fromAutoResponder(post);
expect(result).toBe(false);
});
});
describe('persistentNotificationsConfirmation', () => {
const serverUrl = 'http://server';
const value = '@user';
const mentionsList = ['@user'];
const sendMessage = jest.fn();
const persistentNotificationMaxRecipients = 10;
const persistentNotificationInterval = 5;
const currentUserId = 'current_user_id';
const channelName = 'channel_id__teammate_id';
const intl = createIntl({locale: DEFAULT_LOCALE, messages: getTranslations(DEFAULT_LOCALE)});
it('should show alert with DM channel description when channelType is DM_CHANNEL', async () => {
const mockUser = TestHelper.fakeUserModel({username: 'teammate'});
jest.mocked(getUserById).mockResolvedValue(mockUser);
await persistentNotificationsConfirmation(
serverUrl,
value,
mentionsList,
intl,
sendMessage,
persistentNotificationMaxRecipients,
persistentNotificationInterval,
currentUserId,
channelName,
General.DM_CHANNEL,
);
expect(Alert.alert).toHaveBeenCalledWith(
'Send persistent notifications',
`${mockUser.username} will be notified every ${persistentNotificationInterval} minutes until theyve acknowledged or replied to the message.`,
expect.any(Array),
);
});
it('should show alert when special mentions are present', async () => {
await persistentNotificationsConfirmation(
serverUrl,
'@channel',
mentionsList,
intl,
sendMessage,
persistentNotificationMaxRecipients,
persistentNotificationInterval,
currentUserId,
channelName,
);
expect(Alert.alert).toHaveBeenCalledWith(
'',
'Cannot use @channel, @all or @here to mention recipients of persistent notifications.',
expect.any(Array),
);
});
it('should show alert when no mentions found', async () => {
jest.mocked(getUsersCountFromMentions).mockResolvedValue(0);
await persistentNotificationsConfirmation(
serverUrl,
value,
mentionsList,
intl,
sendMessage,
persistentNotificationMaxRecipients,
persistentNotificationInterval,
currentUserId,
channelName,
);
expect(Alert.alert).toHaveBeenCalledWith(
'Recipients must be @mentioned',
'There are no recipients mentioned in your message. Youll need add mentions to be able to send persistent notifications.',
expect.any(Array),
);
});
it('should show alert when mentions exceed max recipients', async () => {
jest.mocked(getUsersCountFromMentions).mockResolvedValue(15);
await persistentNotificationsConfirmation(
serverUrl,
value,
mentionsList,
intl,
sendMessage,
persistentNotificationMaxRecipients,
persistentNotificationInterval,
currentUserId,
channelName,
);
expect(Alert.alert).toHaveBeenCalledWith(
'Too many recipients',
`You can send persistent notifications to a maximum of ${persistentNotificationMaxRecipients} recipients. There are ${mentionsList.length} recipients mentioned in your message. Youll need to change who youve mentioned before you can send.`,
expect.any(Array),
);
});
it('should show confirmation alert for valid mentions within limit', async () => {
jest.mocked(getUsersCountFromMentions).mockResolvedValue(5);
await persistentNotificationsConfirmation(
serverUrl,
value,
mentionsList,
intl,
sendMessage,
persistentNotificationMaxRecipients,
persistentNotificationInterval,
currentUserId,
channelName,
);
expect(Alert.alert).toHaveBeenCalledWith(
'Send persistent notifications',
`Mentioned recipients will be notified every ${persistentNotificationInterval} minutes until theyve acknowledged or replied to the message.`,
expect.any(Array),
);
});
});
describe('postUserDisplayName', () => {
it('should return the override username if from webhook and override is enabled', () => {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'true',
override_username: 'webhook_user',
},
});
const result = postUserDisplayName(post, undefined, undefined, true);
expect(result).toBe('webhook_user');
});
it('should return the authors display name if not from webhook or override is disabled', () => {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'false',
},
});
const author = TestHelper.fakeUserModel({
username: 'user1',
locale: 'en',
});
const result = postUserDisplayName(post, author, undefined, false);
expect(result).toBe('user1');
});
it('should return the authors display name using the teammate name display', () => {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'false',
},
});
const author = TestHelper.fakeUserModel({
username: 'user1',
locale: 'en',
});
const result = postUserDisplayName(post, author, 'nickname', false);
expect(result).toBe('user1');
});
});
describe('shouldIgnorePost', () => {
it('should return true if the post type is in the ignore list', () => {
const post = TestHelper.fakePost({
type: Post.POST_TYPES.CHANNEL_DELETED,
});
const result = shouldIgnorePost(post);
expect(result).toBe(true);
});
it('should return false if the post type is not in the ignore list', () => {
const post = TestHelper.fakePost({
type: Post.POST_TYPES.EPHEMERAL,
});
const result = shouldIgnorePost(post);
expect(result).toBe(false);
});
});
describe('processPostsFetched', () => {
it('should process the fetched posts correctly', () => {
const data = {
order: ['post1', 'post2'],
posts: {
post1: TestHelper.fakePost({id: 'post1', message: 'First post'}),
post2: TestHelper.fakePost({id: 'post2', message: 'Second post'}),
},
prev_post_id: 'post0',
};
const result = processPostsFetched(data);
expect(result).toEqual({
posts: [
expect.objectContaining({id: 'post1', message: 'First post'}),
expect.objectContaining({id: 'post2', message: 'Second post'}),
],
order: ['post1', 'post2'],
previousPostId: 'post0',
});
});
});
describe('getLastFetchedAtFromPosts', () => {
it('should return the maximum timestamp from the posts', () => {
const posts = [
TestHelper.fakePost({create_at: 1000, update_at: 2000, delete_at: 0}),
TestHelper.fakePost({create_at: 1500, update_at: 2500, delete_at: 3000}),
];
const result = getLastFetchedAtFromPosts(posts);
expect(result).toBe(3000);
});
it('should return 0 if no posts are provided', () => {
const result = getLastFetchedAtFromPosts();
expect(result).toBe(0);
});
});
describe('moreThan5minAgo', () => {
it('should return true if the time is more than 5 minutes ago', () => {
const time = Date.now() - toMilliseconds({minutes: 6});
const result = moreThan5minAgo(time);
expect(result).toBe(true);
});
it('should return false if the time is within 5 minutes', () => {
const time = Date.now() - toMilliseconds({minutes: 4});
const result = moreThan5minAgo(time);
expect(result).toBe(false);
});
});
describe('scheduledPostFromPost', () => {
const post: Post = mockedPosts.posts[mockedPosts.order[0]];
const schedulingInfo: SchedulingInfo = {
scheduled_at: Date.now() + 10000,
};
const postPriority: PostPriority = {
priority: 'important',
};
const postFiles: FileInfo[] = [
TestHelper.fakeFileInfo({id: 'fileid1'}),
TestHelper.fakeFileInfo({id: 'fileid2'}),
];
it('should create a scheduled post with the given scheduling info', () => {
const result: ScheduledPost = scheduledPostFromPost(post, schedulingInfo);
expect(result.scheduled_at).toBe(schedulingInfo.scheduled_at);
});
it('should not include the post priority if its a post in thread', () => {
const result: ScheduledPost = scheduledPostFromPost(post, schedulingInfo, postPriority);
expect(result.priority).toBeUndefined();
});
it('should include the file IDs if post files are provided', () => {
const result: ScheduledPost = scheduledPostFromPost(post, schedulingInfo, postPriority, postFiles);
expect(result.file_ids).toEqual(['fileid1', 'fileid2']);
});
it('should include the post files if provided', () => {
const result: ScheduledPost = scheduledPostFromPost(post, schedulingInfo, postPriority, postFiles);
expect(result.metadata?.files).toEqual(postFiles);
});
it('should return a scheduled post with the same properties as the original post', () => {
const result: ScheduledPost = scheduledPostFromPost(post, schedulingInfo);
expect(result.id).toBe(post.id);
expect(result.message).toBe(post.message);
expect(result.channel_id).toBe(post.channel_id);
expect(result.create_at).toBe(post.create_at);
expect(result.update_at).toBe(post.update_at);
expect(result.user_id).toBe(post.user_id);
});
});
});