Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

Просмотреть файл

@@ -0,0 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {GlobalState} from '@mattermost/types/store';
export const emptyLimits: () => GlobalState['entities']['cloud']['limits'] = () => ({
limitsLoaded: true,
limits: {},
});

Просмотреть файл

@@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {TeamsState} from '@mattermost/types/teams';
import {TestHelper} from 'utils/test_helper';
export const emptyTeams: () => TeamsState = () => ({
currentTeamId: 'current_team_id',
teams: {
current_team_id: TestHelper.getTeamMock({id: 'current_team_id'}),
},
myMembers: {},
membersInTeam: {},
stats: {},
groupsAssociatedToTeam: {},
totalCount: 0,
});

Просмотреть файл

@@ -0,0 +1,45 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {GlobalState} from '@mattermost/types/store';
import {General} from 'mattermost-redux/constants';
import {TestHelper} from 'utils/test_helper';
const emptyOtherUsersState: Omit<GlobalState['entities']['users'], 'profiles' | 'currentUserId'> = {
isManualStatus: {},
mySessions: [],
myAudits: [],
profilesInTeam: {},
profilesNotInTeam: {},
profilesWithoutTeam: new Set(),
profilesInChannel: {},
profilesNotInChannel: {},
profilesInGroup: {},
profilesNotInGroup: {},
statuses: {},
stats: {},
myUserAccessTokens: {},
lastActivity: {},
};
export const adminUsersState: () => GlobalState['entities']['users'] = () => ({
...emptyOtherUsersState,
currentUserId: 'current_user_id',
profiles: {
current_user_id: {
...TestHelper.getUserMock({id: 'current_user_id'}),
roles: General.SYSTEM_ADMIN_ROLE,
},
},
});
export const endUsersState: () => GlobalState['entities']['users'] = () => ({
...emptyOtherUsersState,
currentUserId: 'current_user_id',
profiles: {
current_user_id: {
...TestHelper.getUserMock({id: 'current_user_id'}),
roles: General.CHANNEL_USER_ROLE,
},
},
});

Просмотреть файл

@@ -0,0 +1,90 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const samplePlugin1 = {
id: 'mattermost-autolink',
name: 'Autolink',
description: 'Automatically rewrite text matching a regular expression into a Markdown link.',
version: '1.1.0',
settings_schema: {
header: 'Configure this plugin directly in the config.json file. Learn more [in our documentation](https://github.com/mattermost/mattermost-plugin-autolink/blob/master/README.md).\n\nTo report an issue, make a suggestion or a contribution, [check the plugin repository](https://github.com/mattermost/mattermost-plugin-autolink).',
footer: '',
settings: [
{
key: 'EnableAdminCommand',
display_name: 'Enable administration with /autolink command',
type: 'bool',
help_text: '',
placeholder: '',
default: false,
},
],
},
active: true,
};
export const samplePlugin2 = {
id: 'Some-random-plugin',
name: 'Random',
description: 'Automatically generate random numbers',
version: '1.1.0',
settings_schema: {
header: 'random plugin header',
footer: 'random plugin footer',
settings: [
{
key: 'GenerateRandomNumber',
display_name: 'Generate with /generateRand command',
type: 'bool',
help_text: '/generateRand 10',
placeholder: '',
default: false,
},
{
key: 'setRange',
display_name: 'set range with /setRange command',
type: 'bool',
help_text: '',
placeholder: '',
default: false,
},
],
},
active: true,
};
export const samplePlugin3 = {
id: 'plugin-with-markdown',
name: 'markdown',
description: 'click [here](http://localhost:8080)',
version: '1.1.0',
settings_schema: {
header: 'random plugin header',
footer: 'random plugin footer',
settings: [
{
label: 'Markdown plugin label',
key: 'Markdown plugin',
display_name: 'Markdown',
type: 'bool',
help_text: 'click [here](http://localhost:8080)',
placeholder: '',
default: false,
},
],
},
active: true,
};
export const samplePlugin4 = {
id: 'plugin-without-settings',
name: 'without-settings',
description: 'click [here](http://localhost:8080)',
version: '1.1.0',
settings_schema: {
header: 'random plugin header',
footer: 'random plugin footer',
settings: [],
},
active: true,
};

29
webapp/channels/src/tests/helpers/date.ts Обычный файл
Просмотреть файл

@@ -0,0 +1,29 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const fakeDate = (expected: Date): () => void => {
const OGDate = Date;
// If any Date or number is passed to the constructor
// use that instead of our mocked date
function MockDate(mockOverride?: Date | number) {
return new OGDate(mockOverride || expected);
}
MockDate.UTC = OGDate.UTC;
MockDate.parse = OGDate.parse;
MockDate.now = () => expected.getTime();
// Give our mock Date has the same prototype as Date
// Some libraries rely on this to identify Date objects
MockDate.prototype = OGDate.prototype;
// Our mock is not a full implementation of Date
// Types will not match but it's good enough for our tests
global.Date = MockDate as any;
// Callback function to remove the Date mock
return () => {
global.Date = OGDate;
};
};

Просмотреть файл

@@ -0,0 +1,113 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {
ExoticComponent,
ForwardRefExoticComponent,
ReactElement,
} from 'react';
import {
createIntl,
injectIntl,
IntlShape,
IntlProvider,
} from 'react-intl';
import {
shallow,
mount,
ShallowRendererProps,
MountRendererProps,
} from 'enzyme';
import defaultMessages from 'i18n/en.json';
export const defaultIntl = createIntl({
locale: 'en',
defaultLocale: 'en',
timeZone: 'Etc/UTC',
messages: defaultMessages,
textComponent: 'span',
});
function unwrapForwardRef<WrappedComponentElement extends ReactElement>(element: ReactElement): WrappedComponentElement {
const {type, props} = element as ReactElement<any, ExoticComponent>;
if (type.$$typeof && type.$$typeof === Symbol.for('react.forward_ref')) {
type ForwardRefComponent = ForwardRefExoticComponent<any> & {
render: () => ReactElement;
};
return React.cloneElement(
(type as ForwardRefComponent).render(),
props,
) as WrappedComponentElement;
}
return element as WrappedComponentElement;
}
type IntlInjectedElement = ReactElement<any, ReturnType<typeof injectIntl>>;
export function isIntlInjectedElement(element: ReactElement): element is IntlInjectedElement {
const {type} = element;
if (typeof type === 'function' && type.name === 'WithIntl') {
return true;
}
return false;
}
interface ShallowWithIntlOptions extends ShallowRendererProps {
intl?: IntlShape;
}
export function shallowWithIntl<T extends IntlInjectedElement>(element: T, options?: ShallowWithIntlOptions) {
const {intl = defaultIntl, ...shallowOptions} = options || {};
// eslint-disable-next-line no-param-reassign
element = unwrapForwardRef<T>(element);
if (!isIntlInjectedElement(element)) {
throw new Error('shallowWithIntl() allows only components wrapped by injectIntl() HOC. Use shallow() instead.');
}
return shallow(
// Unwrap injectIntl
<element.type.WrappedComponent
intl={intl}
{...element.props}
/>,
// Override options
shallowOptions,
);
}
// for non-mounted use cases like react-testing-library
export function withIntl(element: ReactElement) {
return <IntlProvider {...defaultIntl}>{element}</IntlProvider>;
}
interface MountWithIntlOptions extends MountRendererProps {
intl?: IntlShape;
}
export function mountWithIntl<T extends ReactElement | IntlInjectedElement>(element: T, options?: MountWithIntlOptions) {
const {intl = defaultIntl, ...mountOptions} = options || {};
// Unwrap injectIntl
const newElement = isIntlInjectedElement(element) ? (
<element.type.WrappedComponent
intl={intl}
{...element.props}
/>
) : element;
return mount(
newElement,
// For useIntl, <Formatted.../>
{
wrappingComponent: IntlProvider,
wrappingComponentProps: {...intl},
// Override options
...mountOptions,
},
);
}

Просмотреть файл

@@ -0,0 +1,121 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/**
* @module lineBreakHelpers
* consolidate testing of similar behavior across components
*/
import {shallow} from 'enzyme';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import Constants from 'utils/constants';
export const INPUT = 'Hello world!';
export const OUTPUT_APPEND = 'Hello world!\n';
export const OUTPUT_REPLACE = 'Hello\norld!';
const REPLACE_START = 5;
const REPLACE_END = 7;
export const BASE_EVENT = {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
ctrlKey: true,
key: Constants.KeyCodes.ENTER[0],
keyCode: Constants.KeyCodes.ENTER[1],
};
/**
* @param {object} [e={}] keydown event object
* @return {object} keydown event object
*/
export function getAppendEvent(e = {}) {
return {
...BASE_EVENT,
...e,
target: {
selectionStart: INPUT.length,
selectionEnd: INPUT.length,
value: INPUT,
focus: jest.fn(),
setSelectionRange: jest.fn(),
},
};
}
/**
* @param {object} [e={}] keydown event object
* @return {object} keydown event object
*/
export function getReplaceEvent(e = {}) {
return {
...BASE_EVENT,
...e,
target: {
selectionStart: REPLACE_START,
selectionEnd: REPLACE_END,
value: INPUT,
focus: jest.fn(),
setSelectionRange: jest.fn(),
},
};
}
/**
* @param {object} [e={}] keydown event object
* @return {object} keydown event object
*/
export const getAltKeyEvent = (e = {}) => ({...BASE_EVENT, ...e, altKey: true});
export const getCtrlKeyEvent = (e = {}) => ({...BASE_EVENT, ...e, ctrlKey: true});
export const getMetaKeyEvent = (e = {}) => ({...BASE_EVENT, ...e, metaKey: true});
export const getShiftKeyEvent = (e = {}) => ({...BASE_EVENT, ...e, shiftKey: true});
/**
* helper to test line break on key down behavior common to many textarea inputs
* @param {function} generateInstance - single paramater "value" of the initial value
* @param {function} getValue - single parameter for the React Component instance
* @param {boolean} intlInhected -
* NOTE: runs Jest tests
*/
export function testComponentForLineBreak(generateInstance, getValue, intlInjected = true) {
const shallowRender = intlInjected ? shallowWithIntl : shallow;
test('component appends line break to input on shift + enter', () => {
const event = getAppendEvent(getShiftKeyEvent());
const instance = shallowRender(generateInstance(INPUT));
instance.simulate('keyDown', event);
setTimeout(() => {
expect(getValue(instance)).toBe(OUTPUT_APPEND);
expect(event.target.value).toBe(OUTPUT_APPEND);
}, 0);
});
test('component appends line break to input on alt + enter', () => {
const event = getAppendEvent(getAltKeyEvent());
const instance = shallowRender(generateInstance(INPUT));
instance.simulate('keyDown', event);
setTimeout(() => {
expect(getValue(instance)).toBe(OUTPUT_APPEND);
expect(event.target.value).toBe(OUTPUT_APPEND);
}, 0);
});
test('component inserts line break and replaces selection on shift + enter', () => {
const event = getReplaceEvent(getShiftKeyEvent());
const instance = shallowRender(generateInstance(INPUT));
instance.simulate('keyDown', event);
setTimeout(() => {
expect(getValue(instance)).toBe(OUTPUT_REPLACE);
expect(event.target.value).toBe(OUTPUT_REPLACE);
}, 0);
});
test('component inserts line break and replaces selection on alt + enter', () => {
const event = getReplaceEvent(getAltKeyEvent());
const instance = shallowRender(generateInstance(INPUT));
instance.simulate('keyDown', event);
setTimeout(() => {
expect(getValue(instance)).toBe(OUTPUT_REPLACE);
expect(event.target.value).toBe(OUTPUT_REPLACE);
}, 0);
});
}

Просмотреть файл

@@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Based on https://stackoverflow.com/a/41434763
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = value.toString();
}
removeItem(key) {
delete this.store[key];
}
}
global.localStorage = new LocalStorageMock();

Просмотреть файл

@@ -0,0 +1,283 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/**
* @module makrdownHotkeyHelpers
* consolidate testing of similar behavior across components
*/
import {shallow} from 'enzyme';
import Constants from 'utils/constants';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
/**
* @param {string} [input] text input
* @param {int} [start] selection's start index
* @param {int} [end] selection's end index
* @param {object} [keycode] Keycode constant associated with key press
* @return {object} keydown event object
*/
export function makeSelectionEvent(input, start, end) {
return {
preventDefault: jest.fn(),
target: {
selectionStart: start,
selectionEnd: end,
value: input,
},
};
}
function makeMarkdownHotkeyEvent(input, start, end, keycode, altKey = false) {
return {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
ctrlKey: true,
altKey,
key: keycode[0],
keyCode: keycode[1],
target: {
selectionStart: start,
selectionEnd: end,
value: input,
},
};
}
/**
* @param {string} [input] text input
* @param {int} [start] selection's start index
* @param {int} [end] selection's end index
* @return {object} keydown event object
*/
export function makeBoldHotkeyEvent(input, start, end) {
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.B);
}
/**
* @param {string} [input] text input
* @param {int} [start] selection's start index
* @param {int} [end] selection's end index
* @return {object} keydown event object
*/
export function makeItalicHotkeyEvent(input, start, end) {
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.I);
}
function makeLinkHotKeyEvent(input, start, end) {
return makeMarkdownHotkeyEvent(input, start, end, Constants.KeyCodes.K, true);
}
/**
* helper to test markdown hotkeys on key down behavior common to many textarea inputs
* @param {function} generateInstance - single paramater "value" of the initial value
* @param {function} initRefs - React Component instance and setSelectionRange function
* @param {function} getValue - single parameter for the React Component instance
* NOTE: runs Jest tests
*/
export function testComponentForMarkdownHotkeys(generateInstance, initRefs, find, getValue, intlInjected = true) {
const shallowRender = intlInjected ? shallowWithIntl : shallow;
test('component adds bold markdown', () => {
// "Fafda" is selected with ctrl + B hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeBoldHotkeyEvent(input, 7, 12);
const instance = shallowRender(generateInstance(input));
const setSelectionRange = jest.fn();
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi **Fafda** & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
});
test('component adds italic markdown', () => {
// "Fafda" is selected with ctrl + I hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeItalicHotkeyEvent(input, 7, 12);
const instance = shallowRender(generateInstance(input));
const setSelectionRange = jest.fn();
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi *Fafda* & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
});
test('component starts bold markdown', () => {
// Nothing is selected, caret is just before "Fafde" with ctrl + B
const input = 'Jalebi Fafda & Sambharo';
const e = makeBoldHotkeyEvent(input, 7, 7);
const instance = shallowRender(generateInstance(input));
const setSelectionRange = jest.fn();
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi ****Fafda & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
});
test('component starts italic markdown', () => {
// Nothing is selected, caret is just before "Fafde" with ctrl + B
const input = 'Jalebi Fafda & Sambharo';
const e = makeItalicHotkeyEvent(input, 7, 7);
const instance = shallowRender(generateInstance(input));
const setSelectionRange = jest.fn();
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi **Fafda & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
});
test('component adds link markdown when something is selected', () => {
// "Fafda" is selected with ctrl + alt + K hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeLinkHotKeyEvent(input, 7, 12);
const instance = shallowRender(generateInstance(input));
let selectionStart = -1;
let selectionEnd = -1;
const setSelectionRange = jest.fn((start, end) => {
selectionStart = start;
selectionEnd = end;
});
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi [Fafda](url) & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
expect(selectionStart).toBe(15);
expect(selectionEnd).toBe(18);
});
test('component adds link markdown when cursor is before a word', () => {
// Cursor is before "Fafda" with ctrl + alt + K hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeLinkHotKeyEvent(input, 7, 7);
const instance = shallowRender(generateInstance(input));
let selectionStart = -1;
let selectionEnd = -1;
const setSelectionRange = jest.fn((start, end) => {
selectionStart = start;
selectionEnd = end;
});
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi [Fafda](url) & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
expect(selectionStart).toBe(15);
expect(selectionEnd).toBe(18);
});
test('component adds link markdown when cursor is in a word', () => {
// Cursor is after "Fafda" with ctrl + alt + K hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeLinkHotKeyEvent(input, 10, 10);
const instance = shallowRender(generateInstance(input));
let selectionStart = -1;
let selectionEnd = -1;
const setSelectionRange = jest.fn((start, end) => {
selectionStart = start;
selectionEnd = end;
});
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi [Fafda](url) & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
expect(selectionStart).toBe(15);
expect(selectionEnd).toBe(18);
});
test('component adds link markdown when cursor is after a word', () => {
// Cursor is after "Fafda" with ctrl + alt + K hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeLinkHotKeyEvent(input, 12, 12);
const instance = shallowRender(generateInstance(input));
let selectionStart = -1;
let selectionEnd = -1;
const setSelectionRange = jest.fn((start, end) => {
selectionStart = start;
selectionEnd = end;
});
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi [Fafda](url) & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
expect(selectionStart).toBe(15);
expect(selectionEnd).toBe(18);
});
test('component adds link markdown when cursor is at the end of line', () => {
// Cursor is after "Sambharo" with ctrl + alt + K hotkey
const input = 'Jalebi Fafda & Sambharo';
const e = makeLinkHotKeyEvent(input, 23, 23);
const instance = shallowRender(generateInstance(input));
let selectionStart = -1;
let selectionEnd = -1;
const setSelectionRange = jest.fn((start, end) => {
selectionStart = start;
selectionEnd = end;
});
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi Fafda & Sambharo [](url)');
expect(setSelectionRange).toHaveBeenCalled();
expect(selectionStart).toBe(25);
expect(selectionEnd).toBe(25);
});
test('component removes link markdown', () => {
// "Fafda" is selected with ctrl + alt + K hotkey
const input = 'Jalebi [Fafda](url) & Sambharo';
const e = makeLinkHotKeyEvent(input, 8, 13);
const instance = shallowRender(generateInstance(input));
let selectionStart = -1;
let selectionEnd = -1;
const setSelectionRange = jest.fn((start, end) => {
selectionStart = start;
selectionEnd = end;
});
initRefs(instance, setSelectionRange);
find(instance).props().onKeyDown?.(e);
find(instance).props().handleKeyDown?.(e);
expect(getValue(instance)).toBe('Jalebi Fafda & Sambharo');
expect(setSelectionRange).toHaveBeenCalled();
expect(selectionStart).toBe(7);
expect(selectionEnd).toBe(12);
});
}

Просмотреть файл

@@ -0,0 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import MatchMedia from '@deanwhillier/jest-matchmedia-mock';
const matchMedia = new MatchMedia();
export default matchMedia;

Просмотреть файл

@@ -0,0 +1,49 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import CompassThemeProvider from 'components/compass_theme_provider/compass_theme_provider';
import {Theme} from 'mattermost-redux/selectors/entities/preferences';
import {mountWithIntl} from './intl-test-helper';
const stubValue = '#fff';
const DEFAULT_THEME: Theme = {
type: 'custom',
sidebarBg: stubValue,
sidebarText: stubValue,
sidebarUnreadText: stubValue,
sidebarTextHoverBg: stubValue,
sidebarTextActiveBorder: stubValue,
sidebarTextActiveColor: stubValue,
sidebarHeaderBg: stubValue,
sidebarTeamBarBg: stubValue,
sidebarHeaderTextColor: stubValue,
onlineIndicator: stubValue,
awayIndicator: stubValue,
dndIndicator: stubValue,
mentionBg: stubValue,
mentionBj: stubValue,
mentionColor: stubValue,
centerChannelBg: stubValue,
centerChannelColor: stubValue,
newMessageSeparator: stubValue,
linkColor: stubValue,
buttonBg: stubValue,
buttonColor: stubValue,
errorTextColor: stubValue,
mentionHighlightBg: stubValue,
mentionHighlightLink: stubValue,
codeTheme: stubValue,
};
export const mountWithThemedIntl = (children: React.ReactNode | React.ReactNodeArray, theme?: Theme) => {
return mountWithIntl(
<CompassThemeProvider
theme={theme || DEFAULT_THEME}
>
{children}
</CompassThemeProvider>,
);
};

Просмотреть файл

@@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/**
* @module userAgentMocks
* NOTE: all functions exported are side effect only
*/
let currentUA = '';
let initialUA = '';
window.navigator = window.navigator || {};
initialUA = window.navigator.userAgent;
Object.defineProperty(window.navigator, 'userAgent', {
get() {
return currentUA;
},
});
export function reset() {
set(initialUA);
}
export function set(ua) {
currentUA = ua;
}
export function mockSafari() {
set('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15');
}
export function mockChrome() {
set('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36');
}

1
webapp/channels/src/tests/i18n_mock.json Обычный файл
Просмотреть файл

@@ -0,0 +1 @@
{}

22
webapp/channels/src/tests/react-intl_mock.js поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
jest.mock('react-intl', function() {
const reactIntl = jest.requireActual('react-intl');
const enMessages = require('i18n/en.json');
const intl = reactIntl.createIntl({
locale: 'en',
messages: enMessages,
defaultLocale: 'en',
timeZone: 'Etc/UTC',
textComponent: 'span',
});
return {
...reactIntl,
useIntl() {
return intl;
},
};
});

Просмотреть файл

@@ -0,0 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
(global as any).historyMock = {
length: -1,
action: 'PUSH',
location: {
pathname: '/a-mocked-location',
search: '',
hash: '',
},
push: jest.fn(),
replace: jest.fn(),
go: jest.fn(),
goBack: jest.fn(),
goForward: jest.fn(),
block: jest.fn(),
listen: jest.fn(),
createHref: jest.fn(),
};
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
return {
...actual,
useHistory: () => (global as any).historyMock,
};
});
jest.mock('utils/browser_history', () => {
return {
getHistory: () => (global as any).historyMock,
};
});
export {};

12
webapp/channels/src/tests/react-tippy_mock.js поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
jest.mock('@tippyjs/react', () => ({
__esModule: true,
default: () => (
<div
id='tippyMock'
/>),
}));

Просмотреть файл

@@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {render} from '@testing-library/react';
import {Provider} from 'react-redux';
import {IntlProvider} from 'react-intl';
import mockStore from 'tests/test_store';
import {GlobalState} from '@mattermost/types/store';
import {DeepPartial} from '@mattermost/types/utilities';
export const renderWithIntl = (component: React.ReactNode | React.ReactNodeArray, locale = 'en') => {
return render(<IntlProvider locale={locale}>{component}</IntlProvider>);
};
export const renderWithIntlAndStore = (component: React.ReactNode | React.ReactNodeArray, initialState: DeepPartial<GlobalState>, locale = 'en') => {
const store = mockStore(initialState);
return render(
<IntlProvider locale={locale}>
<Provider store={store}>
{component}
</Provider>
</IntlProvider>,
);
};

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
jest.mock('redux-persist', () => {
const {combineReducers} = require('redux');
const real = jest.requireActual('redux-persist');
return {
...real,
createTransform: () => {
return {};
},
persistReducer: jest.fn().mockImplementation((config, reducers) => reducers),
persistCombineReducers: (persistConfig, reducers) => combineReducers(reducers),
persistStore: () => {
return {
pause: () => {},
purge: () => Promise.resolve(),
resume: () => {},
};
},
};
});

119
webapp/channels/src/tests/setup.js Обычный файл
Просмотреть файл

@@ -0,0 +1,119 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console */
import Adapter from 'enzyme-adapter-react-17-updated';
import {configure} from 'enzyme';
import '@testing-library/jest-dom';
import './redux-persist_mock';
import './react-intl_mock';
import './react-router-dom_mock';
import './react-tippy_mock';
global.performance = {};
require('isomorphic-fetch');
configure({adapter: new Adapter()});
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost:8065',
origin: 'http://localhost:8065',
port: '8065',
protocol: 'http:',
search: '',
},
});
const supportedCommands = ['copy'];
Object.defineProperty(document, 'queryCommandSupported', {
value: (cmd) => supportedCommands.includes(cmd),
});
Object.defineProperty(document, 'execCommand', {
value: (cmd) => supportedCommands.includes(cmd),
});
document.documentElement.style.fontSize = '12px';
// https://mui.com/material-ui/guides/styled-engine/
jest.mock('@mui/styled-engine', () => {
const styledEngineSc = require('@mui/styled-engine-sc');
return styledEngineSc;
});
// isDependencyWarning returns true when the given console.warn message is coming from a dependency using deprecated
// React lifecycle methods.
function isDependencyWarning(params) {
function paramsHasComponent(name) {
return params.some((param) => param.includes(name));
}
return params[0].includes('Please update the following components:') && (
// React Bootstrap
paramsHasComponent('Modal') ||
paramsHasComponent('Portal') ||
paramsHasComponent('Overlay') ||
paramsHasComponent('Position') ||
// React-Select
paramsHasComponent('Select')
);
}
let warns;
let errors;
beforeAll(() => {
console.originalWarn = console.warn;
console.warn = jest.fn((...params) => {
// Ignore any deprecation warnings coming from dependencies
if (isDependencyWarning(params)) {
return;
}
console.originalWarn(...params);
warns.push(params);
});
console.originalError = console.error;
console.error = jest.fn((...params) => {
console.originalError(...params);
errors.push(params);
});
});
beforeEach(() => {
warns = [];
errors = [];
});
afterEach(() => {
if (warns.length > 0 || errors.length > 0) {
const message = 'Unexpected console logs' + warns + errors;
throw new Error(message);
}
});
expect.extend({
arrayContainingExactly(received, actual) {
const pass = received.sort().join(',') === actual.sort().join(',');
if (pass) {
return {
message: () =>
`expected ${received} to not contain the exact same values as ${actual}`,
pass: true,
};
}
return {
message: () =>
`expected ${received} to not contain the exact same values as ${actual}`,
pass: false,
};
},
});

40
webapp/channels/src/tests/test_store.tsx Обычный файл
Просмотреть файл

@@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Provider} from 'react-redux';
import {AnyAction} from 'redux';
import thunk, {ThunkDispatch} from 'redux-thunk';
import configureStore from 'redux-mock-store';
import {IntlProvider} from 'react-intl';
import {GlobalState} from 'types/store';
import {defaultIntl} from './helpers/intl-test-helper';
export default function testConfigureStore(initialState = {}) {
return configureStore<GlobalState, ThunkDispatch<GlobalState, Record<string, never>, AnyAction>>([thunk])(initialState as GlobalState);
}
export function mockStore(initialState = {}, intl = defaultIntl) {
const store = testConfigureStore(initialState);
return {
store,
mountOptions: intl ? {
wrappingComponent: ({children, ...props}: {children: React.ReactNode} & React.ComponentProps<typeof IntlProvider>) => (
<IntlProvider {...props}>
<Provider store={store}>
{children}
</Provider>
</IntlProvider>
),
wrappingComponentProps: {
...intl,
},
} : {
wrappingComponent: Provider,
wrappingComponentProps: {store},
},
};
}