Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
9
webapp/channels/src/tests/constants/cloud.ts
Обычный файл
9
webapp/channels/src/tests/constants/cloud.ts
Обычный файл
@@ -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: {},
|
||||
});
|
||||
17
webapp/channels/src/tests/constants/teams.ts
Обычный файл
17
webapp/channels/src/tests/constants/teams.ts
Обычный файл
@@ -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,
|
||||
});
|
||||
45
webapp/channels/src/tests/constants/users.ts
Обычный файл
45
webapp/channels/src/tests/constants/users.ts
Обычный файл
@@ -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
Обычный файл
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;
|
||||
};
|
||||
};
|
||||
113
webapp/channels/src/tests/helpers/intl-test-helper.tsx
Обычный файл
113
webapp/channels/src/tests/helpers/intl-test-helper.tsx
Обычный файл
@@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
121
webapp/channels/src/tests/helpers/line_break_helpers.js
Обычный файл
121
webapp/channels/src/tests/helpers/line_break_helpers.js
Обычный файл
@@ -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);
|
||||
});
|
||||
}
|
||||
27
webapp/channels/src/tests/helpers/localstorage.jsx
Обычный файл
27
webapp/channels/src/tests/helpers/localstorage.jsx
Обычный файл
@@ -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();
|
||||
283
webapp/channels/src/tests/helpers/markdown_hotkey_helpers.js
Обычный файл
283
webapp/channels/src/tests/helpers/markdown_hotkey_helpers.js
Обычный файл
@@ -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);
|
||||
});
|
||||
}
|
||||
7
webapp/channels/src/tests/helpers/match_media.mock.ts
Обычный файл
7
webapp/channels/src/tests/helpers/match_media.mock.ts
Обычный файл
@@ -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;
|
||||
49
webapp/channels/src/tests/helpers/themed-intl-test-helper.tsx
Обычный файл
49
webapp/channels/src/tests/helpers/themed-intl-test-helper.tsx
Обычный файл
@@ -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>,
|
||||
);
|
||||
};
|
||||
31
webapp/channels/src/tests/helpers/user_agent_mocks.js
Обычный файл
31
webapp/channels/src/tests/helpers/user_agent_mocks.js
Обычный файл
@@ -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
Обычный файл
1
webapp/channels/src/tests/i18n_mock.json
Обычный файл
@@ -0,0 +1 @@
|
||||
{}
|
||||
22
webapp/channels/src/tests/react-intl_mock.js
поставляемый
Обычный файл
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;
|
||||
},
|
||||
};
|
||||
});
|
||||
37
webapp/channels/src/tests/react-router-dom_mock.ts
Обычный файл
37
webapp/channels/src/tests/react-router-dom_mock.ts
Обычный файл
@@ -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
поставляемый
Обычный файл
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'
|
||||
/>),
|
||||
}));
|
||||
27
webapp/channels/src/tests/react_testing_utils.tsx
Обычный файл
27
webapp/channels/src/tests/react_testing_utils.tsx
Обычный файл
@@ -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>,
|
||||
);
|
||||
};
|
||||
24
webapp/channels/src/tests/redux-persist_mock.js
Обычный файл
24
webapp/channels/src/tests/redux-persist_mock.js
Обычный файл
@@ -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
Обычный файл
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
Обычный файл
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},
|
||||
},
|
||||
};
|
||||
}
|
||||
Ссылка в новой задаче
Block a user