more utils unit test coverage (#8068)
This commit is contained in:
parent
b9f4000dd0
commit
9bdb5c15e1
7 changed files with 1030 additions and 18 deletions
246
app/utils/channel/index.test.ts
Normal file
246
app/utils/channel/index.test.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createIntl} from 'react-intl';
|
||||
|
||||
import {Channel, General, Permissions} from '@constants';
|
||||
import {hasPermission} from '@utils/role';
|
||||
|
||||
import {compareNotifyProps, filterChannelsMatchingTerm, generateChannelNameFromDisplayName, getDirectChannelName, isArchived, isDMorGM, isTypeDMorGM, selectDefaultChannelForTeam, sortChannelsByDisplayName, sortChannelsModelByDisplayName, validateDisplayName} from '.';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
jest.mock('@utils/role', () => ({
|
||||
hasPermission: jest.fn(() => true),
|
||||
}));
|
||||
|
||||
jest.mock('@utils/general', () => ({
|
||||
generateId: jest.fn(() => 'generated-id'),
|
||||
}));
|
||||
|
||||
describe('getDirectChannelName', () => {
|
||||
it('should return the correct direct channel name', () => {
|
||||
expect(getDirectChannelName('id1', 'id2')).toBe('id1__id2');
|
||||
expect(getDirectChannelName('id2', 'id1')).toBe('id1__id2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDMorGM', () => {
|
||||
it('should return true for DM or GM channels', () => {
|
||||
expect(isDMorGM({type: General.DM_CHANNEL} as Channel)).toBe(true);
|
||||
expect(isDMorGM({type: General.GM_CHANNEL} as Channel)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for other channel types', () => {
|
||||
expect(isDMorGM({type: General.OPEN_CHANNEL} as Channel)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isTypeDMorGM', () => {
|
||||
it('should return true for DM or GM channel types', () => {
|
||||
expect(isTypeDMorGM(General.DM_CHANNEL)).toBe(true);
|
||||
expect(isTypeDMorGM(General.GM_CHANNEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for other channel types', () => {
|
||||
expect(isTypeDMorGM(General.OPEN_CHANNEL)).toBe(false);
|
||||
expect(isTypeDMorGM(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isArchived', () => {
|
||||
it('should return true if channel is archived', () => {
|
||||
expect(isArchived({delete_at: 12345} as Channel)).toBe(true);
|
||||
expect(isArchived({deleteAt: 12345} as ChannelModel)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if channel is not archived', () => {
|
||||
expect(isArchived({delete_at: 0} as Channel)).toBe(false);
|
||||
expect(isArchived({deleteAt: 0} as ChannelModel)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectDefaultChannelForTeam', () => {
|
||||
const channels = [
|
||||
{id: '1', name: General.DEFAULT_CHANNEL, team_id: 'team1', type: General.OPEN_CHANNEL},
|
||||
{id: '2', name: 'random', team_id: 'team1', type: General.OPEN_CHANNEL},
|
||||
] as Channel[];
|
||||
|
||||
const memberships = [
|
||||
{channel_id: '2'},
|
||||
] as ChannelMembership[];
|
||||
|
||||
const roles = [
|
||||
{permissions: [Permissions.JOIN_PUBLIC_CHANNELS]},
|
||||
] as Role[];
|
||||
|
||||
it('should select the default channel if user is a member', () => {
|
||||
const result = selectDefaultChannelForTeam(channels, memberships, 'team1', roles);
|
||||
expect(result).toEqual(channels[0]);
|
||||
|
||||
const channelModels = [
|
||||
{id: '1', name: General.DEFAULT_CHANNEL, teamId: 'team1', type: General.OPEN_CHANNEL},
|
||||
{id: '2', name: 'random', teamId: 'team1', type: General.OPEN_CHANNEL},
|
||||
] as ChannelModel[];
|
||||
const result2 = selectDefaultChannelForTeam(channelModels, memberships, 'team1', roles);
|
||||
expect(result2).toEqual(channelModels[0]);
|
||||
});
|
||||
|
||||
it('should select the first channel in the team if user can join public channels', () => {
|
||||
const result = selectDefaultChannelForTeam(channels, [], 'team1', roles);
|
||||
expect(result).toEqual(channels[0]);
|
||||
});
|
||||
|
||||
it('should select the first channel in the team if user cannot join public channels', () => {
|
||||
(hasPermission as jest.Mock).mockReturnValueOnce(false);
|
||||
const result = selectDefaultChannelForTeam(channels, memberships, 'team1', roles);
|
||||
expect(result).toEqual(channels[1]);
|
||||
});
|
||||
|
||||
it('should select the default channel in the team if user cannot join public channels', () => {
|
||||
(hasPermission as jest.Mock).mockReturnValueOnce(false);
|
||||
const memberOf = [
|
||||
{channel_id: '1'},
|
||||
] as ChannelMembership[];
|
||||
const result = selectDefaultChannelForTeam(channels, memberOf, 'team1', roles);
|
||||
expect(result).toEqual(channels[0]);
|
||||
});
|
||||
|
||||
it('should return undefined if no channels are available', () => {
|
||||
const result = selectDefaultChannelForTeam([], [], 'team1', roles);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortChannelsByDisplayName', () => {
|
||||
it('should sort channels by display name', () => {
|
||||
const channels = [
|
||||
{name: 'name1', display_name: 'Zeta'},
|
||||
{name: 'name2', display_name: 'Alpha'},
|
||||
] as Channel[];
|
||||
|
||||
const channelsModels = [
|
||||
{name: 'name1', displayName: 'Zeta'},
|
||||
{name: 'name2', displayName: 'Alpha'},
|
||||
] as ChannelModel[];
|
||||
|
||||
const result = channels.sort((a, b) => sortChannelsByDisplayName('en', a, b));
|
||||
expect(result[0].name).toBe('name2');
|
||||
|
||||
const result2 = channelsModels.sort((a, b) => sortChannelsByDisplayName('en', a, b));
|
||||
expect(result2[0].name).toBe('name2');
|
||||
});
|
||||
|
||||
it('should sort channels by name if display name is not defined', () => {
|
||||
const channels = [
|
||||
{name: 'Zeta'},
|
||||
{name: 'Alpha'},
|
||||
] as Channel[];
|
||||
|
||||
const result = channels.sort((a, b) => sortChannelsByDisplayName('en', a, b));
|
||||
expect(result[0].name).toBe('Alpha');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortChannelsModelByDisplayName', () => {
|
||||
it('should sort channel models by display name', () => {
|
||||
const channels = [
|
||||
{name: 'name1', displayName: 'Zeta'},
|
||||
{name: 'name2', displayName: 'Alpha'},
|
||||
] as ChannelModel[];
|
||||
|
||||
const result = channels.sort((a, b) => sortChannelsModelByDisplayName('en', a, b));
|
||||
expect(result[0].name).toBe('name2');
|
||||
});
|
||||
|
||||
it('should sort channel models by name if display name is not defined', () => {
|
||||
const channels = [
|
||||
{name: 'Zeta'},
|
||||
{name: 'Alpha'},
|
||||
] as ChannelModel[];
|
||||
|
||||
const result = channels.sort((a, b) => sortChannelsModelByDisplayName('en', a, b));
|
||||
expect(result[0].name).toBe('Alpha');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateDisplayName', () => {
|
||||
const intl = createIntl({locale: 'en', messages: {}});
|
||||
it('should return an error if display name is empty', () => {
|
||||
const result = validateDisplayName(intl, '');
|
||||
expect(result.error).toBe('Channel name is required');
|
||||
});
|
||||
|
||||
it('should return an error if display name is too long', () => {
|
||||
const result = validateDisplayName(intl, 'a'.repeat(100));
|
||||
expect(result.error).toBe('Channel name must be less than 64 characters');
|
||||
});
|
||||
|
||||
it('should return an error if display name is too short', () => {
|
||||
const minLength = Channel.MIN_CHANNEL_NAME_LENGTH;
|
||||
Channel.MIN_CHANNEL_NAME_LENGTH = 2;
|
||||
const result = validateDisplayName(intl, 'a');
|
||||
expect(result.error).toBe('Channel name must be 2 or more characters');
|
||||
Channel.MIN_CHANNEL_NAME_LENGTH = minLength;
|
||||
});
|
||||
|
||||
it('should return no error if display name is valid', () => {
|
||||
const result = validateDisplayName(intl, 'valid name');
|
||||
expect(result.error).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateChannelNameFromDisplayName', () => {
|
||||
it('should generate a channel name from display name', () => {
|
||||
const result = generateChannelNameFromDisplayName('Valid Name');
|
||||
expect(result).toBe('valid-name');
|
||||
});
|
||||
|
||||
it('should generate an ID if display name is empty after cleanup', () => {
|
||||
const result = generateChannelNameFromDisplayName('');
|
||||
expect(result).toBe('generated-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareNotifyProps', () => {
|
||||
it('should return true if notify props are equal', () => {
|
||||
const propsA: Partial<ChannelNotifyProps> = {desktop: 'default', email: 'default', mark_unread: 'all', push: 'default', ignore_channel_mentions: 'default'};
|
||||
const propsB = {...propsA};
|
||||
expect(compareNotifyProps(propsA, propsB)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if notify props are different', () => {
|
||||
const propsA: Partial<ChannelNotifyProps> = {desktop: 'default', email: 'default', mark_unread: 'all', push: 'default', ignore_channel_mentions: 'default'};
|
||||
const propsB: Partial<ChannelNotifyProps> = {...propsA, desktop: 'none'};
|
||||
expect(compareNotifyProps(propsA, propsB)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterChannelsMatchingTerm', () => {
|
||||
const channels = [
|
||||
{name: 'channel1', display_name: 'Channel One'},
|
||||
{name: 'channel2', display_name: 'Channel Two'},
|
||||
{display_name: 'Undefined name'},
|
||||
{},
|
||||
] as Channel[];
|
||||
|
||||
it('should filter channels by name or display name', () => {
|
||||
let result = filterChannelsMatchingTerm(channels, 'channel1');
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].name).toBe('channel1');
|
||||
|
||||
result = filterChannelsMatchingTerm(channels, 'Channel One');
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].display_name).toBe('Channel One');
|
||||
|
||||
result = filterChannelsMatchingTerm([...channels, undefined] as Channel[], 'Channel One');
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].display_name).toBe('Channel One');
|
||||
});
|
||||
|
||||
it('should return an empty array if no channels match the term', () => {
|
||||
const result = filterChannelsMatchingTerm(channels, 'nonexistent');
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Channel, General, Permissions} from '@constants';
|
||||
import {t, DEFAULT_LOCALE} from '@i18n';
|
||||
import {hasPermission} from '@utils/role';
|
||||
import {defineMessages, type IntlShape} from 'react-intl';
|
||||
|
||||
import {generateId} from '../general';
|
||||
import {cleanUpUrlable} from '../url';
|
||||
import {Channel, General, Permissions} from '@constants';
|
||||
import {DEFAULT_LOCALE} from '@i18n';
|
||||
import {generateId} from '@utils/general';
|
||||
import {hasPermission} from '@utils/role';
|
||||
import {cleanUpUrlable} from '@utils/url';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
export function getDirectChannelName(id: string, otherId: string): string {
|
||||
let handle;
|
||||
|
|
@ -83,20 +83,20 @@ export function sortChannelsModelByDisplayName(locale: string, a: ChannelModel,
|
|||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase(), locale, {numeric: true});
|
||||
}
|
||||
|
||||
const displayNameValidationMessages = {
|
||||
const displayNameValidationMessages = defineMessages({
|
||||
display_name_required: {
|
||||
id: t('mobile.rename_channel.display_name_required'),
|
||||
id: 'mobile.rename_channel.display_name_required',
|
||||
defaultMessage: 'Channel name is required',
|
||||
},
|
||||
display_name_maxLength: {
|
||||
id: t('mobile.rename_channel.display_name_maxLength'),
|
||||
id: 'mobile.rename_channel.display_name_maxLength',
|
||||
defaultMessage: 'Channel name must be less than {maxLength, number} characters',
|
||||
},
|
||||
display_name_minLength: {
|
||||
id: t('mobile.rename_channel.display_name_minLength'),
|
||||
id: 'mobile.rename_channel.display_name_minLength',
|
||||
defaultMessage: 'Channel name must be {minLength, number} or more characters',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const validateDisplayName = (intl: IntlShape, displayName: string): {error: string} => {
|
||||
let errorMessage;
|
||||
|
|
@ -117,6 +117,7 @@ export const validateDisplayName = (intl: IntlShape, displayName: string): {erro
|
|||
|
||||
default:
|
||||
errorMessage = '';
|
||||
break;
|
||||
}
|
||||
return {error: errorMessage};
|
||||
};
|
||||
|
|
|
|||
227
app/utils/deep_link/index.test.ts
Normal file
227
app/utils/deep_link/index.test.ts
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createIntl} from 'react-intl';
|
||||
|
||||
import {makeDirectChannel, switchToChannelByName} from '@actions/remote/channel';
|
||||
import {showPermalink} from '@actions/remote/permalink';
|
||||
import {fetchUsersByUsernames} from '@actions/remote/user';
|
||||
import {DeepLink, Launch, Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {t} from '@i18n';
|
||||
import WebsocketManager from '@managers/websocket_manager';
|
||||
import {getActiveServerUrl} from '@queries/app/servers';
|
||||
import {queryUsersByUsername} from '@queries/servers/user';
|
||||
import {dismissAllModalsAndPopToRoot} from '@screens/navigation';
|
||||
import {logError} from '@utils/log';
|
||||
import {addNewServer} from '@utils/server';
|
||||
|
||||
import {alertErrorWithFallback, errorBadChannel, errorUnkownUser} from '../draft';
|
||||
|
||||
import {alertInvalidDeepLink, getLaunchPropsFromDeepLink, handleDeepLink} from '.';
|
||||
|
||||
jest.mock('@actions/remote/user', () => ({
|
||||
fetchUsersByUsernames: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@actions/remote/permalink', () => ({
|
||||
showPermalink: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@queries/app/servers', () => ({
|
||||
getActiveServerUrl: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@queries/servers/user', () => ({
|
||||
getCurrentUser: jest.fn(),
|
||||
queryUsersByUsername: jest.fn(() => ({fetchIds: jest.fn(() => ['user-id'])})),
|
||||
}));
|
||||
|
||||
jest.mock('@database/manager', () => ({
|
||||
searchUrl: jest.fn(),
|
||||
setActiveServerDatabase: jest.fn(),
|
||||
getServerDatabaseAndOperator: jest.fn(() => ({database: {}, operator: {}})),
|
||||
}));
|
||||
|
||||
jest.mock('@managers/websocket_manager', () => ({
|
||||
initializeClient: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@store/navigation_store', () => ({
|
||||
getVisibleScreen: jest.fn(() => 'HOME'),
|
||||
hasModalsOpened: jest.fn(() => false),
|
||||
waitUntilScreenHasLoaded: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@utils/server', () => ({
|
||||
addNewServer: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@actions/remote/channel', () => ({
|
||||
makeDirectChannel: jest.fn(),
|
||||
switchToChannelByName: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@utils/draft', () => ({
|
||||
errorBadChannel: jest.fn(),
|
||||
errorUnkownUser: jest.fn(),
|
||||
alertErrorWithFallback: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@utils/log', () => ({
|
||||
logError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@i18n', () => ({
|
||||
DEFAULT_LOCALE: 'en',
|
||||
getTranslations: jest.fn(() => ({})),
|
||||
t: jest.fn((id) => id),
|
||||
}));
|
||||
|
||||
describe('handleDeepLink', () => {
|
||||
const intl = createIntl({locale: 'en', messages: {}});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return error for invalid deep link', async () => {
|
||||
const result = await handleDeepLink('invalid-url');
|
||||
expect(result).toEqual({error: true});
|
||||
});
|
||||
|
||||
it('should add new server if not existing', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://currentserver.com');
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('');
|
||||
const result = await handleDeepLink('https://newserver.com/team/channels/town-square');
|
||||
expect(addNewServer).toHaveBeenCalledWith(Preferences.THEMES.denim, 'newserver.com', undefined, {type: DeepLink.Channel,
|
||||
data: {
|
||||
serverUrl: 'newserver.com',
|
||||
channelName: 'town-square',
|
||||
teamName: 'team',
|
||||
},
|
||||
url: 'https://newserver.com/team/channels/town-square',
|
||||
});
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should handle existing server and switch to home screen', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://currentserver.com');
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
const result = await handleDeepLink('https://existingserver.com/team/channels/town-square');
|
||||
expect(dismissAllModalsAndPopToRoot).toHaveBeenCalled();
|
||||
expect(DatabaseManager.setActiveServerDatabase).toHaveBeenCalledWith('https://existingserver.com');
|
||||
expect(WebsocketManager.initializeClient).toHaveBeenCalledWith('https://existingserver.com');
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should switch to channel by name for Channel deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
const result = await handleDeepLink('https://existingserver.com/team/channels/town-square', intl);
|
||||
expect(switchToChannelByName).toHaveBeenCalledWith('https://existingserver.com', 'town-square', 'team', errorBadChannel, intl);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should create direct message for DirectMessage deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce({fetchIds: jest.fn(() => ['user-id'])});
|
||||
const result = await handleDeepLink('https://existingserver.com/team/messages/@user-id', intl);
|
||||
expect(makeDirectChannel).toHaveBeenCalledWith('https://existingserver.com', 'user-id', '', true);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should fetch user and create direct message if user not found locally', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce({fetchIds: jest.fn(() => [])});
|
||||
(fetchUsersByUsernames as jest.Mock).mockResolvedValueOnce({users: [{id: 'user-id'}]});
|
||||
const result = await handleDeepLink('https://existingserver.com/team/messages/@user-id', intl);
|
||||
expect(makeDirectChannel).toHaveBeenCalledWith('https://existingserver.com', 'user-id', '', true);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should show unknown user error if user not found', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce({fetchIds: jest.fn(() => [])});
|
||||
(fetchUsersByUsernames as jest.Mock).mockResolvedValueOnce({users: []});
|
||||
const result = await handleDeepLink('https://existingserver.com/team/messages/@user-id', intl);
|
||||
expect(errorUnkownUser).toHaveBeenCalledWith(intl);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should switch to group message channel for GroupMessage deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
const result = await handleDeepLink('https://existingserver.com/team/messages/7b35c77a645e1906e03a2c330f89203385db102f', intl);
|
||||
expect(switchToChannelByName).toHaveBeenCalledWith('https://existingserver.com', '7b35c77a645e1906e03a2c330f89203385db102f', 'team', errorBadChannel, intl);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should show permalink for Permalink deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
const postid = '7b35c77a645e1906e03a2c330f';
|
||||
const result = await handleDeepLink(`https://existingserver.com/team/pl/${postid}`, intl);
|
||||
expect(showPermalink).toHaveBeenCalledWith('https://existingserver.com', 'team', postid);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should log error and return error true on failure', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockImplementationOnce(() => {
|
||||
throw new Error('DB does not exist error');
|
||||
});
|
||||
const result = await handleDeepLink('https://existingserver.com/team/messages/7b35c77a645e1906e03a2c330f89203385db102f');
|
||||
expect(logError).toHaveBeenCalledWith('Failed to open channel from deeplink', expect.any(Error), undefined);
|
||||
expect(result).toEqual({error: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLaunchPropsFromDeepLink', () => {
|
||||
it('should return launch props with launchError when deep link is invalid', () => {
|
||||
const result = getLaunchPropsFromDeepLink('invalid-url');
|
||||
|
||||
expect(result).toEqual({
|
||||
launchType: Launch.DeepLink,
|
||||
coldStart: false,
|
||||
launchError: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return launch props with extra data when deep link is valid', () => {
|
||||
const extraData = {
|
||||
type: DeepLink.Channel,
|
||||
data: {
|
||||
channelName: 'town-square',
|
||||
serverUrl: 'existingserver.com',
|
||||
teamName: 'team',
|
||||
},
|
||||
url: 'https://existingserver.com/team/channels/town-square',
|
||||
};
|
||||
const result = getLaunchPropsFromDeepLink('https://existingserver.com/team/channels/town-square', true);
|
||||
|
||||
expect(result).toEqual({
|
||||
launchType: Launch.DeepLink,
|
||||
coldStart: true,
|
||||
extra: extraData,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('alertInvalidDeepLink', () => {
|
||||
it('should call alertErrorWithFallback with correct arguments', () => {
|
||||
const intl = createIntl({locale: 'en', messages: {}});
|
||||
const message = {
|
||||
id: 'mobile.deep_link.invalid',
|
||||
defaultMessage: 'This link you are trying to open is invalid.',
|
||||
};
|
||||
|
||||
(t as jest.Mock).mockReturnValue(message.id);
|
||||
|
||||
alertInvalidDeepLink(intl);
|
||||
|
||||
expect(alertErrorWithFallback).toHaveBeenCalledWith(intl, {}, message);
|
||||
});
|
||||
});
|
||||
77
app/utils/document/index.test.ts
Normal file
77
app/utils/document/index.test.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {alertFailedToOpenDocument, alertDownloadDocumentDisabled, alertDownloadFailed} from './';
|
||||
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
jest.mock('react-native', () => ({
|
||||
Alert: {
|
||||
alert: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('alertUtils', () => {
|
||||
const intlMock = {
|
||||
formatMessage: jest.fn(),
|
||||
} as unknown as IntlShape;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should alert failed to open document with correct messages', () => {
|
||||
const file = {extension: 'pdf'} as FileInfo;
|
||||
|
||||
intlMock.formatMessage.
|
||||
|
||||
//@ts-expect-error type definition
|
||||
mockReturnValueOnce('Open Document failed').
|
||||
mockReturnValueOnce('An error occurred while opening the document. Please make sure you have a PDF viewer installed and try again.\n').
|
||||
mockReturnValueOnce('OK');
|
||||
|
||||
alertFailedToOpenDocument(file, intlMock);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Open Document failed',
|
||||
'An error occurred while opening the document. Please make sure you have a PDF viewer installed and try again.\n',
|
||||
[{text: 'OK'}],
|
||||
);
|
||||
});
|
||||
|
||||
it('should alert download document disabled with correct messages', () => {
|
||||
intlMock.formatMessage.
|
||||
|
||||
//@ts-expect-error type definition
|
||||
mockReturnValueOnce('Download disabled').
|
||||
mockReturnValueOnce('File downloads are disabled on this server. Please contact your System Admin for more details.\n').
|
||||
mockReturnValueOnce('OK');
|
||||
|
||||
alertDownloadDocumentDisabled(intlMock);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Download disabled',
|
||||
'File downloads are disabled on this server. Please contact your System Admin for more details.\n',
|
||||
[{text: 'OK'}],
|
||||
);
|
||||
});
|
||||
|
||||
it('should alert download failed with correct messages', () => {
|
||||
intlMock.formatMessage.
|
||||
|
||||
//@ts-expect-error type definition
|
||||
mockReturnValueOnce('Download failed').
|
||||
mockReturnValueOnce('An error occurred while downloading the file. Please check your internet connection and try again.\n').
|
||||
mockReturnValueOnce('OK');
|
||||
|
||||
alertDownloadFailed(intlMock);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Download failed',
|
||||
'An error occurred while downloading the file. Please check your internet connection and try again.\n',
|
||||
[{text: 'OK'}],
|
||||
);
|
||||
});
|
||||
});
|
||||
194
app/utils/draft/index.test.ts
Normal file
194
app/utils/draft/index.test.ts
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
// 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 {General} from '@constants';
|
||||
|
||||
import {
|
||||
errorBadChannel,
|
||||
errorUnkownUser,
|
||||
permalinkBadTeam,
|
||||
alertErrorWithFallback,
|
||||
alertAttachmentFail,
|
||||
textContainsAtAllAtChannel,
|
||||
textContainsAtHere,
|
||||
buildChannelWideMentionMessage,
|
||||
alertChannelWideMention,
|
||||
alertSendToGroups,
|
||||
getStatusFromSlashCommand,
|
||||
alertSlashCommandFailed,
|
||||
} from './';
|
||||
|
||||
jest.mock('react-native', () => {
|
||||
const RN = jest.requireActual('react-native');
|
||||
return {
|
||||
Platform: RN.Platform,
|
||||
NativeModules: {
|
||||
...RN.NativeModules,
|
||||
RNUtils: {
|
||||
getConstants: () => ({
|
||||
appGroupIdentifier: 'group.mattermost.rnbeta',
|
||||
appGroupSharedDirectory: {
|
||||
sharedDirectory: '',
|
||||
databasePath: '',
|
||||
},
|
||||
}),
|
||||
addListener: jest.fn(),
|
||||
removeListeners: jest.fn(),
|
||||
isRunningInSplitView: jest.fn().mockReturnValue({isSplit: false, isTablet: false}),
|
||||
getDeliveredNotifications: jest.fn().mockResolvedValue([]),
|
||||
removeChannelNotifications: jest.fn().mockImplementation(),
|
||||
removeThreadNotifications: jest.fn().mockImplementation(),
|
||||
removeServerNotifications: jest.fn().mockImplementation(),
|
||||
},
|
||||
},
|
||||
Alert: {
|
||||
alert: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@i18n', () => ({
|
||||
t: (id: string) => id,
|
||||
}));
|
||||
|
||||
describe('draft utils', () => {
|
||||
const intl = createIntl({locale: 'en', messages: {}});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should alert error bad channel', () => {
|
||||
errorBadChannel(intl);
|
||||
expect(Alert.alert).toHaveBeenCalledWith('', 'This link belongs to a deleted channel or to a channel to which you do not have access.', undefined);
|
||||
});
|
||||
|
||||
it('should alert error unknown user', () => {
|
||||
errorUnkownUser(intl);
|
||||
expect(Alert.alert).toHaveBeenCalledWith('', 'We can\'t redirect you to the DM. The user specified is unknown.', undefined);
|
||||
});
|
||||
|
||||
it('should alert permalink bad team', () => {
|
||||
permalinkBadTeam(intl);
|
||||
expect(Alert.alert).toHaveBeenCalledWith('', 'This link belongs to a deleted team or to a team to which you do not have access.', undefined);
|
||||
});
|
||||
|
||||
it('should alert error with fallback', () => {
|
||||
const error = {message: 'Test Error'};
|
||||
const fallback = {id: 'fallback.id', defaultMessage: 'Fallback Message'};
|
||||
alertErrorWithFallback(intl, error, fallback);
|
||||
expect(Alert.alert).toHaveBeenCalledWith('', 'Test Error', undefined);
|
||||
});
|
||||
|
||||
it('should alert error with fallback when network request failed', () => {
|
||||
const error = {message: 'Network request failed'};
|
||||
const fallback = {id: 'fallback.id', defaultMessage: 'Fallback Message'};
|
||||
|
||||
alertErrorWithFallback(intl, error, fallback);
|
||||
expect(Alert.alert).toHaveBeenCalledWith('', 'Fallback Message', undefined);
|
||||
});
|
||||
|
||||
it('should alert attachment fail', () => {
|
||||
const accept = jest.fn();
|
||||
const cancel = jest.fn();
|
||||
|
||||
alertAttachmentFail(intl, accept, cancel);
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Attachment failure',
|
||||
'Some attachments failed to upload to the server. Are you sure you want to post the message?',
|
||||
[
|
||||
{text: 'No', onPress: cancel},
|
||||
{text: 'Yes', onPress: accept},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect @all and @channel in text', () => {
|
||||
const text = 'Hello @all';
|
||||
expect(textContainsAtAllAtChannel(text)).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect @here in text', () => {
|
||||
const text = 'Hello @here';
|
||||
expect(textContainsAtHere(text)).toBe(true);
|
||||
});
|
||||
|
||||
it('should build channel-wide mention message with timezones', () => {
|
||||
const membersCount = 10;
|
||||
const channelTimezoneCount = 3;
|
||||
|
||||
let message = buildChannelWideMentionMessage(intl, membersCount, channelTimezoneCount, true);
|
||||
expect(message).toBe('By using @here you are about to send notifications up to 9 people in 3 timezones. Are you sure you want to do this?');
|
||||
|
||||
message = buildChannelWideMentionMessage(intl, membersCount, channelTimezoneCount, false);
|
||||
expect(message).toBe('By using @all or @channel you are about to send notifications to 9 people in 3 timezones. Are you sure you want to do this?');
|
||||
});
|
||||
|
||||
it('should build channel-wide mention message without timezones', () => {
|
||||
const membersCount = 10;
|
||||
const channelTimezoneCount = 0;
|
||||
|
||||
let message = buildChannelWideMentionMessage(intl, membersCount, channelTimezoneCount, true);
|
||||
expect(message).toBe('By using @here you are about to send notifications to up to 9 people. Are you sure you want to do this?');
|
||||
|
||||
message = buildChannelWideMentionMessage(intl, membersCount, channelTimezoneCount, false);
|
||||
expect(message).toBe('By using @all or @channel you are about to send notifications to 9 people. Are you sure you want to do this?');
|
||||
});
|
||||
|
||||
it('should alert channel-wide mention', () => {
|
||||
const notifyAllMessage = 'Notify all message';
|
||||
const accept = jest.fn();
|
||||
const cancel = jest.fn();
|
||||
|
||||
alertChannelWideMention(intl, notifyAllMessage, accept, cancel);
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Confirm sending notifications to entire channel',
|
||||
'Notify all message',
|
||||
[
|
||||
{text: 'Cancel', onPress: cancel},
|
||||
{text: 'Confirm', onPress: accept},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should alert send to groups', () => {
|
||||
const notifyAllMessage = 'Notify all message';
|
||||
const accept = jest.fn();
|
||||
const cancel = jest.fn();
|
||||
|
||||
alertSendToGroups(intl, notifyAllMessage, accept, cancel);
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Confirm sending notifications to groups',
|
||||
'Notify all message',
|
||||
[
|
||||
{text: 'Cancel', onPress: cancel},
|
||||
{text: 'Confirm', onPress: accept},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should get status from slash command', () => {
|
||||
General.STATUS_COMMANDS = ['status'];
|
||||
const message = '/status';
|
||||
expect(getStatusFromSlashCommand(message)).toBe('status');
|
||||
});
|
||||
|
||||
it('should not get status from non-status slash command', () => {
|
||||
General.STATUS_COMMANDS = ['status'];
|
||||
const message = '/nonstatus';
|
||||
expect(getStatusFromSlashCommand(message)).toBe('');
|
||||
});
|
||||
|
||||
it('should alert slash command failed', () => {
|
||||
const error = 'Error message';
|
||||
|
||||
alertSlashCommandFailed(intl, error);
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Error Executing Command',
|
||||
'Error message',
|
||||
);
|
||||
});
|
||||
});
|
||||
259
app/utils/emoji/helpers.test.ts
Normal file
259
app/utils/emoji/helpers.test.ts
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import {
|
||||
isEmoticon,
|
||||
isUnicodeEmoji,
|
||||
getEmoticonName,
|
||||
matchEmoticons,
|
||||
getValidEmojis,
|
||||
getEmojiName,
|
||||
isReactionMatch,
|
||||
isValidNamedEmoji,
|
||||
hasJumboEmojiOnly,
|
||||
doesMatchNamedEmoji,
|
||||
getEmojiFirstAlias,
|
||||
getEmojiByName,
|
||||
mapCustomEmojiNames,
|
||||
compareEmojis,
|
||||
isCustomEmojiEnabled,
|
||||
fillEmoji,
|
||||
getSkin,
|
||||
getEmojis,
|
||||
searchEmojis,
|
||||
} from './helpers';
|
||||
|
||||
import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';
|
||||
import type SystemModel from '@typings/database/models/servers/system';
|
||||
|
||||
jest.mock('@database/models/server/system');
|
||||
|
||||
describe('helpers.ts tests', () => {
|
||||
describe('isEmoticon', () => {
|
||||
it('should return true for valid emoticons', () => {
|
||||
expect(isEmoticon(':)')).toBe(true);
|
||||
expect(isEmoticon(';)')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid emoticons', () => {
|
||||
expect(isEmoticon(':-*')).toBe(false);
|
||||
expect(isEmoticon('hello')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUnicodeEmoji', () => {
|
||||
it('should return true for unicode emoji', () => {
|
||||
expect(isUnicodeEmoji('😊')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-emoji text', () => {
|
||||
expect(isUnicodeEmoji('hello')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmoticonName', () => {
|
||||
it('should return correct emoticon name', () => {
|
||||
expect(getEmoticonName(':)')).toBe('slightly_smiling_face');
|
||||
});
|
||||
|
||||
it('should return undefined for invalid emoticon', () => {
|
||||
expect(getEmoticonName('hello')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchEmoticons', () => {
|
||||
it('should match named, unicode, and emoticon emojis', () => {
|
||||
const text = 'Hello :) :smile: 😊';
|
||||
const result = matchEmoticons(text);
|
||||
expect(result).toEqual([':smile:', ' :)', '😊']);
|
||||
});
|
||||
|
||||
it('should return empty array for no matches', () => {
|
||||
expect(matchEmoticons('hello')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValidEmojis', () => {
|
||||
it('should return valid emoji names', () => {
|
||||
const emojis = [':smile:', '😊', ':)'];
|
||||
const customEmojis = [{name: 'custom_emoji'}] as CustomEmojiModel[];
|
||||
const result = getValidEmojis(emojis, customEmojis);
|
||||
expect(result).toContain('smile');
|
||||
});
|
||||
|
||||
it('should return empty array for no valid emojis', () => {
|
||||
const emojis = ['hello'];
|
||||
const customEmojis = [{name: 'custom_emoji'}] as CustomEmojiModel[];
|
||||
const result = getValidEmojis(emojis, customEmojis);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmojiName', () => {
|
||||
it('should return correct emoji name for named emoji', () => {
|
||||
expect(getEmojiName(':smile:', [])).toBe('smile');
|
||||
});
|
||||
|
||||
it('should return correct emoji name for unicode emoji', () => {
|
||||
expect(getEmojiName('😊', [])).toBe('blush');
|
||||
});
|
||||
|
||||
it('should return correct emoji name for emoticon', () => {
|
||||
expect(getEmojiName(':)', [])).toBe('slightly_smiling_face');
|
||||
});
|
||||
|
||||
it('should return undefined for invalid emoji', () => {
|
||||
expect(getEmojiName('hello', [])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isReactionMatch', () => {
|
||||
it('should return correct reaction match for valid reaction', () => {
|
||||
const custom = [{name: 'custom'}] as CustomEmojiModel[];
|
||||
expect(isReactionMatch('+:smile:', custom)).toEqual({add: true, emoji: 'smile'});
|
||||
expect(isReactionMatch('+:custom:', custom)).toEqual({add: true, emoji: 'custom'});
|
||||
expect(isReactionMatch('+:lala:', custom)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for invalid reaction', () => {
|
||||
expect(isReactionMatch(':hello:', [])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidNamedEmoji', () => {
|
||||
it('should return true for valid named emoji', () => {
|
||||
expect(isValidNamedEmoji('smile', [])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid named emoji', () => {
|
||||
expect(isValidNamedEmoji('hello', [])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasJumboEmojiOnly', () => {
|
||||
it('should return true for jumbo emojis only', () => {
|
||||
expect(hasJumboEmojiOnly('😊', [])).toBe(true);
|
||||
expect(hasJumboEmojiOnly('😊 😊', [])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-jumbo emojis', () => {
|
||||
expect(hasJumboEmojiOnly('hello', [])).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for strings with spaces only', () => {
|
||||
expect(hasJumboEmojiOnly(' ', [])).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for strings with custom emojis', () => {
|
||||
const custom = ['custom'];
|
||||
expect(hasJumboEmojiOnly('😊 :custom: :blush:', custom)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for strings with more than 8 emojis', () => {
|
||||
expect(hasJumboEmojiOnly('😊 😊 😊 😊 😊 😊 😊 😊 😊', [])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('doesMatchNamedEmoji', () => {
|
||||
it('should return true for valid named emoji', () => {
|
||||
expect(doesMatchNamedEmoji(':smile:')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid named emoji', () => {
|
||||
expect(doesMatchNamedEmoji('hello')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmojiFirstAlias', () => {
|
||||
it('should return first alias of emoji', () => {
|
||||
expect(getEmojiFirstAlias('smile')).toBe('smile');
|
||||
expect(getEmojiFirstAlias('lolo')).toBe('lolo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmojiByName', () => {
|
||||
it('should return emoji by name for standard emoji', () => {
|
||||
expect(getEmojiByName('smile', [])).toEqual(expect.objectContaining({short_names: ['smile'], category: 'smileys-emotion'}));
|
||||
});
|
||||
|
||||
it('should return emoji by name for custom emoji', () => {
|
||||
const customEmojis = [{name: 'custom_emoji'}] as CustomEmojiModel[];
|
||||
expect(getEmojiByName('custom_emoji', customEmojis)).toEqual({name: 'custom_emoji'});
|
||||
});
|
||||
|
||||
it('should return undefined for invalid emoji', () => {
|
||||
expect(getEmojiByName('hello', [])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapCustomEmojiNames', () => {
|
||||
it('should return names of custom emojis', () => {
|
||||
const customEmojis = [{name: 'custom_emoji'}] as CustomEmojiModel[];
|
||||
expect(mapCustomEmojiNames(customEmojis)).toEqual(['custom_emoji']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareEmojis', () => {
|
||||
it('should compare emojis correctly based on search term', () => {
|
||||
expect(compareEmojis('smile', 'grin', 'smi')).toBe(-1);
|
||||
expect(compareEmojis('grin', 'smile', 'smi')).toBe(1);
|
||||
expect(compareEmojis('smile', 'grin', '')).toBe(1);
|
||||
expect(compareEmojis('smile', 'smile', 'smi')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isCustomEmojiEnabled', () => {
|
||||
it('should return true if custom emoji is enabled in config', () => {
|
||||
expect(isCustomEmojiEnabled({EnableCustomEmoji: 'true'} as any)).toBe(true);
|
||||
|
||||
expect(isCustomEmojiEnabled({value: {EnableCustomEmoji: 'true'}} as SystemModel)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if custom emoji is disabled in config', () => {
|
||||
expect(isCustomEmojiEnabled({EnableCustomEmoji: 'false'} as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillEmoji', () => {
|
||||
it('should fill emoji correctly', () => {
|
||||
expect(fillEmoji('category', 1)).toEqual({name: 'smiley', aliases: ['smiley'], category: 'category'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSkin', () => {
|
||||
it('should return default for skin variations', () => {
|
||||
const emoji = {skin_variations: []};
|
||||
expect(getSkin(emoji)).toBe('default');
|
||||
});
|
||||
|
||||
it('should return first skin for skins array', () => {
|
||||
const emoji = {skins: ['light']};
|
||||
expect(getSkin(emoji)).toBe('light');
|
||||
});
|
||||
|
||||
it('should return null for no skin variations', () => {
|
||||
const emoji = {};
|
||||
expect(getSkin(emoji)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmojis', () => {
|
||||
it('should return emojis for specified skin tone', () => {
|
||||
const custom = [{name: 'hey_custom'}] as CustomEmojiModel[];
|
||||
expect(getEmojis('dark', custom)).toEqual(expect.arrayContaining(['grinning', 'smiley', 'smile', 'grin', 'laughing', 'satisfied', 'sweat_smile', 'rolling_on_the_floor_laughing', 'rofl', 'joy', 'hey_custom']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchEmojis', () => {
|
||||
it('should return emojis matching search term', () => {
|
||||
const options = {findAllMatches: true, ignoreLocation: true, includeMatches: true, shouldSort: false, includeScore: true};
|
||||
const emojis = getEmojis('light', []);
|
||||
const fuse = new Fuse(emojis, options);
|
||||
const result = searchEmojis(fuse, 'smile');
|
||||
expect(result).toEqual(['smile', 'smile_cat', 'smiley', 'smiley_cat', 'sweat_smile']);
|
||||
expect(searchEmojis(fuse, 'cawabunga')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -42,7 +42,7 @@ const RE_REACTION = /^(\+|-):([^:\s]+):\s*$/;
|
|||
|
||||
const MAX_JUMBO_EMOJIS = 8;
|
||||
|
||||
function isEmoticon(text: string) {
|
||||
export function isEmoticon(text: string) {
|
||||
for (const emoticon of Object.keys(RE_EMOTICON)) {
|
||||
const reEmoticon = RE_EMOTICON[emoticon];
|
||||
const matchEmoticon = text.match(reEmoticon);
|
||||
|
|
@ -106,9 +106,17 @@ export function getEmojiName(emoji: string, customEmojiNames: string[]) {
|
|||
|
||||
const matchUnicodeEmoji = emoji.match(RE_UNICODE_EMOJI);
|
||||
if (matchUnicodeEmoji) {
|
||||
const index = EmojiIndicesByUnicode.get(matchUnicodeEmoji[0]);
|
||||
if (index != null) {
|
||||
return fillEmoji('', Emojis[index]).name;
|
||||
function getEmojiHexValue(code: string) {
|
||||
let hexValue = '';
|
||||
for (const char of code) {
|
||||
hexValue += char.codePointAt(0)?.toString(16).toLowerCase() + ' ';
|
||||
}
|
||||
return hexValue.trim();
|
||||
}
|
||||
const hex = getEmojiHexValue(matchUnicodeEmoji[0]);
|
||||
const index = EmojiIndicesByUnicode.get(hex);
|
||||
if (index != null && Emojis[index]) {
|
||||
return fillEmoji('', index).name;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -303,11 +311,11 @@ export function compareEmojis(emojiA: string | Partial<EmojiType>, emojiB: strin
|
|||
}
|
||||
|
||||
export const isCustomEmojiEnabled = (config: ClientConfig | SystemModel) => {
|
||||
if (config instanceof SystemModel) {
|
||||
return config?.value?.EnableCustomEmoji === 'true';
|
||||
if ('value' in config) {
|
||||
return config.value?.EnableCustomEmoji === 'true';
|
||||
}
|
||||
|
||||
return config?.EnableCustomEmoji === 'true';
|
||||
return config.EnableCustomEmoji === 'true';
|
||||
};
|
||||
|
||||
export function fillEmoji(category: string, index: number) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue