[MM-47181] Migrate "components/suggestion/at_mention_provider" and associated tests to Typescript (#25342)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8f0abc1316
Коммит
53298a2d60
@@ -8,7 +8,7 @@ exports[`components/TextBox should match snapshot with additional, optional prop
|
|||||||
className="form-control custom-textarea textbox-preview-area"
|
className="form-control custom-textarea textbox-preview-area"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<Connect(PostMarkdown)
|
<Connect(PostMarkdown)
|
||||||
@@ -52,10 +52,10 @@ exports[`components/TextBox should match snapshot with additional, optional prop
|
|||||||
listPosition="top"
|
listPosition="top"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onComposition={[Function]}
|
onComposition={[MockFunction]}
|
||||||
onHeightChange={[Function]}
|
onHeightChange={[MockFunction]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
onMouseUp={[Function]}
|
onMouseUp={[Function]}
|
||||||
openWhenEmpty={true}
|
openWhenEmpty={true}
|
||||||
@@ -66,10 +66,34 @@ exports[`components/TextBox should match snapshot with additional, optional prop
|
|||||||
"addLastViewAtToProfiles": [Function],
|
"addLastViewAtToProfiles": [Function],
|
||||||
"autocompleteGroups": Array [
|
"autocompleteGroups": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid1",
|
"id": "gid1",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid2",
|
"id": "gid2",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"autocompleteUsersInChannel": [Function],
|
"autocompleteUsersInChannel": [Function],
|
||||||
@@ -136,7 +160,7 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
|
|||||||
className="form-control custom-textarea textbox-preview-area"
|
className="form-control custom-textarea textbox-preview-area"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<Connect(PostMarkdown)
|
<Connect(PostMarkdown)
|
||||||
@@ -179,7 +203,7 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
|
|||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
onMouseUp={[Function]}
|
onMouseUp={[Function]}
|
||||||
placeholder="placeholder text"
|
placeholder="placeholder text"
|
||||||
@@ -189,10 +213,34 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
|
|||||||
"addLastViewAtToProfiles": [Function],
|
"addLastViewAtToProfiles": [Function],
|
||||||
"autocompleteGroups": Array [
|
"autocompleteGroups": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid1",
|
"id": "gid1",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid2",
|
"id": "gid2",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"autocompleteUsersInChannel": [Function],
|
"autocompleteUsersInChannel": [Function],
|
||||||
@@ -259,7 +307,7 @@ exports[`components/TextBox should throw error when new property is too long 1`]
|
|||||||
className="form-control custom-textarea textbox-preview-area"
|
className="form-control custom-textarea textbox-preview-area"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<Connect(PostMarkdown)
|
<Connect(PostMarkdown)
|
||||||
@@ -302,7 +350,7 @@ exports[`components/TextBox should throw error when new property is too long 1`]
|
|||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
onMouseUp={[Function]}
|
onMouseUp={[Function]}
|
||||||
placeholder="placeholder text"
|
placeholder="placeholder text"
|
||||||
@@ -312,10 +360,34 @@ exports[`components/TextBox should throw error when new property is too long 1`]
|
|||||||
"addLastViewAtToProfiles": [Function],
|
"addLastViewAtToProfiles": [Function],
|
||||||
"autocompleteGroups": Array [
|
"autocompleteGroups": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid1",
|
"id": "gid1",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid2",
|
"id": "gid2",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"autocompleteUsersInChannel": [Function],
|
"autocompleteUsersInChannel": [Function],
|
||||||
@@ -382,7 +454,7 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
|
|||||||
className="form-control custom-textarea textbox-preview-area"
|
className="form-control custom-textarea textbox-preview-area"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<Connect(PostMarkdown)
|
<Connect(PostMarkdown)
|
||||||
@@ -425,7 +497,7 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
|
|||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyPress={[Function]}
|
onKeyPress={[MockFunction]}
|
||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
onMouseUp={[Function]}
|
onMouseUp={[Function]}
|
||||||
placeholder="placeholder text"
|
placeholder="placeholder text"
|
||||||
@@ -435,10 +507,34 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
|
|||||||
"addLastViewAtToProfiles": [Function],
|
"addLastViewAtToProfiles": [Function],
|
||||||
"autocompleteGroups": Array [
|
"autocompleteGroups": Array [
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid1",
|
"id": "gid1",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"allow_reference": true,
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 0,
|
||||||
|
"description": "",
|
||||||
|
"display_name": "group_display_name",
|
||||||
|
"has_syncables": false,
|
||||||
"id": "gid2",
|
"id": "gid2",
|
||||||
|
"member_count": 0,
|
||||||
|
"name": "group_name",
|
||||||
|
"remote_id": "",
|
||||||
|
"scheme_admin": false,
|
||||||
|
"source": "",
|
||||||
|
"update_at": 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"autocompleteUsersInChannel": [Function],
|
"autocompleteUsersInChannel": [Function],
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import AtMentionProvider from 'components/suggestion/at_mention_provider/at_mention_provider.jsx';
|
import AtMentionProvider, {type Props} from 'components/suggestion/at_mention_provider/at_mention_provider';
|
||||||
import AtMentionSuggestion from 'components/suggestion/at_mention_provider/at_mention_suggestion';
|
import AtMentionSuggestion from 'components/suggestion/at_mention_provider/at_mention_suggestion';
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
import {Constants} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe('components/suggestion/at_mention_provider/AtMentionProvider', () => {
|
describe('components/suggestion/at_mention_provider/AtMentionProvider', () => {
|
||||||
const userid10 = {id: 'userid10', username: 'nicknamer', first_name: '', last_name: '', nickname: 'Z'};
|
const userid10 = TestHelper.getUserMock({id: 'userid10', username: 'nicknamer', first_name: '', last_name: '', nickname: 'Z'});
|
||||||
const userid3 = {id: 'userid3', username: 'other', first_name: 'X', last_name: 'Y', nickname: 'Z'};
|
const userid3 = TestHelper.getUserMock({id: 'userid3', username: 'other', first_name: 'X', last_name: 'Y', nickname: 'Z'});
|
||||||
const userid1 = {id: 'userid1', username: 'user', first_name: 'a', last_name: 'b', nickname: 'c', isCurrentUser: true};
|
const userid1 = {...TestHelper.getUserMock({id: 'userid1', username: 'user', first_name: 'a', last_name: 'b', nickname: 'c'}), isCurrentUser: true};
|
||||||
const userid2 = {id: 'userid2', username: 'user2', first_name: 'd', last_name: 'e', nickname: 'f'};
|
const userid2 = TestHelper.getUserMock({id: 'userid2', username: 'user2', first_name: 'd', last_name: 'e', nickname: 'f'});
|
||||||
const userid4 = {id: 'userid4', username: 'user4', first_name: 'X', last_name: 'Y', nickname: 'Z'};
|
const userid4 = TestHelper.getUserMock({id: 'userid4', username: 'user4', first_name: 'X', last_name: 'Y', nickname: 'Z'});
|
||||||
const userid5 = {id: 'userid5', username: 'user5', first_name: 'out', last_name: 'out', nickname: 'out'};
|
const userid5 = TestHelper.getUserMock({id: 'userid5', username: 'user5', first_name: 'out', last_name: 'out', nickname: 'out'});
|
||||||
const userid6 = {id: 'userid6', username: 'user6.six-split', first_name: 'out Junior', last_name: 'out', nickname: 'out'};
|
const userid6 = TestHelper.getUserMock({id: 'userid6', username: 'user6.six-split', first_name: 'out Junior', last_name: 'out', nickname: 'out'});
|
||||||
const userid7 = {id: 'userid7', username: 'xuser7', first_name: '', last_name: '', nickname: 'x'};
|
const userid7 = TestHelper.getUserMock({id: 'userid7', username: 'xuser7', first_name: '', last_name: '', nickname: 'x'});
|
||||||
const userid8 = {id: 'userid8', username: 'xuser8', first_name: 'Robert', last_name: 'Ward', nickname: 'nickname'};
|
const userid8 = TestHelper.getUserMock({id: 'userid8', username: 'xuser8', first_name: 'Robert', last_name: 'Ward', nickname: 'nickname'});
|
||||||
|
|
||||||
const groupid1 = {id: 'groupid1', name: 'board', display_name: 'board'};
|
const groupid1 = TestHelper.getGroupMock({id: 'groupid1', name: 'board', display_name: 'board'});
|
||||||
const groupid2 = {id: 'groupid2', name: 'developers', display_name: 'developers'};
|
const groupid2 = TestHelper.getGroupMock({id: 'groupid2', name: 'developers', display_name: 'developers'});
|
||||||
const groupid3 = {id: 'groupid3', name: 'software-engineers', display_name: 'software engineers'};
|
const groupid3 = TestHelper.getGroupMock({id: 'groupid3', name: 'software-engineers', display_name: 'software engineers'});
|
||||||
|
|
||||||
const baseParams = {
|
const baseParams: Props = {
|
||||||
currentUserId: 'userid1',
|
currentUserId: 'userid1',
|
||||||
channelId: 'channelid1',
|
channelId: 'channelid1',
|
||||||
autocompleteUsersInChannel: jest.fn().mockResolvedValue(false),
|
autocompleteUsersInChannel: jest.fn().mockResolvedValue(false),
|
||||||
autocompleteGroups: [groupid1, groupid2, groupid3],
|
autocompleteGroups: [groupid1, groupid2, groupid3],
|
||||||
useChannelMentions: true,
|
useChannelMentions: true,
|
||||||
searchAssociatedGroupsForReference: jest.fn().mockResolvedValue(false),
|
searchAssociatedGroupsForReference: jest.fn().mockResolvedValue(false),
|
||||||
|
priorityProfiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should ignore pretexts that are not at-mentions', () => {
|
it('should ignore pretexts that are not at-mentions', () => {
|
||||||
@@ -169,8 +171,8 @@ describe('components/suggestion/at_mention_provider/AtMentionProvider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have priorityProfiles at the top', async () => {
|
it('should have priorityProfiles at the top', async () => {
|
||||||
const userid11 = {id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'};
|
const userid11 = TestHelper.getUserMock({id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'});
|
||||||
const userid12 = {id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'};
|
const userid12 = TestHelper.getUserMock({id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'});
|
||||||
|
|
||||||
const pretext = '@';
|
const pretext = '@';
|
||||||
const matchedPretext = '@';
|
const matchedPretext = '@';
|
||||||
@@ -315,8 +317,8 @@ describe('components/suggestion/at_mention_provider/AtMentionProvider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should remove duplicates from results', async () => {
|
it('should remove duplicates from results', async () => {
|
||||||
const userid11 = {id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'};
|
const userid11 = TestHelper.getUserMock({id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'});
|
||||||
const userid12 = {id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'};
|
const userid12 = TestHelper.getUserMock({id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'});
|
||||||
|
|
||||||
const pretext = '@';
|
const pretext = '@';
|
||||||
const matchedPretext = '@';
|
const matchedPretext = '@';
|
||||||
@@ -461,8 +463,8 @@ describe('components/suggestion/at_mention_provider/AtMentionProvider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should sort results based on last_viewed_at', async () => {
|
it('should sort results based on last_viewed_at', async () => {
|
||||||
const userid11 = {id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'};
|
const userid11 = TestHelper.getUserMock({id: 'userid11', username: 'user11', first_name: 'firstname11', last_name: 'lastname11', nickname: 'nickname11'});
|
||||||
const userid12 = {id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'};
|
const userid12 = TestHelper.getUserMock({id: 'userid12', username: 'user12', first_name: 'firstname12', last_name: 'lastname12', nickname: 'nickname12'});
|
||||||
|
|
||||||
const pretext = '@';
|
const pretext = '@';
|
||||||
const matchedPretext = '@';
|
const matchedPretext = '@';
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {Group} from '@mattermost/types/groups';
|
||||||
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
|
import type {Filters} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {makeGetProfilesInChannel} from 'mattermost-redux/selectors/entities/users';
|
import {makeGetProfilesInChannel} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {makeAddLastViewAtToProfiles} from 'mattermost-redux/selectors/entities/utils';
|
import {makeAddLastViewAtToProfiles} from 'mattermost-redux/selectors/entities/utils';
|
||||||
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
import {getSuggestionsSplitBy, getSuggestionsSplitByMultiple} from 'mattermost-redux/utils/user_utils';
|
import {getSuggestionsSplitBy, getSuggestionsSplitByMultiple} from 'mattermost-redux/utils/user_utils';
|
||||||
|
|
||||||
import store from 'stores/redux_store';
|
import store from 'stores/redux_store';
|
||||||
|
|
||||||
import {Constants} from 'utils/constants';
|
import {Constants} from 'utils/constants';
|
||||||
|
|
||||||
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import AtMentionSuggestion from './at_mention_suggestion';
|
import AtMentionSuggestion from './at_mention_suggestion';
|
||||||
|
|
||||||
import Provider from '../provider';
|
import Provider from '../provider';
|
||||||
@@ -16,14 +23,65 @@ import Provider from '../provider';
|
|||||||
const profilesInChannelOptions = {active: true};
|
const profilesInChannelOptions = {active: true};
|
||||||
const regexForAtMention = /(?:^|\W)@([\p{L}\d\-_. ]*)$/iu;
|
const regexForAtMention = /(?:^|\W)@([\p{L}\d\-_. ]*)$/iu;
|
||||||
|
|
||||||
|
type UserProfileWithLastViewAt = UserProfile & {last_viewed_at?: number};
|
||||||
|
|
||||||
|
type CreatedProfile = UserProfile & {
|
||||||
|
type: string;
|
||||||
|
isCurrentUser?: boolean;
|
||||||
|
last_viewed_at?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CreatedGroup = Group & {type: string};
|
||||||
|
|
||||||
|
type Results = {
|
||||||
|
matchedPretext: string;
|
||||||
|
terms: string[];
|
||||||
|
items: any;
|
||||||
|
component: React.ElementType;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResultsCallback = (results: Results) => void;
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
currentUserId: string;
|
||||||
|
channelId: string;
|
||||||
|
autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
|
||||||
|
useChannelMentions: boolean;
|
||||||
|
autocompleteGroups: Group[] | null;
|
||||||
|
searchAssociatedGroupsForReference: (prefix: string) => Promise<{data: any}>;
|
||||||
|
priorityProfiles: UserProfile[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// The AtMentionProvider provides matches for at mentions, including @here, @channel, @all,
|
// The AtMentionProvider provides matches for at mentions, including @here, @channel, @all,
|
||||||
// users in the channel and users not in the channel. It mixes together results from the local
|
// users in the channel and users not in the channel. It mixes together results from the local
|
||||||
// store with results fetched from the server.
|
// store with results fetched from the server.
|
||||||
export default class AtMentionProvider extends Provider {
|
export default class AtMentionProvider extends Provider {
|
||||||
constructor(props) {
|
public currentUserId: string;
|
||||||
|
public channelId: string;
|
||||||
|
public autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
|
||||||
|
public useChannelMentions: boolean;
|
||||||
|
public autocompleteGroups: Group[] | null;
|
||||||
|
public searchAssociatedGroupsForReference: (prefix: string) => Promise<{data: any}>;
|
||||||
|
public priorityProfiles: UserProfile[] | undefined;
|
||||||
|
|
||||||
|
public data: any;
|
||||||
|
public lastCompletedWord: string;
|
||||||
|
public lastPrefixWithNoResults: string;
|
||||||
|
public getProfilesInChannel: (state: GlobalState, channelId: string, filters?: Filters | undefined) => UserProfile[];
|
||||||
|
public addLastViewAtToProfiles: (state: GlobalState, profiles: UserProfile[]) => UserProfileWithLastViewAt[];
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.setProps(props);
|
const {currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles} = props;
|
||||||
|
|
||||||
|
this.currentUserId = currentUserId;
|
||||||
|
this.channelId = channelId;
|
||||||
|
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
||||||
|
this.useChannelMentions = useChannelMentions;
|
||||||
|
this.autocompleteGroups = autocompleteGroups;
|
||||||
|
this.searchAssociatedGroupsForReference = searchAssociatedGroupsForReference;
|
||||||
|
this.priorityProfiles = priorityProfiles;
|
||||||
|
|
||||||
this.data = null;
|
this.data = null;
|
||||||
this.lastCompletedWord = '';
|
this.lastCompletedWord = '';
|
||||||
@@ -33,9 +91,7 @@ export default class AtMentionProvider extends Provider {
|
|||||||
this.addLastViewAtToProfiles = makeAddLastViewAtToProfiles();
|
this.addLastViewAtToProfiles = makeAddLastViewAtToProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
// setProps gives the provider additional context for matching pretexts. Ideally this would
|
setProps({currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles}: Props) {
|
||||||
// just be something akin to a connected component with access to the store itself.
|
|
||||||
setProps({currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles}) {
|
|
||||||
this.currentUserId = currentUserId;
|
this.currentUserId = currentUserId;
|
||||||
this.channelId = channelId;
|
this.channelId = channelId;
|
||||||
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
||||||
@@ -61,8 +117,8 @@ export default class AtMentionProvider extends Provider {
|
|||||||
|
|
||||||
// retrieves the parts of the profile that should be checked
|
// retrieves the parts of the profile that should be checked
|
||||||
// against the term
|
// against the term
|
||||||
getProfileSuggestions(profile) {
|
getProfileSuggestions(profile: UserProfile) {
|
||||||
const profileSuggestions = [];
|
const profileSuggestions: string[] = [];
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return profileSuggestions;
|
return profileSuggestions;
|
||||||
}
|
}
|
||||||
@@ -82,8 +138,8 @@ export default class AtMentionProvider extends Provider {
|
|||||||
|
|
||||||
// retrieves the parts of the group mention that should be checked
|
// retrieves the parts of the group mention that should be checked
|
||||||
// against the term
|
// against the term
|
||||||
getGroupSuggestions(group) {
|
getGroupSuggestions(group: Group) {
|
||||||
const groupSuggestions = [];
|
const groupSuggestions: string[] = [];
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return groupSuggestions;
|
return groupSuggestions;
|
||||||
}
|
}
|
||||||
@@ -101,12 +157,12 @@ export default class AtMentionProvider extends Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// normalizeString performs a unicode normalization to a string
|
// normalizeString performs a unicode normalization to a string
|
||||||
normalizeString(name) {
|
normalizeString(name: string) {
|
||||||
return name.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
return name.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterProfile constrains profiles to those matching the latest prefix.
|
// filterProfile constrains profiles to those matching the latest prefix.
|
||||||
filterProfile(profile) {
|
filterProfile(profile: UserProfile | UserProfileWithLastViewAt) {
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -119,7 +175,7 @@ export default class AtMentionProvider extends Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filterGroup constrains group mentions to those matching the latest prefix.
|
// filterGroup constrains group mentions to those matching the latest prefix.
|
||||||
filterGroup(group) {
|
filterGroup(group: Group) {
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -182,8 +238,8 @@ export default class AtMentionProvider extends Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const remoteMembers = (this.data.users || []).
|
const remoteMembers = (this.data.users || []).
|
||||||
filter((profile) => this.filterProfile(profile)).
|
filter((profile: UserProfileWithLastViewAt) => this.filterProfile(profile)).
|
||||||
map((profile) => this.createFromProfile(profile, Constants.MENTION_MEMBERS));
|
map((profile: UserProfileWithLastViewAt) => this.createFromProfile(profile, Constants.MENTION_MEMBERS));
|
||||||
|
|
||||||
return remoteMembers;
|
return remoteMembers;
|
||||||
}
|
}
|
||||||
@@ -193,30 +249,30 @@ export default class AtMentionProvider extends Provider {
|
|||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const remoteGroups = (this.data.groups || []).
|
const remoteGroups = ((this.data.groups || []) as Group[]).
|
||||||
filter((group) => this.filterGroup(group)).
|
filter((group: Group) => this.filterGroup(group)).
|
||||||
map((group) => this.createFromGroup(group, Constants.MENTION_GROUPS));
|
map((group: Group) => this.createFromGroup(group, Constants.MENTION_GROUPS));
|
||||||
|
|
||||||
return remoteGroups;
|
return remoteGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remoteNonMembers matches users listed as not in the channel by the server.
|
// remoteNonMembers matches users listed as not in the channel by the server.
|
||||||
// listed in the channel from local results.
|
// listed in the channel from local results.
|
||||||
remoteNonMembers() {
|
remoteNonMembers(): CreatedProfile[] {
|
||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this.data.out_of_channel || []).
|
return (this.data.out_of_channel || []).
|
||||||
filter((profile) => this.filterProfile(profile)).
|
filter((profile: UserProfileWithLastViewAt) => this.filterProfile(profile)).
|
||||||
map((profile) => ({
|
map((profile: UserProfileWithLastViewAt) => ({
|
||||||
type: Constants.MENTION_NONMEMBERS,
|
type: Constants.MENTION_NONMEMBERS,
|
||||||
...profile,
|
...profile,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
items() {
|
items() {
|
||||||
const priorityProfilesIds = {};
|
const priorityProfilesIds: Record<string, boolean> = {};
|
||||||
const priorityProfiles = this.filterPriorityProfiles();
|
const priorityProfiles = this.filterPriorityProfiles();
|
||||||
|
|
||||||
priorityProfiles.forEach((member) => {
|
priorityProfiles.forEach((member) => {
|
||||||
@@ -226,16 +282,16 @@ export default class AtMentionProvider extends Provider {
|
|||||||
const specialMentions = this.specialMentions();
|
const specialMentions = this.specialMentions();
|
||||||
const localMembers = this.localMembers().filter((member) => !priorityProfilesIds[member.id]);
|
const localMembers = this.localMembers().filter((member) => !priorityProfilesIds[member.id]);
|
||||||
|
|
||||||
const localUserIds = {};
|
const localUserIds: Record<string, boolean> = {};
|
||||||
|
|
||||||
localMembers.forEach((member) => {
|
localMembers.forEach((member) => {
|
||||||
localUserIds[member.id] = true;
|
localUserIds[member.id] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const remoteMembers = this.remoteMembers().filter((member) => !localUserIds[member.id] && !priorityProfilesIds[member.id]);
|
const remoteMembers = this.remoteMembers().filter((member: CreatedProfile) => !localUserIds[member.id] && !priorityProfilesIds[member.id]);
|
||||||
|
|
||||||
// comparator which prioritises users with usernames starting with search term
|
// comparator which prioritises users with usernames starting with search term
|
||||||
const orderUsers = (a, b) => {
|
const orderUsers = (a: CreatedProfile, b: CreatedProfile) => {
|
||||||
const aStartsWith = a.username.startsWith(this.latestPrefix);
|
const aStartsWith = a.username.startsWith(this.latestPrefix);
|
||||||
const bStartsWith = b.username.startsWith(this.latestPrefix);
|
const bStartsWith = b.username.startsWith(this.latestPrefix);
|
||||||
|
|
||||||
@@ -263,7 +319,7 @@ export default class AtMentionProvider extends Provider {
|
|||||||
// handle groups
|
// handle groups
|
||||||
const localGroups = this.localGroups();
|
const localGroups = this.localGroups();
|
||||||
|
|
||||||
const localGroupIds = {};
|
const localGroupIds: Record<string, boolean> = {};
|
||||||
localGroups.forEach((group) => {
|
localGroups.forEach((group) => {
|
||||||
localGroupIds[group.id] = true;
|
localGroupIds[group.id] = true;
|
||||||
});
|
});
|
||||||
@@ -271,7 +327,7 @@ export default class AtMentionProvider extends Provider {
|
|||||||
const remoteGroups = this.remoteGroups().filter((group) => !localGroupIds[group.id]);
|
const remoteGroups = this.remoteGroups().filter((group) => !localGroupIds[group.id]);
|
||||||
|
|
||||||
// comparator which prioritises users with usernames starting with search term
|
// comparator which prioritises users with usernames starting with search term
|
||||||
const orderGroups = (a, b) => {
|
const orderGroups = (a: CreatedGroup, b: CreatedGroup) => {
|
||||||
const aStartsWith = a.name.startsWith(this.latestPrefix);
|
const aStartsWith = a.name.startsWith(this.latestPrefix);
|
||||||
const bStartsWith = b.name.startsWith(this.latestPrefix);
|
const bStartsWith = b.name.startsWith(this.latestPrefix);
|
||||||
|
|
||||||
@@ -294,11 +350,11 @@ export default class AtMentionProvider extends Provider {
|
|||||||
filter((member) => !localUserIds[member.id]).
|
filter((member) => !localUserIds[member.id]).
|
||||||
sort(orderUsers);
|
sort(orderUsers);
|
||||||
|
|
||||||
return priorityProfiles.concat(localAndRemoteMembers).concat(localAndRemoteGroups).concat(specialMentions).concat(remoteNonMembers);
|
return [...priorityProfiles, ...localAndRemoteMembers, ...localAndRemoteGroups, ...specialMentions, ...remoteNonMembers];
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateMatches invokes the resultCallback with the metadata for rendering at mentions
|
// updateMatches invokes the resultCallback with the metadata for rendering at mentions
|
||||||
updateMatches(resultCallback, items) {
|
updateMatches(resultCallback: ResultsCallback, items: any[]) {
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
this.lastPrefixWithNoResults = this.latestPrefix;
|
this.lastPrefixWithNoResults = this.latestPrefix;
|
||||||
} else if (this.lastPrefixWithNoResults === this.latestPrefix) {
|
} else if (this.lastPrefixWithNoResults === this.latestPrefix) {
|
||||||
@@ -321,7 +377,7 @@ export default class AtMentionProvider extends Provider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePretextChanged(pretext, resultCallback) {
|
handlePretextChanged(pretext: string, resultCallback: ResultsCallback) {
|
||||||
const captured = regexForAtMention.exec(pretext.toLowerCase());
|
const captured = regexForAtMention.exec(pretext.toLowerCase());
|
||||||
if (!captured) {
|
if (!captured) {
|
||||||
return false;
|
return false;
|
||||||
@@ -342,15 +398,15 @@ export default class AtMentionProvider extends Provider {
|
|||||||
this.updateMatches(resultCallback, this.items());
|
this.updateMatches(resultCallback, this.items());
|
||||||
|
|
||||||
// If we haven't gotten server-side results in 500 ms, add the loading indicator.
|
// If we haven't gotten server-side results in 500 ms, add the loading indicator.
|
||||||
let showLoadingIndicator = setTimeout(() => {
|
let showLoadingIndicator: NodeJS.Timeout | null = setTimeout(() => {
|
||||||
if (this.shouldCancelDispatch(prefix)) {
|
if (this.shouldCancelDispatch(prefix)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateMatches(resultCallback, this.items().concat([{
|
this.updateMatches(resultCallback, [...this.items(), ...[{
|
||||||
type: Constants.MENTION_MORE_MEMBERS,
|
type: Constants.MENTION_MORE_MEMBERS,
|
||||||
loading: true,
|
loading: true,
|
||||||
}]));
|
}]]);
|
||||||
|
|
||||||
showLoadingIndicator = null;
|
showLoadingIndicator = null;
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -375,12 +431,12 @@ export default class AtMentionProvider extends Provider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCompleteWord(term) {
|
handleCompleteWord(term: string) {
|
||||||
this.lastCompletedWord = term;
|
this.lastCompletedWord = term;
|
||||||
this.lastPrefixWithNoResults = '';
|
this.lastPrefixWithNoResults = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
createFromProfile(profile, type) {
|
createFromProfile(profile: UserProfile | UserProfileWithLastViewAt, type: string): CreatedProfile {
|
||||||
if (profile.id === this.currentUserId) {
|
if (profile.id === this.currentUserId) {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
@@ -395,7 +451,7 @@ export default class AtMentionProvider extends Provider {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
createFromGroup(group, type) {
|
createFromGroup(group: Group, type: string): CreatedGroup {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
...group,
|
...group,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import AtMentionProvider from './at_mention_provider.jsx';
|
import AtMentionProvider from './at_mention_provider';
|
||||||
|
|
||||||
export default AtMentionProvider;
|
export default AtMentionProvider;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {shallow, mount} from 'enzyme';
|
import {shallow, mount} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import AtMentionProvider from 'components/suggestion/at_mention_provider/at_mention_provider.jsx';
|
import AtMentionProvider from 'components/suggestion/at_mention_provider/at_mention_provider';
|
||||||
import CommandProvider from 'components/suggestion/command_provider/command_provider';
|
import CommandProvider from 'components/suggestion/command_provider/command_provider';
|
||||||
import SuggestionBox from 'components/suggestion/suggestion_box/suggestion_box';
|
import SuggestionBox from 'components/suggestion/suggestion_box/suggestion_box';
|
||||||
import SuggestionList from 'components/suggestion/suggestion_list';
|
import SuggestionList from 'components/suggestion/suggestion_list';
|
||||||
|
|||||||
@@ -4,22 +4,20 @@
|
|||||||
import {shallow} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Textbox from 'components/textbox/textbox';
|
import Textbox, {type Props} from 'components/textbox/textbox';
|
||||||
|
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
describe('components/TextBox', () => {
|
describe('components/TextBox', () => {
|
||||||
const baseProps = {
|
const baseProps: Props = {
|
||||||
channelId: 'channelId',
|
channelId: 'channelId',
|
||||||
rootId: 'rootId',
|
rootId: 'rootId',
|
||||||
currentUserId: 'currentUserId',
|
currentUserId: 'currentUserId',
|
||||||
currentTeamId: 'currentTeamId',
|
currentTeamId: 'currentTeamId',
|
||||||
profilesInChannel: [
|
|
||||||
{id: 'id1'},
|
|
||||||
{id: 'id2'},
|
|
||||||
],
|
|
||||||
delayChannelAutocomplete: false,
|
delayChannelAutocomplete: false,
|
||||||
autocompleteGroups: [
|
autocompleteGroups: [
|
||||||
{id: 'gid1'},
|
TestHelper.getGroupMock({id: 'gid1'}),
|
||||||
{id: 'gid2'},
|
TestHelper.getGroupMock({id: 'gid2'}),
|
||||||
],
|
],
|
||||||
actions: {
|
actions: {
|
||||||
autocompleteUsersInChannel: jest.fn(),
|
autocompleteUsersInChannel: jest.fn(),
|
||||||
@@ -28,79 +26,89 @@ describe('components/TextBox', () => {
|
|||||||
},
|
},
|
||||||
useChannelMentions: true,
|
useChannelMentions: true,
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
|
id: '',
|
||||||
|
value: '',
|
||||||
|
onChange: jest.fn(),
|
||||||
|
onKeyPress: jest.fn(),
|
||||||
|
createMessage: '',
|
||||||
|
characterLimit: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should match snapshot with required props', () => {
|
test('should match snapshot with required props', () => {
|
||||||
function emptyFunction() {} //eslint-disable-line no-empty-function
|
const emptyFunction = jest.fn();
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
id: 'someid',
|
||||||
|
value: 'some test text',
|
||||||
|
onChange: emptyFunction,
|
||||||
|
onKeyPress: emptyFunction,
|
||||||
|
characterLimit: 400,
|
||||||
|
createMessage: 'placeholder text',
|
||||||
|
supportsCommands: false,
|
||||||
|
};
|
||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<Textbox
|
<Textbox {...props}/>,
|
||||||
id='someid'
|
|
||||||
value='some test text'
|
|
||||||
onChange={emptyFunction}
|
|
||||||
onKeyPress={emptyFunction}
|
|
||||||
characterLimit={4000}
|
|
||||||
createMessage='placeholder text'
|
|
||||||
supportsCommands={false}
|
|
||||||
{...baseProps}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot with additional, optional props', () => {
|
test('should match snapshot with additional, optional props', () => {
|
||||||
function emptyFunction() {} //eslint-disable-line no-empty-function
|
const emptyFunction = jest.fn();
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
id: 'someid',
|
||||||
|
value: 'some test text',
|
||||||
|
onChange: emptyFunction,
|
||||||
|
onKeyPress: emptyFunction,
|
||||||
|
characterLimit: 4000,
|
||||||
|
createMessage: 'placeholder text',
|
||||||
|
supportsCommands: false,
|
||||||
|
rootId: 'root_id',
|
||||||
|
onComposition: jest.fn().mockReturnValue({}),
|
||||||
|
onHeightChange: jest.fn().mockReturnValue({}),
|
||||||
|
onKeyDown: jest.fn().mockReturnValue({}),
|
||||||
|
onMouseUp: jest.fn().mockReturnValue({}),
|
||||||
|
onKeyUp: jest.fn().mockReturnValue({}),
|
||||||
|
onBlur: jest.fn().mockReturnValue({}),
|
||||||
|
handlePostError: jest.fn().mockReturnValue({}),
|
||||||
|
suggestionListPosition: 'top' as const,
|
||||||
|
emojiEnabled: true,
|
||||||
|
disabled: true,
|
||||||
|
badConnection: true,
|
||||||
|
preview: true,
|
||||||
|
openWhenEmpty: true,
|
||||||
|
};
|
||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<Textbox
|
<Textbox {...props}/>,
|
||||||
id='someid'
|
|
||||||
value='some test text'
|
|
||||||
onChange={emptyFunction}
|
|
||||||
onKeyPress={emptyFunction}
|
|
||||||
characterLimit={4000}
|
|
||||||
createMessage='placeholder text'
|
|
||||||
supportsCommands={false}
|
|
||||||
{...baseProps}
|
|
||||||
rootId='root_id'
|
|
||||||
onComposition={() => {}}
|
|
||||||
onHeightChange={() => {}}
|
|
||||||
onKeyDown={() => {}}
|
|
||||||
onMouseUp={() => {}}
|
|
||||||
onKeyUp={() => {}}
|
|
||||||
onBlur={() => {}}
|
|
||||||
handlePostError={() => {}}
|
|
||||||
suggestionListPosition='top'
|
|
||||||
emojiEnabled={true}
|
|
||||||
disabled={true}
|
|
||||||
badConnection={true}
|
|
||||||
preview={true}
|
|
||||||
openWhenEmpty={true}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error when value is too long', () => {
|
test('should throw error when value is too long', () => {
|
||||||
function emptyFunction() {} //eslint-disable-line no-empty-function
|
const emptyFunction = jest.fn();
|
||||||
|
|
||||||
// this mock function should be called when the textbox value is too long
|
// this mock function should be called when the textbox value is too long
|
||||||
let gotError = false;
|
let gotError = false;
|
||||||
function handlePostError(msg: React.ReactNode) {
|
const handlePostError = jest.fn((msg: React.ReactNode) => {
|
||||||
gotError = msg !== null;
|
gotError = msg !== null;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
id: 'someid',
|
||||||
|
value: 'some test text that exceeds char limit',
|
||||||
|
onChange: emptyFunction,
|
||||||
|
onKeyPress: emptyFunction,
|
||||||
|
characterLimit: 14,
|
||||||
|
createMessage: 'placeholder text',
|
||||||
|
supportsCommands: false,
|
||||||
|
handlePostError,
|
||||||
|
};
|
||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<Textbox
|
<Textbox {...props}/>,
|
||||||
id='someid'
|
|
||||||
value='some test text that exceeds char limit'
|
|
||||||
onChange={emptyFunction}
|
|
||||||
onKeyPress={emptyFunction}
|
|
||||||
characterLimit={14}
|
|
||||||
createMessage='placeholder text'
|
|
||||||
supportsCommands={false}
|
|
||||||
handlePostError={handlePostError}
|
|
||||||
{...baseProps}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(gotError).toEqual(true);
|
expect(gotError).toEqual(true);
|
||||||
@@ -108,26 +116,28 @@ describe('components/TextBox', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error when new property is too long', () => {
|
test('should throw error when new property is too long', () => {
|
||||||
function emptyFunction() {} //eslint-disable-line no-empty-function
|
const emptyFunction = jest.fn();
|
||||||
|
|
||||||
// this mock function should be called when the textbox value is too long
|
// this mock function should be called when the textbox value is too long
|
||||||
let gotError = false;
|
let gotError = false;
|
||||||
function handlePostError(msg: React.ReactNode) {
|
const handlePostError = jest.fn((msg: React.ReactNode) => {
|
||||||
gotError = msg !== null;
|
gotError = msg !== null;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
...baseProps,
|
||||||
|
id: 'someid',
|
||||||
|
value: 'some test text',
|
||||||
|
onChange: emptyFunction,
|
||||||
|
onKeyPress: emptyFunction,
|
||||||
|
characterLimit: 14,
|
||||||
|
createMessage: 'placeholder text',
|
||||||
|
supportsCommands: false,
|
||||||
|
handlePostError,
|
||||||
|
};
|
||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<Textbox
|
<Textbox {...props}/>,
|
||||||
id='someid'
|
|
||||||
value='some test text'
|
|
||||||
onChange={emptyFunction}
|
|
||||||
onKeyPress={emptyFunction}
|
|
||||||
characterLimit={14}
|
|
||||||
createMessage='placeholder text'
|
|
||||||
supportsCommands={false}
|
|
||||||
handlePostError={handlePostError}
|
|
||||||
{...baseProps}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
wrapper.setProps({value: 'some test text that exceeds char limit'});
|
wrapper.setProps({value: 'some test text that exceeds char limit'});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {ChangeEvent, ElementType, FocusEvent, KeyboardEvent, MouseEvent} fr
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
import type {Group} from '@mattermost/types/groups';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
@@ -59,7 +60,7 @@ export type Props = {
|
|||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
currentTeamId: string;
|
currentTeamId: string;
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
autocompleteGroups: Array<{ id: string }> | null;
|
autocompleteGroups: Group[] | null;
|
||||||
delayChannelAutocomplete: boolean;
|
delayChannelAutocomplete: boolean;
|
||||||
actions: {
|
actions: {
|
||||||
autocompleteUsersInChannel: (prefix: string, channelId: string) => Promise<ActionResult>;
|
autocompleteUsersInChannel: (prefix: string, channelId: string) => Promise<ActionResult>;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import {
|
|||||||
|
|
||||||
export {getCurrentUser, getCurrentUserId, getUsers};
|
export {getCurrentUser, getCurrentUserId, getUsers};
|
||||||
|
|
||||||
type Filters = {
|
export type Filters = {
|
||||||
role?: string;
|
role?: string;
|
||||||
inactive?: boolean;
|
inactive?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user