add unit tests to app/utils/post (#8142)
This commit is contained in:
parent
13ca346155
commit
48af5fdcd5
2 changed files with 569 additions and 35 deletions
|
|
@ -77,4 +77,5 @@ export default {
|
|||
PostTypes.ADD_TO_TEAM,
|
||||
PostTypes.REMOVE_FROM_TEAM,
|
||||
],
|
||||
POST_TIME_TO_FAIL,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,52 +1,585 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as utils from './index';
|
||||
import {createIntl} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {getUsersCountFromMentions} from '@actions/local/post';
|
||||
import {General, Post} from '@constants';
|
||||
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
|
||||
import {getUserById} from '@queries/servers/user';
|
||||
import {toMilliseconds} from '@utils/datetime';
|
||||
|
||||
import {
|
||||
areConsecutivePosts,
|
||||
isFromWebhook,
|
||||
isEdited,
|
||||
isPostEphemeral,
|
||||
isPostFailed,
|
||||
isPostPendingOrFailed,
|
||||
isSystemMessage,
|
||||
fromAutoResponder,
|
||||
postUserDisplayName,
|
||||
shouldIgnorePost,
|
||||
processPostsFetched,
|
||||
getLastFetchedAtFromPosts,
|
||||
moreThan5minAgo,
|
||||
hasSpecialMentions,
|
||||
persistentNotificationsConfirmation,
|
||||
} from '.';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
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', () => {
|
||||
test.each([
|
||||
['@here where is Jessica Hyde', true],
|
||||
['@all where is Jessica Hyde', true],
|
||||
['@channel where is Jessica Hyde', true],
|
||||
describe('areConsecutivePosts', () => {
|
||||
it('should return true for consecutive posts from the same user within the collapse timeout', () => {
|
||||
const post = {
|
||||
userId: 'user1',
|
||||
createAt: 1000,
|
||||
props: {},
|
||||
} as PostModel;
|
||||
const previousPost = {
|
||||
userId: 'user1',
|
||||
createAt: 500,
|
||||
props: {},
|
||||
} as PostModel;
|
||||
|
||||
['where is Jessica Hyde @here', true],
|
||||
['where is Jessica Hyde @all', true],
|
||||
['where is Jessica Hyde @channel', true],
|
||||
const result = areConsecutivePosts(post, previousPost);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
['where is Jessica @here Hyde', true],
|
||||
['where is Jessica @all Hyde', true],
|
||||
['where is Jessica @channel Hyde', true],
|
||||
it('should return false for posts from different users', () => {
|
||||
const post = {
|
||||
userId: 'user1',
|
||||
createAt: 1000,
|
||||
props: {},
|
||||
} as PostModel;
|
||||
const previousPost = {
|
||||
userId: 'user2',
|
||||
createAt: 500,
|
||||
props: {},
|
||||
} as PostModel;
|
||||
|
||||
['where is Jessica Hyde\n@here', true],
|
||||
['where is Jessica Hyde\n@all', true],
|
||||
['where is Jessica Hyde\n@channel', true],
|
||||
const result = areConsecutivePosts(post, previousPost);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
['where is Jessica\n@here Hyde', true],
|
||||
['where is Jessica\n@all Hyde', true],
|
||||
['where is Jessica\n@channel Hyde', true],
|
||||
describe('isFromWebhook', () => {
|
||||
it('should return true for posts from a webhook', () => {
|
||||
const post = {
|
||||
props: {
|
||||
from_webhook: 'true',
|
||||
},
|
||||
} as PostModel;
|
||||
|
||||
['where is Jessica Hyde @her', false],
|
||||
['where is Jessica Hyde @al', false],
|
||||
['where is Jessica Hyde @chann', false],
|
||||
const result = isFromWebhook(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
['where is Jessica Hyde@here', false],
|
||||
['where is Jessica Hyde@all', false],
|
||||
['where is Jessica Hyde@channel', false],
|
||||
it('should return false for posts not from a webhook', () => {
|
||||
const post = {
|
||||
props: {
|
||||
from_webhook: 'false',
|
||||
},
|
||||
} as PostModel;
|
||||
|
||||
['where is Jessica @hereHyde', false],
|
||||
['where is Jessica @allHyde', false],
|
||||
['where is Jessica @channelHyde', false],
|
||||
const result = isFromWebhook(post);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
['@herewhere is Jessica Hyde@here', false],
|
||||
['@allwhere is Jessica Hyde@all', false],
|
||||
['@channelwhere is Jessica Hyde@channel', false],
|
||||
describe('isEdited', () => {
|
||||
it('should return true if the post is edited', () => {
|
||||
const post = {
|
||||
editAt: 1000,
|
||||
} as PostModel;
|
||||
|
||||
['where is Jessica Hyde here', false],
|
||||
['where is Jessica Hyde all', false],
|
||||
['where is Jessica Hyde channel', false],
|
||||
const result = isEdited(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
['where is Jessica Hyde', false],
|
||||
])('hasSpecialMentions: %s => %s', (message, expected) => {
|
||||
expect(utils.hasSpecialMentions(message)).toBe(expected);
|
||||
it('should return false if the post is not edited', () => {
|
||||
const post = {
|
||||
editAt: 0,
|
||||
} as PostModel;
|
||||
|
||||
const result = isEdited(post);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPostEphemeral', () => {
|
||||
it('should return true for an ephemeral post', () => {
|
||||
const post = {
|
||||
type: Post.POST_TYPES.EPHEMERAL,
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostEphemeral(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for a non-ephemeral post', () => {
|
||||
const post = {
|
||||
type: 'normal',
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostEphemeral(post);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPostFailed', () => {
|
||||
it('should return true if the post has failed prop', () => {
|
||||
const post = {
|
||||
props: {
|
||||
failed: true,
|
||||
},
|
||||
pendingPostId: 'id',
|
||||
id: 'id',
|
||||
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
|
||||
} as PostModel;
|
||||
|
||||
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 = {
|
||||
props: {},
|
||||
pendingPostId: 'id',
|
||||
id: 'id',
|
||||
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostFailed(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the post is not failed', () => {
|
||||
const post = {
|
||||
props: {},
|
||||
pendingPostId: 'id',
|
||||
id: 'id',
|
||||
updateAt: Date.now(),
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostFailed(post);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPostPendingOrFailed', () => {
|
||||
it('should return true if the post is pending', () => {
|
||||
const post = {
|
||||
pendingPostId: 'id',
|
||||
id: 'id',
|
||||
props: {},
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostPendingOrFailed(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the post has failed', () => {
|
||||
const post = {
|
||||
pendingPostId: 'id',
|
||||
id: 'id',
|
||||
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
|
||||
props: {},
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostPendingOrFailed(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the post is neither pending nor failed', () => {
|
||||
const post = {
|
||||
pendingPostId: 'differentId',
|
||||
id: 'id',
|
||||
props: {},
|
||||
} as PostModel;
|
||||
|
||||
const result = isPostPendingOrFailed(post);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSystemMessage', () => {
|
||||
it('should return true if the post is a system message', () => {
|
||||
const post = {
|
||||
type: `${Post.POST_TYPES.SYSTEM_MESSAGE_PREFIX}any_type`,
|
||||
} as PostModel;
|
||||
|
||||
const result = isSystemMessage(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the post is not a system message', () => {
|
||||
const post = {
|
||||
type: 'normal_type',
|
||||
} as PostModel;
|
||||
|
||||
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 = {
|
||||
type: Post.POST_TYPES.SYSTEM_AUTO_RESPONDER,
|
||||
} as PostModel;
|
||||
|
||||
const result = fromAutoResponder(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the post is not from an auto responder', () => {
|
||||
const post = {
|
||||
type: 'normal_type',
|
||||
} as PostModel;
|
||||
|
||||
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 = {username: 'teammate'};
|
||||
(getUserById as jest.Mock).mockResolvedValue(mockUser);
|
||||
|
||||
await persistentNotificationsConfirmation(
|
||||
serverUrl,
|
||||
value,
|
||||
mentionsList,
|
||||
intl,
|
||||
sendMessage,
|
||||
persistentNotificationMaxRecipients,
|
||||
persistentNotificationInterval,
|
||||
currentUserId,
|
||||
channelName,
|
||||
General.DM_CHANNEL,
|
||||
);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.confirm.title',
|
||||
defaultMessage: 'Send persistent notifications',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.dm_channel.description',
|
||||
defaultMessage: '@{username} will be notified every {interval, plural, one {minute} other {{interval} minutes}} until they’ve acknowledged or replied to the message.',
|
||||
}, {
|
||||
interval: persistentNotificationInterval,
|
||||
username: mockUser.username,
|
||||
}),
|
||||
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(
|
||||
'',
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.error.special_mentions',
|
||||
defaultMessage: 'Cannot use @channel, @all or @here to mention recipients of persistent notifications.',
|
||||
}),
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show alert when no mentions found', async () => {
|
||||
(getUsersCountFromMentions as jest.Mock).mockResolvedValue(0);
|
||||
|
||||
await persistentNotificationsConfirmation(
|
||||
serverUrl,
|
||||
value,
|
||||
mentionsList,
|
||||
intl,
|
||||
sendMessage,
|
||||
persistentNotificationMaxRecipients,
|
||||
persistentNotificationInterval,
|
||||
currentUserId,
|
||||
channelName,
|
||||
);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.error.no_mentions.title',
|
||||
defaultMessage: 'Recipients must be @mentioned',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.error.no_mentions.description',
|
||||
defaultMessage: 'There are no recipients mentioned in your message. You’ll need add mentions to be able to send persistent notifications.',
|
||||
}),
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show alert when mentions exceed max recipients', async () => {
|
||||
(getUsersCountFromMentions as jest.Mock).mockResolvedValue(15);
|
||||
|
||||
await persistentNotificationsConfirmation(
|
||||
serverUrl,
|
||||
value,
|
||||
mentionsList,
|
||||
intl,
|
||||
sendMessage,
|
||||
persistentNotificationMaxRecipients,
|
||||
persistentNotificationInterval,
|
||||
currentUserId,
|
||||
channelName,
|
||||
);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.error.max_recipients.title',
|
||||
defaultMessage: 'Too many recipients',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.error.max_recipients.description',
|
||||
defaultMessage: 'You can send persistent notifications to a maximum of {max} recipients. There are {count} recipients mentioned in your message. You’ll need to change who you’ve mentioned before you can send.',
|
||||
}, {
|
||||
max: persistentNotificationMaxRecipients,
|
||||
count: mentionsList.length,
|
||||
}),
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show confirmation alert for valid mentions within limit', async () => {
|
||||
(getUsersCountFromMentions as jest.Mock).mockResolvedValue(5);
|
||||
|
||||
await persistentNotificationsConfirmation(
|
||||
serverUrl,
|
||||
value,
|
||||
mentionsList,
|
||||
intl,
|
||||
sendMessage,
|
||||
persistentNotificationMaxRecipients,
|
||||
persistentNotificationInterval,
|
||||
currentUserId,
|
||||
channelName,
|
||||
);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.confirm.title',
|
||||
defaultMessage: 'Send persistent notifications',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'persistent_notifications.confirm.description',
|
||||
defaultMessage: 'Mentioned recipients will be notified every {interval, plural, one {minute} other {{interval} minutes}} until they’ve acknowledged or replied to the message.',
|
||||
}, {
|
||||
interval: persistentNotificationInterval,
|
||||
}),
|
||||
expect.any(Array),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('postUserDisplayName', () => {
|
||||
it('should return the override username if from webhook and override is enabled', () => {
|
||||
const post = {
|
||||
props: {
|
||||
from_webhook: 'true',
|
||||
override_username: 'webhook_user',
|
||||
},
|
||||
} as PostModel;
|
||||
|
||||
const result = postUserDisplayName(post, undefined, undefined, true);
|
||||
expect(result).toBe('webhook_user');
|
||||
});
|
||||
|
||||
it('should return the author’s display name if not from webhook or override is disabled', () => {
|
||||
const post = {
|
||||
props: {
|
||||
from_webhook: 'false',
|
||||
},
|
||||
} as PostModel;
|
||||
const author = {
|
||||
username: 'user1',
|
||||
locale: 'en',
|
||||
} as UserModel;
|
||||
|
||||
const result = postUserDisplayName(post, author, undefined, false);
|
||||
expect(result).toBe('user1');
|
||||
});
|
||||
|
||||
it('should return the author’s display name using the teammate name display', () => {
|
||||
const post = {
|
||||
props: {
|
||||
from_webhook: 'false',
|
||||
},
|
||||
} as PostModel;
|
||||
const author = {
|
||||
username: 'user1',
|
||||
locale: 'en',
|
||||
} as UserModel;
|
||||
|
||||
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 = {
|
||||
type: Post.POST_TYPES.CHANNEL_DELETED,
|
||||
} as Post;
|
||||
|
||||
const result = shouldIgnorePost(post);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the post type is not in the ignore list', () => {
|
||||
const post = {
|
||||
type: Post.POST_TYPES.EPHEMERAL,
|
||||
} as Post;
|
||||
|
||||
const result = shouldIgnorePost(post);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processPostsFetched', () => {
|
||||
it('should process the fetched posts correctly', () => {
|
||||
const data = {
|
||||
order: ['post1', 'post2'],
|
||||
posts: {
|
||||
post1: {id: 'post1', message: 'First post'},
|
||||
post2: {id: 'post2', message: 'Second post'},
|
||||
},
|
||||
prev_post_id: 'post0',
|
||||
} as unknown as PostResponse;
|
||||
|
||||
const result = processPostsFetched(data);
|
||||
expect(result).toEqual({
|
||||
posts: [
|
||||
{id: 'post1', message: 'First post'},
|
||||
{id: 'post2', message: 'Second post'},
|
||||
],
|
||||
order: ['post1', 'post2'],
|
||||
previousPostId: 'post0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastFetchedAtFromPosts', () => {
|
||||
it('should return the maximum timestamp from the posts', () => {
|
||||
const posts = [
|
||||
{create_at: 1000, update_at: 2000, delete_at: 0},
|
||||
{create_at: 1500, update_at: 2500, delete_at: 3000},
|
||||
] as Post[];
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue