Add unit tests to utils config, datetime, error_handling, errors and files (#8018)

This commit is contained in:
Elias Nahum 2024-06-15 01:29:03 +08:00 committed by GitHub
parent bc29223315
commit dddd09aa9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 312 additions and 8 deletions

15
app/utils/config.test.ts Normal file
View file

@ -0,0 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {hasReliableWebsocket} from './config';
describe('Config utilities', () => {
test('hasReliableWebsocket', () => {
expect(hasReliableWebsocket('5.8.0')).toBe(false);
expect(hasReliableWebsocket('6.4.0', 'false')).toBe(false);
expect(hasReliableWebsocket('6.4.0', 'true')).toBe(true);
expect(hasReliableWebsocket('9.4.0', 'false')).toBe(true);
expect(hasReliableWebsocket('9.4.0', 'true')).toBe(true);
expect(hasReliableWebsocket('9.4.0')).toBe(true);
});
});

View file

@ -0,0 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {isSameDate, isSameMonth, isSameYear, isToday, isYesterday} from './datetime';
describe('Datetime', () => {
test('isSameDate (isSameMonth / isSameYear)', () => {
expect(isSameDate(new Date('2024-03-28 02:23:27'), new Date('2025-03-28 02:23:27'))).toBe(false);
expect(isSameDate(new Date('2024-03-28 02:23:27'), new Date('2024-02-28 02:23:27'))).toBe(false);
expect(isSameDate(new Date('2024-03-28 02:23:27'), new Date('2024-03-18 02:23:27'))).toBe(false);
expect(isSameDate(new Date('2024-03-28 02:23:27'), new Date('2024-03-28 00:00:00'))).toBe(true);
expect(isSameDate(new Date('2024-03-28 02:23:27'))).toBe(false);
expect(isSameDate(new Date())).toBe(true);
});
test('isSameMonth with default', () => {
expect(isSameMonth(new Date('2024-03-28 02:23:27'))).toBe(false);
expect(isSameMonth(new Date())).toBe(true);
});
test('isSameYear with default', () => {
expect(isSameYear(new Date('2022-03-28 02:23:27'))).toBe(false);
expect(isSameYear(new Date())).toBe(true);
});
test('isToday', () => {
expect(isToday(new Date('2024-03-28 02:23:27'))).toBe(false);
expect(isToday(new Date())).toBe(true);
});
test('isYesteday', () => {
expect(isYesterday(new Date('2024-03-28 02:23:27'))).toBe(false);
const today = new Date();
today.setDate(today.getDate() - 1);
expect(isYesterday(today)).toBe(true);
});
});

View file

@ -0,0 +1,69 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Alert} from 'react-native';
import Exception from 'react-native-exception-handler';
import {dismissAllModals, dismissAllOverlays} from '@screens/navigation';
import testHelper from '@test/test_helper';
import * as Sentry from '@utils/sentry';
import errorHandling from './error_handling';
import * as Log from './log';
jest.mock('react-native-exception-handler', () => ({
setJSExceptionHandler: jest.fn((callback: () => void, allowInDevMode: boolean) => {
if (!allowInDevMode) {
callback();
}
}),
}));
jest.mock('@utils/log', () => ({
logWarning: jest.fn(() => ''),
}));
describe('JavascriptAndNativeErrorHandler', () => {
const warning = jest.spyOn(Log, 'logWarning');
const error = 'some error';
test('Initialization', () => {
const setJSExceptionHandler = jest.spyOn(Exception, 'setJSExceptionHandler');
const initializeSentry = jest.spyOn(Sentry, 'initializeSentry');
errorHandling.initializeErrorHandling();
expect(setJSExceptionHandler).toHaveBeenCalledTimes(1);
expect(initializeSentry).toHaveBeenCalledTimes(1);
expect(setJSExceptionHandler).toHaveBeenCalledWith(errorHandling.errorHandler, false);
});
test('nativeErrorHander', () => {
const captureException = jest.spyOn(Sentry, 'captureException');
errorHandling.nativeErrorHandler(error);
expect(warning).toHaveBeenCalledTimes(1);
expect(warning).toHaveBeenCalledWith(`Handling native error ${error}`);
expect(captureException).toHaveBeenCalledTimes(1);
expect(captureException).toHaveBeenCalledWith(error);
});
test('errorHandler', async () => {
const captureJSException = jest.spyOn(Sentry, 'captureJSException');
errorHandling.errorHandler(null, false);
expect(warning).toHaveBeenCalledTimes(0);
errorHandling.errorHandler(error, true);
expect(warning).toHaveBeenCalledTimes(1);
expect(warning).toHaveBeenCalledWith('Handling Javascript error', error, true);
expect(captureJSException).toHaveBeenCalledTimes(1);
expect(captureJSException).toHaveBeenCalledWith(error, true);
const throwError = new Error(error);
const alert = jest.spyOn(Alert, 'alert');
errorHandling.errorHandler(throwError, true);
expect(alert?.mock?.calls?.[0]?.length).toBe(4);
alert?.mock.calls?.[0]?.[2]?.[0]?.onPress?.();
expect(dismissAllModals).toHaveBeenCalledTimes(1);
await testHelper.wait(20);
expect(dismissAllOverlays).toHaveBeenCalledTimes(1);
});
});

76
app/utils/errors.test.ts Normal file
View file

@ -0,0 +1,76 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {createIntl} from 'react-intl';
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
import {
isServerError,
isErrorWithMessage,
isErrorWithDetails,
isErrorWithIntl,
isErrorWithStatusCode,
isErrorWithUrl,
getFullErrorMessage,
} from './errors';
describe('Errors', () => {
test('isServerError', () => {
expect(isServerError('error')).toBe(false);
expect(isServerError({message: 'some error'})).toBe(false);
expect(isServerError({server_error_id: 'error_id'})).toBe(true);
});
test('isErrorWithMessage', () => {
expect(isErrorWithMessage('error')).toBe(false);
expect(isErrorWithMessage({message: 'some error'})).toBe(true);
expect(isErrorWithMessage({server_error_id: 'error_id'})).toBe(false);
});
test('isErrorWithDetails', () => {
expect(isErrorWithDetails('error')).toBe(false);
expect(isErrorWithDetails({message: 'some error'})).toBe(false);
expect(isErrorWithDetails({details: 'more info'})).toBe(true);
});
test('isErrorWithIntl', () => {
expect(isErrorWithIntl('error')).toBe(false);
expect(isErrorWithIntl({message: 'some error'})).toBe(false);
expect(isErrorWithIntl({intl: {id: 'some_error_id', defaultMessage: 'message text'}})).toBe(true);
});
test('isErrorWithStatusCode', () => {
expect(isErrorWithStatusCode('error')).toBe(false);
expect(isErrorWithStatusCode({message: 'some error'})).toBe(false);
expect(isErrorWithStatusCode({status_code: 95})).toBe(true);
});
test('isErrorWithUrl', () => {
expect(isErrorWithUrl('error')).toBe(false);
expect(isErrorWithUrl({message: 'some error'})).toBe(false);
expect(isErrorWithUrl({url: 'http://localhost:8065'})).toBe(true);
});
test('getFullErrorMessage', () => {
const locale = DEFAULT_LOCALE;
const intl = createIntl({locale, messages: getTranslations(locale)});
expect(getFullErrorMessage('error', intl)).toBe('error');
expect(getFullErrorMessage({details: 'more info', message: 'error message'}, intl)).toBe('error message; more info');
expect(getFullErrorMessage({details: 'more info', message: 'error message'}, intl, 3)).toBe('error message; error message');
expect(getFullErrorMessage({
details: 'more info',
message: 'error message',
intl: {id: 'some_error_id', defaultMessage: 'message text'},
}, intl)).toBe('message text; more info');
expect(getFullErrorMessage({
details: 'more info',
message: 'error message',
intl: {id: 'some_error_id', defaultMessage: 'default message text'},
})).toBe('default message text; more info');
expect(getFullErrorMessage({
details: 'more info',
})).toBe('Unknown error; more info');
});
});

View file

@ -8,9 +8,8 @@ export function isServerError(obj: unknown): obj is {server_error_id?: string} {
typeof obj === 'object' &&
obj !== null &&
(
!('server_error_id' in obj) ||
typeof obj.server_error_id === 'string' ||
typeof obj.server_error_id === 'undefined'
('server_error_id' in obj) &&
typeof obj.server_error_id === 'string'
)
);
}
@ -56,11 +55,8 @@ export function isErrorWithUrl(obj: unknown): obj is {url?: string} {
return (
typeof obj === 'object' &&
obj !== null &&
(
!('url' in obj) ||
typeof obj.url === 'string' ||
typeof obj.url === 'undefined'
)
('url' in obj) &&
typeof obj.url === 'string'
);
}

110
app/utils/files.test.ts Normal file
View file

@ -0,0 +1,110 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import testHelper from '@test/test_helper';
import {toMilliseconds} from './datetime';
import {getNumberFileMenuOptions, getChannelNamesWithID, getOrderedFileInfos, getFileInfosIndexes, getOrderedGalleryItems, pathWithPrefix} from './files';
import type ChannelModel from '@typings/database/models/servers/channel';
describe('Files utils', () => {
const buildFileInfos = (): FileInfo[] => {
return [{
id: testHelper.generateId(),
create_at: Date.now() + toMilliseconds({days: 3, hours: 12, minutes: 8, seconds: 23}),
post_id: '123',
size: 10,
height: 0,
width: 0,
mime_type: 'application/pdf',
user_id: 'me',
extension: 'pdf',
name: 'file 3',
has_preview_image: false,
}, {
id: testHelper.generateId(),
create_at: Date.now() + toMilliseconds({days: 1, hours: 14}),
post_id: '123',
size: 10,
height: 100,
width: 100,
mime_type: 'image/png',
user_id: 'me',
extension: 'png',
name: 'file 2',
has_preview_image: true,
}, {
id: testHelper.generateId(),
create_at: Date.now() + toMilliseconds({days: 1, hours: 12, minutes: 10}),
post_id: '123',
size: 10,
height: 200,
width: 200,
mime_type: 'video/mp4',
user_id: 'me',
extension: 'mp4',
name: 'file 1',
has_preview_image: false,
}];
};
test('getNumberFileMenuOptions', () => {
expect(getNumberFileMenuOptions(false, false)).toBe(1);
expect(getNumberFileMenuOptions(true, false)).toBe(2);
expect(getNumberFileMenuOptions(false, true)).toBe(2);
expect(getNumberFileMenuOptions(true, true)).toBe(3);
});
test('getChannelNamesWithID', () => {
const displayNames = ['channel 1', 'channel 2', 'channel 3'];
const channels = displayNames.map((d, i) => ({
id: i.toString(),
displayName: d,
} as ChannelModel));
const expected = channels.reduce<Record<string, string>>((obj, channel) => {
obj[channel.id] = channel.displayName;
return obj;
}, {});
expect(getChannelNamesWithID(channels)).toEqual(expected);
});
test('getOrderedFileInfos', () => {
let fileInfos = buildFileInfos();
const result = getOrderedFileInfos(fileInfos);
const names = result.map((f) => f.name);
expect(names).toEqual(['file 3', 'file 2', 'file 1']);
// testing the variant without setting create_at
fileInfos = fileInfos.map((f) => ({
...f,
create_at: undefined,
}));
const result2 = getOrderedFileInfos(fileInfos);
const names2 = result2.map((f) => f.name);
expect(names2).toEqual(names);
});
test('getFileInfosIndexes', () => {
const fileInfos = getOrderedFileInfos(buildFileInfos());
const result = [0, 1, 2].reduce<Record<string, number>>((obj, index) => {
obj[fileInfos[index].id!] = index;
return obj;
}, {});
expect(getFileInfosIndexes(fileInfos)).toEqual(result);
});
test('getOrderedGalleryItems', () => {
const fileInfos = getOrderedFileInfos(buildFileInfos());
const result = getOrderedGalleryItems(fileInfos);
const types = result.map((f) => f.type);
expect(types).toEqual(['file', 'image', 'video']);
});
test('pathWithPrefix', () => {
expect(pathWithPrefix('file://', 'file://something')).toEqual('file://something');
expect(pathWithPrefix('file://', 'something')).toEqual('file://something');
});
});

View file

@ -369,6 +369,7 @@ jest.mock('@screens/navigation', () => ({
dismissAllModalsAndPopToScreen: jest.fn(),
dismissAllModalsAndPopToRoot: jest.fn(),
dismissOverlay: jest.fn(() => Promise.resolve()),
dismissAllOverlays: jest.fn(() => Promise.resolve()),
}));
jest.mock('@mattermost/react-native-emm', () => ({