mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Remove access to global state from some files (#26752)
* admin_console/license_settings/trial_banner * invitation_modal and associated utils * overlay trigger * Change TrialBanner to not use makeGetCategory * Address feedback * Fixing unit tests D: * Address further feedback * Fix one last test
This commit is contained in:
parent
7429ddaf04
commit
22d72b6df8
@ -161,7 +161,6 @@
|
||||
"jest-cli": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-junit": "16.0.0",
|
||||
"jest-styled-components": "7.2.0",
|
||||
"jest-watch-typeahead": "2.2.2",
|
||||
"mmjstool": "github:mattermost/mattermost-utilities#73e61d2ede0ebf802492df4cfbac481d35efed54",
|
||||
"nock": "13.2.8",
|
||||
|
@ -6,14 +6,10 @@ import type {ReactNode} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
||||
|
||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getBool as getBoolPreference} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import store from 'stores/redux_store';
|
||||
|
||||
import AlertBanner from 'components/alert_banner';
|
||||
import withOpenStartTrialFormModal from 'components/common/hocs/cloud/with_open_start_trial_form_modal';
|
||||
import type {TelemetryProps} from 'components/common/hooks/useOpenPricingModal';
|
||||
@ -93,14 +89,9 @@ const TrialBanner = ({
|
||||
let gettingTrialErrorMsg;
|
||||
|
||||
const {formatMessage} = useIntl();
|
||||
const state = store.getState();
|
||||
const getCategory = makeGetCategory();
|
||||
const preferences = getCategory(state, Preferences.UNIQUE);
|
||||
const restartedAfterUpgradePrefValue = preferences.find((pref: PreferenceType) => pref.name === Unique.REQUEST_TRIAL_AFTER_SERVER_UPGRADE);
|
||||
const clickedUpgradeAndStartTrialBtn = preferences.find((pref: PreferenceType) => pref.name === Unique.CLICKED_UPGRADE_AND_TRIAL_BTN);
|
||||
|
||||
const restartedAfterUpgradePrefs = restartedAfterUpgradePrefValue?.value === 'true';
|
||||
const clickedUpgradeAndTrialBtn = clickedUpgradeAndStartTrialBtn?.value === 'true';
|
||||
const restartedAfterUpgradePrefs = useSelector<GlobalState>((state) => getBoolPreference(state, Preferences.UNIQUE, Unique.REQUEST_TRIAL_AFTER_SERVER_UPGRADE));
|
||||
const clickedUpgradeAndTrialBtn = useSelector<GlobalState>((state) => getBoolPreference(state, Preferences.UNIQUE, Unique.CLICKED_UPGRADE_AND_TRIAL_BTN));
|
||||
|
||||
const userId = useSelector((state: GlobalState) => getCurrentUserId(state));
|
||||
|
||||
|
@ -1,335 +1,79 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/app_bar/app_bar should match snapshot on mount 1`] = `
|
||||
<AppBar>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="app-bar"
|
||||
class="app-bar"
|
||||
>
|
||||
<div
|
||||
className="app-bar__top"
|
||||
class="app-bar__top"
|
||||
>
|
||||
<AppBarPluginComponent
|
||||
component={
|
||||
Object {
|
||||
"action": [MockFunction],
|
||||
"icon": "fallback_component",
|
||||
"id": "the_component_id",
|
||||
"pluginId": "playbooks",
|
||||
"tooltipText": "Playbooks Tooltip",
|
||||
}
|
||||
}
|
||||
key="the_component_id"
|
||||
<div
|
||||
class="app-bar__icon"
|
||||
id="app-bar-icon-playbooks"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
bsClass="tooltip"
|
||||
id="pluginTooltip-app-bar-icon-playbooks"
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Playbooks Tooltip
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
<div
|
||||
class="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
bsClass="tooltip"
|
||||
id="pluginTooltip-app-bar-icon-playbooks"
|
||||
intl={null}
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Playbooks Tooltip
|
||||
</span>
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="app-bar__icon"
|
||||
id="app-bar-icon-playbooks"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
fallback_component
|
||||
</div>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</AppBarPluginComponent>
|
||||
fallback_component
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
className="app-bar__divider"
|
||||
key="divider"
|
||||
class="app-bar__divider"
|
||||
/>
|
||||
<AppBarBinding
|
||||
binding={
|
||||
Object {
|
||||
"app_id": "com.mattermost.zendesk",
|
||||
"label": "Create Subscription",
|
||||
}
|
||||
}
|
||||
key="com.mattermost.zendesk_Create Subscription"
|
||||
<div
|
||||
aria-label="Create Subscription"
|
||||
class="app-bar__icon"
|
||||
id="app-bar-icon-com.mattermost.zendesk"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
bsClass="tooltip"
|
||||
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Create Subscription
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
<div
|
||||
class="app-bar__icon-inner"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
bsClass="tooltip"
|
||||
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
||||
intl={null}
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Create Subscription
|
||||
</span>
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<div
|
||||
aria-label="Create Subscription"
|
||||
className="app-bar__icon"
|
||||
id="app-bar-icon-com.mattermost.zendesk"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="app-bar__icon-inner"
|
||||
>
|
||||
<img />
|
||||
</div>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</AppBarBinding>
|
||||
<img />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppBar>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`components/app_bar/app_bar should match snapshot on mount when App Bar is disabled 1`] = `
|
||||
<AppBar>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="app-bar"
|
||||
class="app-bar"
|
||||
>
|
||||
<div
|
||||
className="app-bar__top"
|
||||
class="app-bar__top"
|
||||
>
|
||||
<AppBarPluginComponent
|
||||
component={
|
||||
Object {
|
||||
"action": [MockFunction],
|
||||
"icon": "fallback_component",
|
||||
"id": "the_component_id",
|
||||
"pluginId": "playbooks",
|
||||
"tooltipText": "Playbooks Tooltip",
|
||||
}
|
||||
}
|
||||
key="the_component_id"
|
||||
<div
|
||||
class="app-bar__icon"
|
||||
id="app-bar-icon-playbooks"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
bsClass="tooltip"
|
||||
id="pluginTooltip-app-bar-icon-playbooks"
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Playbooks Tooltip
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
<div
|
||||
class="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
bsClass="tooltip"
|
||||
id="pluginTooltip-app-bar-icon-playbooks"
|
||||
intl={null}
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Playbooks Tooltip
|
||||
</span>
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="app-bar__icon"
|
||||
id="app-bar-icon-playbooks"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
fallback_component
|
||||
</div>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</AppBarPluginComponent>
|
||||
fallback_component
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
className="app-bar__divider"
|
||||
key="divider"
|
||||
class="app-bar__divider"
|
||||
/>
|
||||
<AppBarBinding
|
||||
binding={
|
||||
Object {
|
||||
"app_id": "com.mattermost.zendesk",
|
||||
"label": "Create Subscription",
|
||||
}
|
||||
}
|
||||
key="com.mattermost.zendesk_Create Subscription"
|
||||
<div
|
||||
aria-label="Create Subscription"
|
||||
class="app-bar__icon"
|
||||
id="app-bar-icon-com.mattermost.zendesk"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
bsClass="tooltip"
|
||||
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Create Subscription
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
<div
|
||||
class="app-bar__icon-inner"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
bsClass="tooltip"
|
||||
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
||||
intl={null}
|
||||
placement="right"
|
||||
>
|
||||
<span>
|
||||
Create Subscription
|
||||
</span>
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="left"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<div
|
||||
aria-label="Create Subscription"
|
||||
className="app-bar__icon"
|
||||
id="app-bar-icon-com.mattermost.zendesk"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
className="app-bar__icon-inner"
|
||||
>
|
||||
<img />
|
||||
</div>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</AppBarBinding>
|
||||
<img />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppBar>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount, shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {AppBinding} from '@mattermost/types/apps';
|
||||
@ -9,100 +8,15 @@ import type {AppBinding} from '@mattermost/types/apps';
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
import {AppBindingLocations} from 'mattermost-redux/constants/apps';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import type {PluginComponent} from 'types/store/plugins';
|
||||
|
||||
import AppBar from './app_bar';
|
||||
|
||||
import 'jest-styled-components';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: GlobalState;
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
useDispatch: () => mockDispatch,
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom') as typeof import('react-router-dom'),
|
||||
useLocation: () => {
|
||||
return {
|
||||
pathname: '',
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe('components/app_bar/app_bar', () => {
|
||||
beforeEach(() => {
|
||||
mockState = {
|
||||
views: {
|
||||
rhs: {
|
||||
isSidebarOpen: true,
|
||||
rhsState: 'plugin',
|
||||
pluggableId: 'the_rhs_plugin_component',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
components: {
|
||||
AppBar: channelHeaderComponents,
|
||||
RightHandSidebarComponent: rhsComponents,
|
||||
Product: [],
|
||||
} as {[componentName: string]: PluginComponent[]},
|
||||
},
|
||||
entities: {
|
||||
apps: {
|
||||
main: {
|
||||
bindings: channelHeaderAppBindings,
|
||||
} as {bindings: AppBinding[]},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
general: {
|
||||
config: {
|
||||
DisableAppBar: 'false',
|
||||
FeatureFlagAppsEnabled: 'true',
|
||||
} as any,
|
||||
},
|
||||
channels: {
|
||||
currentChannelId: 'currentchannel',
|
||||
channels: {
|
||||
currentchannel: {
|
||||
id: 'currentchannel',
|
||||
},
|
||||
} as any,
|
||||
myMembers: {
|
||||
currentchannel: {
|
||||
id: 'memberid',
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'currentteam',
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {
|
||||
},
|
||||
} as any,
|
||||
users: {
|
||||
currentUserId: 'user1',
|
||||
profiles: {
|
||||
user1: {
|
||||
roles: 'system_user',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
roles: {
|
||||
roles: {
|
||||
system_user: {
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
} as GlobalState;
|
||||
});
|
||||
|
||||
const channelHeaderComponents: PluginComponent[] = [
|
||||
{
|
||||
id: 'the_component_id',
|
||||
@ -134,65 +48,141 @@ describe('components/app_bar/app_bar', () => {
|
||||
},
|
||||
] as AppBinding[];
|
||||
|
||||
test('should match snapshot on mount', async () => {
|
||||
const wrapper = mount(
|
||||
<AppBar/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot on mount when App Bar is disabled', async () => {
|
||||
mockState.entities.general.config.DisableAppBar = 'false';
|
||||
|
||||
const wrapper = mount(
|
||||
<AppBar/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not show marketplace if disabled or user does not have SYSCONSOLE_WRITE_PLUGINS permission', async () => {
|
||||
mockState.entities.general = {
|
||||
config: {
|
||||
DisableAppBar: 'true',
|
||||
FeatureFlagAppsEnabled: 'true',
|
||||
EnableMarketplace: 'true',
|
||||
PluginsEnabled: 'true',
|
||||
const initialState = {
|
||||
views: {
|
||||
rhs: {
|
||||
isSidebarOpen: true,
|
||||
rhsState: 'plugin',
|
||||
pluggableId: 'the_rhs_plugin_component',
|
||||
},
|
||||
} as any;
|
||||
|
||||
const wrapper = shallow(
|
||||
<AppBar/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('AppBarMarketplace').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('should show marketplace if enabled and user has SYSCONSOLE_WRITE_PLUGINS permission', async () => {
|
||||
mockState.entities.general = {
|
||||
config: {
|
||||
DisableAppBar: 'false',
|
||||
FeatureFlagAppsEnabled: 'true',
|
||||
EnableMarketplace: 'true',
|
||||
PluginsEnabled: 'true',
|
||||
},
|
||||
plugins: {
|
||||
components: {
|
||||
AppBar: channelHeaderComponents,
|
||||
RightHandSidebarComponent: rhsComponents,
|
||||
Product: [],
|
||||
} as {[componentName: string]: PluginComponent[]},
|
||||
},
|
||||
entities: {
|
||||
apps: {
|
||||
main: {
|
||||
bindings: channelHeaderAppBindings,
|
||||
} as {bindings: AppBinding[]},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
} as any;
|
||||
|
||||
mockState.entities.roles = {
|
||||
roles: {
|
||||
system_user: {
|
||||
permissions: [
|
||||
Permissions.SYSCONSOLE_WRITE_PLUGINS,
|
||||
],
|
||||
general: {
|
||||
config: {
|
||||
DisableAppBar: 'false',
|
||||
FeatureFlagAppsEnabled: 'true',
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
channels: {
|
||||
currentChannelId: 'currentchannel',
|
||||
channels: {
|
||||
currentchannel: TestHelper.getChannelMock({
|
||||
id: 'currentchannel',
|
||||
}),
|
||||
},
|
||||
myMembers: {
|
||||
currentchannel: TestHelper.getChannelMembershipMock({
|
||||
channel_id: 'currentchannel',
|
||||
user_id: 'user1',
|
||||
}),
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'currentteam',
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'user1',
|
||||
profiles: {
|
||||
user1: TestHelper.getUserMock({
|
||||
roles: 'system_user',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
test('should match snapshot on mount', () => {
|
||||
const testState = initialState;
|
||||
const {asFragment} = renderWithContext(
|
||||
<AppBar/>,
|
||||
testState,
|
||||
);
|
||||
|
||||
expect(wrapper.find('AppBarMarketplace').exists()).toEqual(true);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot on mount when App Bar is disabled', () => {
|
||||
const testState = mergeObjects(initialState, {
|
||||
entities: {
|
||||
general: {
|
||||
config: {
|
||||
DisableAppbar: 'false',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {asFragment} = renderWithContext(
|
||||
<AppBar/>,
|
||||
testState,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not show marketplace if disabled or user does not have SYSCONSOLE_WRITE_PLUGINS permission', () => {
|
||||
const testState = mergeObjects(initialState, {
|
||||
entities: {
|
||||
general: {
|
||||
config: {
|
||||
DisableAppBar: 'true',
|
||||
FeatureFlagAppsEnabled: 'true',
|
||||
EnableMarketplace: 'true',
|
||||
PluginsEnabled: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderWithContext(
|
||||
<AppBar/>,
|
||||
testState,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('App Marketplace')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show marketplace if enabled and user has SYSCONSOLE_WRITE_PLUGINS permission', () => {
|
||||
const testState = mergeObjects(initialState, {
|
||||
entities: {
|
||||
general: {
|
||||
config: {
|
||||
DisableAppBar: 'false',
|
||||
FeatureFlagAppsEnabled: 'true',
|
||||
EnableMarketplace: 'true',
|
||||
PluginsEnabled: 'true',
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
system_user: {
|
||||
permissions: [
|
||||
Permissions.SYSCONSOLE_WRITE_PLUGINS,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderWithContext(
|
||||
<AppBar/>,
|
||||
testState,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('App Marketplace')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -4,7 +4,7 @@
|
||||
import classNames from 'classnames';
|
||||
import React, {useRef, useState} from 'react';
|
||||
import {Tooltip} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import OverlayTrigger from 'components/overlay_trigger';
|
||||
|
||||
@ -21,6 +21,8 @@ type Props = {
|
||||
};
|
||||
|
||||
const CopyButton: React.FC<Props> = (props: Props) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
@ -74,16 +76,16 @@ const CopyButton: React.FC<Props> = (props: Props) => {
|
||||
<span
|
||||
className={spanClassName}
|
||||
onClick={copyText}
|
||||
aria-label={intl.formatMessage({id: getId(), defaultMessage: getDefaultMessage()})}
|
||||
role='button'
|
||||
>
|
||||
{!isCopied &&
|
||||
<i
|
||||
role='button'
|
||||
className='icon icon-content-copy'
|
||||
/>
|
||||
}
|
||||
{isCopied &&
|
||||
<i
|
||||
role='button'
|
||||
className='icon icon-check'
|
||||
/>
|
||||
}
|
||||
|
@ -3,14 +3,10 @@
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {Button} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import TeamUrl from 'components/create_team/components/team_url/team_url';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import Constants from 'utils/constants';
|
||||
|
||||
jest.mock('images/logo.png', () => 'logo.png');
|
||||
@ -30,31 +26,20 @@ describe('/components/create_team/components/display_name', () => {
|
||||
history: {push: jest.fn()},
|
||||
};
|
||||
|
||||
const chatLengthError = (
|
||||
<FormattedMessage
|
||||
id='create_team.team_url.charLength'
|
||||
defaultMessage='Name must be {min} or more characters up to a maximum of {max}'
|
||||
values={{
|
||||
min: Constants.MIN_TEAMNAME_LENGTH,
|
||||
max: Constants.MAX_TEAMNAME_LENGTH,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<TeamUrl {...defaultProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should return to display_name.jsx page', () => {
|
||||
const wrapper = mountWithIntl(<TeamUrl {...defaultProps}/>);
|
||||
test('should return to display_name.jsx page', async () => {
|
||||
renderWithContext(<TeamUrl {...defaultProps}/>);
|
||||
|
||||
wrapper.find('a').simulate('click', {
|
||||
preventDefault: () => jest.fn(),
|
||||
screen.getByText('Back to previous step').click();
|
||||
|
||||
expect(defaultProps.updateParent).toHaveBeenCalledWith({
|
||||
...defaultProps.state,
|
||||
wizard: 'display_name',
|
||||
});
|
||||
|
||||
expect(wrapper.prop('state').wizard).toBe('display_name');
|
||||
expect(wrapper.prop('updateParent')).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should successfully submit', async () => {
|
||||
@ -65,81 +50,80 @@ describe('/components/create_team/components/display_name', () => {
|
||||
const actions = {...defaultProps.actions, checkIfTeamExists};
|
||||
const props = {...defaultProps, actions};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
renderWithContext(
|
||||
<TeamUrl {...props}/>,
|
||||
);
|
||||
|
||||
await (wrapper.instance() as unknown as TeamUrl).submitNext({preventDefault: jest.fn()} as unknown as React.MouseEvent<Button, MouseEvent>);
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('This URL is taken or unavailable. Please try another.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(actions.checkIfTeamExists).toHaveBeenCalledTimes(1);
|
||||
expect(actions.createTeam).not.toHaveBeenCalled();
|
||||
|
||||
await (wrapper.instance() as unknown as TeamUrl).submitNext({preventDefault: jest.fn()} as unknown as React.MouseEvent<Button, MouseEvent>);
|
||||
expect(actions.checkIfTeamExists).toHaveBeenCalledTimes(2);
|
||||
expect(actions.createTeam).toHaveBeenCalledTimes(1);
|
||||
expect(actions.createTeam).toBeCalledWith({display_name: 'test-team', name: 'test-team', type: 'O'});
|
||||
expect(props.history.push).toHaveBeenCalledTimes(1);
|
||||
expect(props.history.push).toBeCalledWith('/test-team/channels/town-square');
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(actions.checkIfTeamExists).toHaveBeenCalledTimes(2);
|
||||
expect(actions.createTeam).toHaveBeenCalledTimes(1);
|
||||
expect(actions.createTeam).toBeCalledWith({display_name: 'test-team', name: 'test-team', type: 'O'});
|
||||
expect(props.history.push).toHaveBeenCalledTimes(1);
|
||||
expect(props.history.push).toBeCalledWith('/test-team/channels/town-square');
|
||||
});
|
||||
});
|
||||
|
||||
test('should display isRequired error', () => {
|
||||
const wrapper = mountWithIntl(<TeamUrl {...defaultProps}/>);
|
||||
(wrapper.find('.form-control').instance() as unknown as HTMLInputElement).value = '';
|
||||
wrapper.find('.form-control').simulate('change');
|
||||
wrapper.find('button').simulate('click', {preventDefault: () => jest.fn()});
|
||||
|
||||
expect(wrapper.state('nameError')).toEqual(
|
||||
<FormattedMessage
|
||||
id='create_team.team_url.required'
|
||||
defaultMessage='This field is required'
|
||||
/>,
|
||||
renderWithContext(
|
||||
<TeamUrl {...defaultProps}/>,
|
||||
);
|
||||
|
||||
userEvent.clear(screen.getByRole('textbox'));
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
expect(screen.getByText('This field is required')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should display charLength error', () => {
|
||||
const wrapper = mountWithIntl(<TeamUrl {...defaultProps}/>);
|
||||
(wrapper.find('.form-control').instance() as unknown as HTMLInputElement).value = 'a';
|
||||
wrapper.find('.form-control').simulate('change');
|
||||
wrapper.find('button').simulate('click', {preventDefault: () => jest.fn()});
|
||||
expect(wrapper.state('nameError')).toEqual(chatLengthError);
|
||||
const lengthError = `Name must be ${Constants.MIN_TEAMNAME_LENGTH} or more characters up to a maximum of ${Constants.MAX_TEAMNAME_LENGTH}`;
|
||||
|
||||
(wrapper.find('.form-control').instance() as unknown as HTMLInputElement).value = 'a'.repeat(Constants.MAX_TEAMNAME_LENGTH + 1);
|
||||
wrapper.find('.form-control').simulate('change');
|
||||
wrapper.find('button').simulate('click', {preventDefault: () => jest.fn()});
|
||||
expect(wrapper.state('nameError')).toEqual(chatLengthError);
|
||||
renderWithContext(
|
||||
<TeamUrl {...defaultProps}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText(lengthError)).not.toBeInTheDocument();
|
||||
|
||||
userEvent.type(screen.getByRole('textbox'), 'a');
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
expect(screen.getByText(lengthError)).toBeInTheDocument();
|
||||
|
||||
userEvent.type(screen.getByRole('textbox'), 'a'.repeat(Constants.MAX_TEAMNAME_LENGTH + 1));
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
expect(screen.getByText(lengthError)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should display teamUrl regex error', () => {
|
||||
const wrapper = mountWithIntl(<TeamUrl {...defaultProps}/>);
|
||||
(wrapper.find('.form-control').instance() as unknown as HTMLInputElement).value = '!!wrongName1';
|
||||
wrapper.find('.form-control').simulate('change');
|
||||
wrapper.find('button').simulate('click', {preventDefault: () => jest.fn()});
|
||||
expect(wrapper.state('nameError')).toEqual(
|
||||
<FormattedMessage
|
||||
id='create_team.team_url.regex'
|
||||
defaultMessage="Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."
|
||||
/>,
|
||||
renderWithContext(
|
||||
<TeamUrl {...defaultProps}/>,
|
||||
);
|
||||
|
||||
userEvent.type(screen.getByRole('textbox'), '!!wrongName1');
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
expect(screen.getByText("Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should display teamUrl taken error', () => {
|
||||
const store = mockStore({
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
license: {
|
||||
Cloud: 'false',
|
||||
},
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'currentUserId',
|
||||
},
|
||||
},
|
||||
});
|
||||
renderWithContext(
|
||||
<TeamUrl {...defaultProps}/>,
|
||||
);
|
||||
|
||||
const wrapper = mountWithIntl(<Provider store={store}><TeamUrl {...defaultProps}/></Provider>);
|
||||
(wrapper.find('.form-control').instance() as unknown as HTMLInputElement).value = 'channel';
|
||||
wrapper.find('.form-control').simulate('change');
|
||||
wrapper.find('button').simulate('click', {preventDefault: () => jest.fn()});
|
||||
expect((wrapper as any).find(TeamUrl).state('nameError').props.id).toEqual('create_team.team_url.taken');
|
||||
userEvent.type(screen.getByRole('textbox'), 'channel');
|
||||
screen.getByText('Finish').click();
|
||||
|
||||
expect(screen.getByText('Please try another.', {exact: false})).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -2,11 +2,10 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
|
||||
import type {SystemEmoji} from '@mattermost/types/emojis';
|
||||
|
||||
import {render, screen} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import EmojiMap from 'utils/emoji_map';
|
||||
|
||||
import EmojiPicker from './emoji_picker';
|
||||
@ -19,11 +18,6 @@ jest.mock('components/emoji_picker/components/emoji_picker_preview', () => ({emo
|
||||
));
|
||||
|
||||
describe('components/emoji_picker/EmojiPicker', () => {
|
||||
const intlProviderProps = {
|
||||
defaultLocale: 'en',
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
filter: '',
|
||||
visible: true,
|
||||
@ -45,20 +39,16 @@ describe('components/emoji_picker/EmojiPicker', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const {asFragment} = render(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<EmojiPicker {...baseProps}/>
|
||||
</IntlProvider>,
|
||||
const {asFragment} = renderWithContext(
|
||||
<EmojiPicker {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Recent category should not exist if there are no recent emojis', () => {
|
||||
render(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<EmojiPicker {...baseProps}/>
|
||||
</IntlProvider>,
|
||||
renderWithContext(
|
||||
<EmojiPicker {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('emoji_picker.recent')).toBeNull();
|
||||
@ -70,10 +60,8 @@ describe('components/emoji_picker/EmojiPicker', () => {
|
||||
recentEmojis: ['smile'],
|
||||
};
|
||||
|
||||
render(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<EmojiPicker {...props}/>
|
||||
</IntlProvider>,
|
||||
renderWithContext(
|
||||
<EmojiPicker {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('emoji_picker.recent')).not.toBeNull();
|
||||
@ -85,10 +73,8 @@ describe('components/emoji_picker/EmojiPicker', () => {
|
||||
filter: 'wave',
|
||||
};
|
||||
|
||||
render(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<EmojiPicker {...props}/>
|
||||
</IntlProvider>,
|
||||
renderWithContext(
|
||||
<EmojiPicker {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText('Preview for wave emoji')).not.toBeNull();
|
||||
|
@ -22,7 +22,6 @@ exports[`components/file_attachment/FilenameOverlay should match snapshot, compa
|
||||
<a
|
||||
className="post-image__name"
|
||||
href="#"
|
||||
id="file-attachment-link"
|
||||
onClick={[MockFunction]}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
@ -7,7 +7,6 @@ import React from 'react';
|
||||
import type {GlobalState} from '@mattermost/types/store';
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import FileAttachment from './file_attachment';
|
||||
@ -168,15 +167,12 @@ describe('FileAttachment', () => {
|
||||
|
||||
test('should blur file attachment link after click', () => {
|
||||
const props = {...baseProps, compactDisplay: true};
|
||||
const wrapper = mountWithIntl(<FileAttachment {...props}/>);
|
||||
const e = {
|
||||
preventDefault: jest.fn(),
|
||||
target: {blur: jest.fn()},
|
||||
};
|
||||
renderWithContext(<FileAttachment {...props}/>);
|
||||
|
||||
const a = wrapper.find('#file-attachment-link');
|
||||
a.simulate('click', e);
|
||||
expect(e.target.blur).toHaveBeenCalled();
|
||||
const link = screen.getByText(baseProps.fileInfo.name);
|
||||
const blur = jest.spyOn(link, 'blur');
|
||||
screen.getByText(baseProps.fileInfo.name).click();
|
||||
expect(blur).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('archived file', () => {
|
||||
|
@ -71,7 +71,6 @@ export default class FilenameOverlay extends React.PureComponent<Props> {
|
||||
overlay={<Tooltip id='file-name__tooltip'>{fileName}</Tooltip>}
|
||||
>
|
||||
<a
|
||||
id='file-attachment-link'
|
||||
href='#'
|
||||
onClick={handleImageClick}
|
||||
className='post-image__name'
|
||||
|
@ -1,337 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/file_preview_modal/file_preview_modal_main_actions/FilePreviewModalMainActions should match snapshot for external image with public links enabled 1`] = `
|
||||
<div
|
||||
className="file-preview-modal-main-actions__actions"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="download"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="download-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Download"
|
||||
id="view_image_popover.download"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<ExternalLink
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
download="img.png"
|
||||
href="http://example.com/img.png"
|
||||
location="file_preview_modal_main_actions"
|
||||
>
|
||||
<i
|
||||
className="icon icon-download-outline"
|
||||
/>
|
||||
</ExternalLink>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="publicLink"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="close-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Close"
|
||||
id="full_screen_modal.close"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close"
|
||||
/>
|
||||
</button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/file_preview_modal/file_preview_modal_main_actions/FilePreviewModalMainActions should match snapshot when copy content is enabled 1`] = `
|
||||
<div
|
||||
className="file-preview-modal-main-actions__actions"
|
||||
>
|
||||
<CopyButton
|
||||
afterCopyText="Copied"
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
content="test content"
|
||||
placement="bottom"
|
||||
/>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="download"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="download-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Download"
|
||||
id="view_image_popover.download"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<ExternalLink
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
download="img.png"
|
||||
href="http://example.com/img.png"
|
||||
location="file_preview_modal_main_actions"
|
||||
>
|
||||
<i
|
||||
className="icon icon-download-outline"
|
||||
/>
|
||||
</ExternalLink>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="publicLink"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="close-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Close"
|
||||
id="full_screen_modal.close"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close"
|
||||
/>
|
||||
</button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/file_preview_modal/file_preview_modal_main_actions/FilePreviewModalMainActions should match snapshot with public links disabled 1`] = `
|
||||
<div
|
||||
className="file-preview-modal-main-actions__actions"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="download"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="download-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Download"
|
||||
id="view_image_popover.download"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<ExternalLink
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
download="img.png"
|
||||
href="http://example.com/img.png"
|
||||
location="file_preview_modal_main_actions"
|
||||
>
|
||||
<i
|
||||
className="icon icon-download-outline"
|
||||
/>
|
||||
</ExternalLink>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="publicLink"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="close-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Close"
|
||||
id="full_screen_modal.close"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close"
|
||||
/>
|
||||
</button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/file_preview_modal/file_preview_modal_main_actions/FilePreviewModalMainActions should match snapshot with public links enabled 1`] = `
|
||||
<div
|
||||
className="file-preview-modal-main-actions__actions"
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="filePreviewPublicLink"
|
||||
onExit={[Function]}
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="link-variant-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Get a public link"
|
||||
id="view_image_popover.publicLink"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
shouldUpdatePosition={true}
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<a
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-link-variant"
|
||||
/>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="download"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="download-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Download"
|
||||
id="view_image_popover.download"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<ExternalLink
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
download="img.png"
|
||||
href="http://example.com/img.png"
|
||||
location="file_preview_modal_main_actions"
|
||||
>
|
||||
<i
|
||||
className="icon icon-download-outline"
|
||||
/>
|
||||
</ExternalLink>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
key="publicLink"
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="close-icon-tooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Close"
|
||||
id="full_screen_modal.close"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
onClick={[MockFunction]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close"
|
||||
/>
|
||||
</button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/file_preview_modal/file_preview_modal_main_actions/FilePreviewModalMainActions should match snapshot with public links enabled 2`] = `
|
||||
<a
|
||||
className="file-preview-modal-main-actions__action-item"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-link-variant"
|
||||
/>
|
||||
</a>
|
||||
`;
|
@ -1,30 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount, shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
import * as fileActions from 'mattermost-redux/actions/files';
|
||||
|
||||
import OverlayTrigger from 'components/overlay_trigger';
|
||||
import Tooltip from 'components/tooltip';
|
||||
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import FilePreviewModalMainActions from './file_preview_modal_main_actions';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: GlobalState;
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
useDispatch: () => mockDispatch,
|
||||
}));
|
||||
|
||||
describe('components/file_preview_modal/file_preview_modal_main_actions/FilePreviewModalMainActions', () => {
|
||||
let defaultProps: ComponentProps<typeof FilePreviewModalMainActions>;
|
||||
beforeEach(() => {
|
||||
@ -39,22 +26,6 @@ describe('components/file_preview_modal/file_preview_modal_main_actions/FilePrev
|
||||
content: 'test content',
|
||||
canCopyContent: false,
|
||||
};
|
||||
|
||||
mockState = {
|
||||
entities: {
|
||||
general: {config: {}},
|
||||
users: {profiles: {}},
|
||||
channels: {channels: {}},
|
||||
preferences: {
|
||||
myPreferences: {
|
||||
|
||||
},
|
||||
},
|
||||
files: {
|
||||
filePublicLink: {link: 'http://example.com/img.png'},
|
||||
},
|
||||
},
|
||||
} as GlobalState;
|
||||
});
|
||||
|
||||
test('should match snapshot with public links disabled', () => {
|
||||
@ -63,8 +34,11 @@ describe('components/file_preview_modal/file_preview_modal_main_actions/FilePrev
|
||||
enablePublicLink: false,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<FilePreviewModalMainActions {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('Get a public link')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should match snapshot with public links enabled', () => {
|
||||
@ -73,32 +47,38 @@ describe('components/file_preview_modal/file_preview_modal_main_actions/FilePrev
|
||||
enablePublicLink: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<FilePreviewModalMainActions {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
const overlayWrapper = wrapper.find(OverlayTrigger).first();
|
||||
expect(overlayWrapper.prop('overlay').type).toEqual(Tooltip);
|
||||
expect(overlayWrapper.prop('children')).toMatchSnapshot();
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('Get a public link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should match snapshot for external image with public links enabled', () => {
|
||||
test('should not show public link button for external image with public links enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
enablePublicLink: true,
|
||||
showPublicLink: false,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<FilePreviewModalMainActions {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('Get a public link')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should match snapshot when copy content is enabled', () => {
|
||||
test('should show copy button when copy content is enabled', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
canCopyContent: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<FilePreviewModalMainActions {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText('Copy code')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call public link callback', () => {
|
||||
@ -107,17 +87,22 @@ describe('components/file_preview_modal/file_preview_modal_main_actions/FilePrev
|
||||
...defaultProps,
|
||||
enablePublicLink: true,
|
||||
};
|
||||
const wrapper = shallow(<FilePreviewModalMainActions {...props}/>);
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(3);
|
||||
const overlayWrapper = wrapper.find(OverlayTrigger).first().children('a');
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(0);
|
||||
overlayWrapper.simulate('click');
|
||||
|
||||
screen.getByLabelText('Get a public link').click();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should not get public api when public links is disabled', async () => {
|
||||
const spy = jest.spyOn(fileActions, 'getFilePublicLink');
|
||||
mount(<FilePreviewModalMainActions {...defaultProps}/>);
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...defaultProps}/>,
|
||||
);
|
||||
expect(spy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@ -127,7 +112,9 @@ describe('components/file_preview_modal/file_preview_modal_main_actions/FilePrev
|
||||
...defaultProps,
|
||||
enablePublicLink: true,
|
||||
};
|
||||
mount(<FilePreviewModalMainActions {...props}/>);
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@ -137,9 +124,11 @@ describe('components/file_preview_modal/file_preview_modal_main_actions/FilePrev
|
||||
...defaultProps,
|
||||
canCopyContent: true,
|
||||
};
|
||||
const wrapper = mount(<FilePreviewModalMainActions {...props}/>);
|
||||
renderWithContext(
|
||||
<FilePreviewModalMainActions {...props}/>,
|
||||
);
|
||||
expect(spy).toHaveBeenCalledTimes(0);
|
||||
wrapper.find('.icon-content-copy').simulate('click');
|
||||
screen.getByLabelText('Copy code').click();
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {memo, useEffect, useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import type {FileInfo} from '@mattermost/types/files';
|
||||
@ -25,10 +25,6 @@ import type {LinkInfo} from '../types';
|
||||
|
||||
import './file_preview_modal_main_actions.scss';
|
||||
|
||||
interface DownloadLinkProps {
|
||||
download?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
usedInside?: 'Header' | 'Footer';
|
||||
showOnlyClose?: boolean;
|
||||
@ -45,6 +41,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const tooltipPlacement = props.usedInside === 'Header' ? 'bottom' : 'top';
|
||||
const selectedFilePublicLink = useSelector((state: GlobalState) => selectFilePublicLink(state)?.link);
|
||||
const dispatch = useDispatch();
|
||||
@ -60,6 +58,10 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
setPublicLinkCopied(true);
|
||||
};
|
||||
|
||||
const closeMessage = intl.formatMessage({
|
||||
id: 'full_screen_modal.close',
|
||||
defaultMessage: 'Close',
|
||||
});
|
||||
const closeButton = (
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
@ -67,34 +69,31 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
placement={tooltipPlacement}
|
||||
overlay={
|
||||
<Tooltip id='close-icon-tooltip'>
|
||||
<FormattedMessage
|
||||
id='full_screen_modal.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
{closeMessage}
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<button
|
||||
className='file-preview-modal-main-actions__action-item'
|
||||
onClick={props.handleModalClose}
|
||||
aria-label={closeMessage}
|
||||
>
|
||||
<i className='icon icon-close'/>
|
||||
</button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
let publicTooltipMessage = (
|
||||
<FormattedMessage
|
||||
id='view_image_popover.publicLink'
|
||||
defaultMessage='Get a public link'
|
||||
/>
|
||||
);
|
||||
|
||||
let publicTooltipMessage;
|
||||
if (publicLinkCopied) {
|
||||
publicTooltipMessage = (
|
||||
<FormattedMessage
|
||||
id='file_preview_modal_main_actions.public_link-copied'
|
||||
defaultMessage='Public link copied'
|
||||
/>
|
||||
);
|
||||
publicTooltipMessage = intl.formatMessage({
|
||||
id: 'file_preview_modal_main_actions.public_link-copied',
|
||||
defaultMessage: 'Public link copied',
|
||||
});
|
||||
} else {
|
||||
publicTooltipMessage = intl.formatMessage({
|
||||
id: 'view_image_popover.publicLink',
|
||||
defaultMessage: 'Get a public link',
|
||||
});
|
||||
}
|
||||
const publicLink = (
|
||||
<OverlayTrigger
|
||||
@ -113,13 +112,17 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
href='#'
|
||||
className='file-preview-modal-main-actions__action-item'
|
||||
onClick={copyPublicLink}
|
||||
aria-label={publicTooltipMessage}
|
||||
>
|
||||
<i className='icon icon-link-variant'/>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
const downloadLinkProps: DownloadLinkProps = {};
|
||||
downloadLinkProps.download = props.filename;
|
||||
|
||||
const downloadMessage = intl.formatMessage({
|
||||
id: 'view_image_popover.download',
|
||||
defaultMessage: 'Download',
|
||||
});
|
||||
const download = (
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
@ -127,10 +130,7 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
placement={tooltipPlacement}
|
||||
overlay={
|
||||
<Tooltip id='download-icon-tooltip'>
|
||||
<FormattedMessage
|
||||
id='view_image_popover.download'
|
||||
defaultMessage='Download'
|
||||
/>
|
||||
{downloadMessage}
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
@ -139,6 +139,7 @@ const FilePreviewModalMainActions: React.FC<Props> = (props: Props) => {
|
||||
className='file-preview-modal-main-actions__action-item'
|
||||
location='file_preview_modal_main_actions'
|
||||
download={props.filename}
|
||||
aria-label={downloadMessage}
|
||||
>
|
||||
<i className='icon icon-download-outline'/>
|
||||
</ExternalLink>
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
import {makeAsyncComponent} from 'components/async_load';
|
||||
|
||||
import {Constants} from 'utils/constants';
|
||||
import {getRoleForTrackFlow} from 'utils/utils';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
@ -86,6 +87,7 @@ export function mapStateToProps(state: GlobalState, props: OwnProps) {
|
||||
isAdmin: isAdmin(getCurrentUser(state).roles),
|
||||
currentChannel,
|
||||
townSquareDisplayName,
|
||||
roleForTrackFlow: getRoleForTrackFlow(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,11 @@ import {Provider} from 'react-redux';
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import deepFreeze from 'mattermost-redux/utils/deep_freeze';
|
||||
|
||||
import store from 'stores/redux_store';
|
||||
|
||||
import {mountWithThemedIntl} from 'tests/helpers/themed-intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {SelfHostedProducts} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
import {generateId} from 'utils/utils';
|
||||
@ -47,6 +47,7 @@ const defaultProps: Props = deepFreeze({
|
||||
intl: {} as IntlShape,
|
||||
townSquareDisplayName: '',
|
||||
onExited: jest.fn(),
|
||||
roleForTrackFlow: {started_by_role: General.SYSTEM_USER_ROLE},
|
||||
});
|
||||
|
||||
let props = defaultProps;
|
||||
@ -107,7 +108,7 @@ describe('InvitationModal', () => {
|
||||
},
|
||||
};
|
||||
|
||||
store.getState = () => (state);
|
||||
const store = mockStore(state);
|
||||
|
||||
beforeEach(() => {
|
||||
props = defaultProps;
|
||||
|
@ -17,8 +17,6 @@ import {isEmail} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
import {trackEvent} from 'actions/telemetry_actions';
|
||||
|
||||
import {getRoleForTrackFlow} from 'utils/utils';
|
||||
|
||||
import {InviteType} from './invite_as';
|
||||
import InviteView, {initializeInviteState} from './invite_view';
|
||||
import type {InviteState} from './invite_view';
|
||||
@ -73,6 +71,7 @@ export type Props = {
|
||||
channelToInvite?: Channel;
|
||||
initialValue?: string;
|
||||
inviteAsGuest?: boolean;
|
||||
roleForTrackFlow: {started_by_role: string};
|
||||
}
|
||||
|
||||
export const View = {
|
||||
@ -162,12 +161,11 @@ export class InvitationModal extends React.PureComponent<Props, State> {
|
||||
if (!this.props.currentTeam) {
|
||||
return;
|
||||
}
|
||||
const roleForTrackFlow = getRoleForTrackFlow();
|
||||
const inviteAs = this.state.invite.inviteType;
|
||||
if (inviteAs === InviteType.MEMBER && this.props.isCloud) {
|
||||
trackEvent('cloud_invite_users', 'click_send_invitations', {num_invitations: this.state.invite.usersEmails.length, ...roleForTrackFlow});
|
||||
trackEvent('cloud_invite_users', 'click_send_invitations', {num_invitations: this.state.invite.usersEmails.length, ...this.props.roleForTrackFlow});
|
||||
}
|
||||
trackEvent('invite_users', 'click_invite', roleForTrackFlow);
|
||||
trackEvent('invite_users', 'click_invite', this.props.roleForTrackFlow);
|
||||
|
||||
const users: UserProfile[] = [];
|
||||
const emails: string[] = [];
|
||||
|
@ -9,9 +9,8 @@ import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import deepFreeze from 'mattermost-redux/utils/deep_freeze';
|
||||
|
||||
import store from 'stores/redux_store';
|
||||
|
||||
import {mountWithThemedIntl} from 'tests/helpers/themed-intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {SelfHostedProducts} from 'utils/constants';
|
||||
import {TestHelper as TH} from 'utils/test_helper';
|
||||
import {generateId} from 'utils/utils';
|
||||
@ -120,7 +119,7 @@ describe('InviteView', () => {
|
||||
},
|
||||
};
|
||||
|
||||
store.getState = () => (state);
|
||||
const store = mockStore(state);
|
||||
|
||||
beforeEach(() => {
|
||||
props = defaultProps;
|
||||
|
@ -5,6 +5,7 @@ import classNames from 'classnames';
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
@ -76,6 +77,9 @@ export type Props = InviteState & {
|
||||
}
|
||||
|
||||
export default function InviteView(props: Props) {
|
||||
const trackFlowRole = useSelector(getTrackFlowRole);
|
||||
const roleForTrackFlow = useSelector(getRoleForTrackFlow);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.currentTeam.invite_id) {
|
||||
props.regenerateTeamInviteId(props.currentTeam.id);
|
||||
@ -85,11 +89,11 @@ export default function InviteView(props: Props) {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const inviteURL = useMemo(() => {
|
||||
return `${getSiteURL()}/signup_user_complete/?id=${props.currentTeam.invite_id}&md=link&sbr=${getTrackFlowRole()}`;
|
||||
}, [props.currentTeam.invite_id]);
|
||||
return `${getSiteURL()}/signup_user_complete/?id=${props.currentTeam.invite_id}&md=link&sbr=${trackFlowRole}`;
|
||||
}, [props.currentTeam.invite_id, trackFlowRole]);
|
||||
|
||||
const copyText = useCopyText({
|
||||
trackCallback: () => trackEvent(getAnalyticsCategory(props.isAdmin), 'click_copy_invite_link', {...getRoleForTrackFlow(), ...getSourceForTrackFlow()}),
|
||||
trackCallback: () => trackEvent(getAnalyticsCategory(props.isAdmin), 'click_copy_invite_link', {...roleForTrackFlow, ...getSourceForTrackFlow()}),
|
||||
text: inviteURL,
|
||||
});
|
||||
|
||||
|
@ -4,11 +4,12 @@
|
||||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import {createChannel} from 'mattermost-redux/actions/channels';
|
||||
import Permissions from 'mattermost-redux/constants/permissions';
|
||||
|
||||
import {
|
||||
render,
|
||||
renderWithContext,
|
||||
screen,
|
||||
userEvent,
|
||||
@ -23,97 +24,89 @@ import NewChannelModal from './new_channel_modal';
|
||||
|
||||
jest.mock('mattermost-redux/actions/channels');
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
let mockState: GlobalState;
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
useDispatch: () => mockDispatch,
|
||||
}));
|
||||
|
||||
describe('components/new_channel_modal', () => {
|
||||
beforeEach(() => {
|
||||
mockState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
const initialState: DeepPartial<GlobalState> = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
channels: {
|
||||
currentChannelId: 'current_channel_id',
|
||||
channels: {
|
||||
currentChannelId: 'current_channel_id',
|
||||
channels: {
|
||||
current_channel_id: {
|
||||
id: 'current_channel_id',
|
||||
display_name: 'Current channel',
|
||||
name: 'current_channel',
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
current_channel_id: [
|
||||
'channel_user',
|
||||
'channel_admin',
|
||||
],
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'current_team_id',
|
||||
myMembers: {
|
||||
current_team_id: {
|
||||
roles: 'team_user team_admin',
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
current_team_id: {
|
||||
id: 'current_team_id',
|
||||
description: 'Curent team description',
|
||||
name: 'current-team',
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'current_user_id',
|
||||
profiles: {
|
||||
current_user_id: {roles: 'system_admin system_user'},
|
||||
current_channel_id: {
|
||||
id: 'current_channel_id',
|
||||
display_name: 'Current channel',
|
||||
name: 'current_channel',
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
channel_admin: {
|
||||
permissions: [],
|
||||
},
|
||||
channel_user: {
|
||||
permissions: [],
|
||||
},
|
||||
team_admin: {
|
||||
permissions: [],
|
||||
},
|
||||
team_user: {
|
||||
permissions: [
|
||||
Permissions.CREATE_PRIVATE_CHANNEL,
|
||||
],
|
||||
},
|
||||
system_admin: {
|
||||
permissions: [
|
||||
Permissions.CREATE_PUBLIC_CHANNEL,
|
||||
],
|
||||
},
|
||||
system_user: {
|
||||
permissions: [],
|
||||
},
|
||||
current_channel_id: new Set([
|
||||
'channel_user',
|
||||
'channel_admin',
|
||||
]),
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'current_team_id',
|
||||
myMembers: {
|
||||
current_team_id: {
|
||||
roles: 'team_user team_admin',
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
current_team_id: {
|
||||
id: 'current_team_id',
|
||||
description: 'Curent team description',
|
||||
name: 'current-team',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
plugins: {focalboard: {id: suitePluginIds.focalboard}},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
} as unknown as GlobalState;
|
||||
});
|
||||
users: {
|
||||
currentUserId: 'current_user_id',
|
||||
profiles: {
|
||||
current_user_id: {roles: 'system_admin system_user'},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
roles: {
|
||||
channel_admin: {
|
||||
permissions: [],
|
||||
},
|
||||
channel_user: {
|
||||
permissions: [],
|
||||
},
|
||||
team_admin: {
|
||||
permissions: [],
|
||||
},
|
||||
team_user: {
|
||||
permissions: [
|
||||
Permissions.CREATE_PRIVATE_CHANNEL,
|
||||
],
|
||||
},
|
||||
system_admin: {
|
||||
permissions: [
|
||||
Permissions.CREATE_PUBLIC_CHANNEL,
|
||||
],
|
||||
},
|
||||
system_user: {
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
plugins: {focalboard: {id: suitePluginIds.focalboard}},
|
||||
},
|
||||
};
|
||||
|
||||
test('should match component state with given props', () => {
|
||||
render(<NewChannelModal/>);
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
const heading = screen.getByRole('heading');
|
||||
expect(heading).toBeInTheDocument();
|
||||
@ -172,8 +165,9 @@ describe('components/new_channel_modal', () => {
|
||||
test('should handle display name change', () => {
|
||||
const value = 'Channel name';
|
||||
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Change display name
|
||||
@ -196,8 +190,9 @@ describe('components/new_channel_modal', () => {
|
||||
|
||||
const url = 'channel-name-new';
|
||||
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Change display name
|
||||
@ -235,8 +230,9 @@ describe('components/new_channel_modal', () => {
|
||||
});
|
||||
|
||||
test('should handle type changes', () => {
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Change type to private
|
||||
@ -261,8 +257,9 @@ describe('components/new_channel_modal', () => {
|
||||
test('should handle purpose changes', () => {
|
||||
const value = 'Purpose';
|
||||
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Change purpose
|
||||
@ -277,8 +274,9 @@ describe('components/new_channel_modal', () => {
|
||||
});
|
||||
|
||||
test('should enable confirm button when having valid display name, url and type', () => {
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Confirm button should be disabled
|
||||
@ -304,8 +302,9 @@ describe('components/new_channel_modal', () => {
|
||||
});
|
||||
|
||||
test('should disable confirm button when display name in error', () => {
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Change display name
|
||||
@ -333,8 +332,9 @@ describe('components/new_channel_modal', () => {
|
||||
});
|
||||
|
||||
test('should disable confirm button when url in error', () => {
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Change display name
|
||||
@ -369,8 +369,9 @@ describe('components/new_channel_modal', () => {
|
||||
});
|
||||
|
||||
test('should disable confirm button when server error', async () => {
|
||||
render(
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Confirm button should be disabled
|
||||
@ -406,6 +407,7 @@ describe('components/new_channel_modal', () => {
|
||||
|
||||
renderWithContext(
|
||||
<NewChannelModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
// Confirm button should be disabled
|
||||
|
@ -5,7 +5,10 @@ import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {OverlayTrigger as BaseOverlayTrigger} from 'react-bootstrap'; // eslint-disable-line no-restricted-imports
|
||||
import {FormattedMessage, IntlProvider} from 'react-intl';
|
||||
import {Provider as ReduxProvider} from 'react-redux';
|
||||
import type {Store} from 'redux';
|
||||
|
||||
import testConfigureStore from 'packages/mattermost-redux/test/test_store';
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
|
||||
import OverlayTrigger from './overlay_trigger';
|
||||
@ -13,6 +16,8 @@ import OverlayTrigger from './overlay_trigger';
|
||||
describe('OverlayTrigger', () => {
|
||||
const testId = 'test.value';
|
||||
|
||||
let store: Store;
|
||||
|
||||
const intlProviderProps = {
|
||||
defaultLocale: 'en',
|
||||
locale: 'en',
|
||||
@ -33,6 +38,7 @@ describe('OverlayTrigger', () => {
|
||||
let originalConsoleError: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
store = testConfigureStore();
|
||||
originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
});
|
||||
@ -43,11 +49,13 @@ describe('OverlayTrigger', () => {
|
||||
|
||||
test('base OverlayTrigger should fail to pass intl to overlay', () => {
|
||||
const wrapper = mount(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<BaseOverlayTrigger {...baseProps}>
|
||||
<span/>
|
||||
</BaseOverlayTrigger>
|
||||
</IntlProvider>,
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<BaseOverlayTrigger {...baseProps}>
|
||||
<span/>
|
||||
</BaseOverlayTrigger>
|
||||
</IntlProvider>
|
||||
</ReduxProvider>,
|
||||
);
|
||||
|
||||
// console.error will have been called by FormattedMessage because its intl context is missing
|
||||
@ -58,11 +66,13 @@ describe('OverlayTrigger', () => {
|
||||
|
||||
test('custom OverlayTrigger should pass intl to overlay', () => {
|
||||
const wrapper = mount(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...baseProps}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>,
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...baseProps}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>
|
||||
</ReduxProvider>,
|
||||
);
|
||||
|
||||
const overlay = mount(wrapper.find(BaseOverlayTrigger).prop('overlay'));
|
||||
@ -79,11 +89,13 @@ describe('OverlayTrigger', () => {
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...props}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>,
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...props}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>
|
||||
</ReduxProvider>,
|
||||
);
|
||||
|
||||
expect(ref.current).toBe(wrapper.find(BaseOverlayTrigger).instance());
|
||||
@ -104,11 +116,13 @@ describe('OverlayTrigger', () => {
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...props}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>,
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...props}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>
|
||||
</ReduxProvider>,
|
||||
);
|
||||
|
||||
// Dive into the react-bootstrap internals to find our overlay
|
||||
@ -143,11 +157,13 @@ describe('OverlayTrigger', () => {
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...props}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>,
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<OverlayTrigger {...props}>
|
||||
<span/>
|
||||
</OverlayTrigger>
|
||||
</IntlProvider>
|
||||
</ReduxProvider>,
|
||||
);
|
||||
|
||||
// Dive into the react-bootstrap internals to find our overlay
|
||||
|
@ -6,9 +6,7 @@ import {OverlayTrigger as OriginalOverlayTrigger} from 'react-bootstrap'; // esl
|
||||
import type {OverlayTriggerProps} from 'react-bootstrap';
|
||||
import {IntlContext} from 'react-intl';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import store from 'stores/redux_store';
|
||||
import {Provider, useStore} from 'react-redux';
|
||||
|
||||
export type BaseOverlayTrigger = OriginalOverlayTrigger & {
|
||||
hide: () => void;
|
||||
@ -25,6 +23,8 @@ type Props = OverlayTriggerProps & {
|
||||
const OverlayTrigger = React.forwardRef((props: Props, ref?: React.Ref<OriginalOverlayTrigger>) => {
|
||||
const {overlay, disabled, ...otherProps} = props;
|
||||
|
||||
const store = useStore();
|
||||
|
||||
// The overlay is rendered outside of the regular React context, and our version react-bootstrap can't forward
|
||||
// that context itself, so we have to manually forward the react-intl context to this component's child.
|
||||
const OverlayWrapper = ({intl, ...overlayProps}: {intl: IntlShape}) => (
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {render, screen} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import PostEmoji from './post_emoji';
|
||||
|
||||
@ -14,14 +14,14 @@ describe('PostEmoji', () => {
|
||||
};
|
||||
|
||||
test('should render image when imageUrl is provided', () => {
|
||||
render(<PostEmoji {...baseProps}/>);
|
||||
renderWithContext(<PostEmoji {...baseProps}/>);
|
||||
|
||||
expect(screen.queryByTestId('postEmoji.:' + baseProps.name + ':')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('postEmoji.:' + baseProps.name + ':')).toHaveStyle(`backgroundImage: url(${baseProps.imageUrl})}`);
|
||||
});
|
||||
|
||||
test('should render shortcode text within span when imageUrl is provided', () => {
|
||||
render(<PostEmoji {...baseProps}/>);
|
||||
renderWithContext(<PostEmoji {...baseProps}/>);
|
||||
|
||||
expect(screen.queryByTestId('postEmoji.:' + baseProps.name + ':')).toHaveTextContent(`:${baseProps.name}:`);
|
||||
});
|
||||
@ -32,7 +32,7 @@ describe('PostEmoji', () => {
|
||||
imageUrl: '',
|
||||
};
|
||||
|
||||
render(<PostEmoji {...props}/>);
|
||||
renderWithContext(<PostEmoji {...props}/>);
|
||||
|
||||
expect(screen.queryByTestId('postEmoji.:' + baseProps.name + ':')).not.toBeInTheDocument();
|
||||
expect(screen.getByText(`:${props.name}:`)).toBeInTheDocument();
|
||||
|
@ -4,7 +4,7 @@
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
import {render, screen} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import PostProfilePicture from './post_profile_picture';
|
||||
@ -31,7 +31,7 @@ describe('components/PostProfilePicture', () => {
|
||||
|
||||
test('no status and post icon override specified, default props', () => {
|
||||
const props: Props = baseProps;
|
||||
render(
|
||||
renderWithContext(
|
||||
<PostProfilePicture {...props}/>,
|
||||
);
|
||||
|
||||
@ -47,7 +47,7 @@ describe('components/PostProfilePicture', () => {
|
||||
status: 'away',
|
||||
postIconOverrideURL: 'http://example.com/image.png',
|
||||
};
|
||||
render(
|
||||
renderWithContext(
|
||||
<PostProfilePicture {...props}/>,
|
||||
);
|
||||
|
||||
|
@ -1,194 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/QuickInput should render clear button with customized tooltip component 1`] = `
|
||||
<div
|
||||
className="input-clear visible"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="InputClearTooltip"
|
||||
>
|
||||
<span>
|
||||
Custom
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
id="InputClearTooltip"
|
||||
intl={null}
|
||||
>
|
||||
<span>
|
||||
Custom
|
||||
</span>
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="input-clear-x"
|
||||
onBlur={[Function]}
|
||||
onClick={null}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close-circle"
|
||||
/>
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/QuickInput should render clear button with customized tooltip text 1`] = `
|
||||
<div
|
||||
className="input-clear visible"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="InputClearTooltip"
|
||||
>
|
||||
Custom
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
id="InputClearTooltip"
|
||||
intl={null}
|
||||
>
|
||||
Custom
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="input-clear-x"
|
||||
onBlur={[Function]}
|
||||
onClick={null}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close-circle"
|
||||
/>
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/QuickInput should render clear button with default tooltip text 1`] = `
|
||||
<div
|
||||
className="input-clear visible"
|
||||
onMouseDown={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<Tooltip
|
||||
id="InputClearTooltip"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Clear"
|
||||
id="input.clear"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<OverlayTrigger
|
||||
defaultOverlayShown={false}
|
||||
delayShow={400}
|
||||
overlay={
|
||||
<OverlayWrapper
|
||||
id="InputClearTooltip"
|
||||
intl={null}
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Clear"
|
||||
id="input.clear"
|
||||
/>
|
||||
</OverlayWrapper>
|
||||
}
|
||||
placement="bottom"
|
||||
trigger={
|
||||
Array [
|
||||
"hover",
|
||||
"focus",
|
||||
]
|
||||
}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="input-clear-x"
|
||||
onBlur={[Function]}
|
||||
onClick={null}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<i
|
||||
className="icon icon-close-circle"
|
||||
/>
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
`;
|
@ -1,9 +1,10 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import {QuickInput} from './quick_input';
|
||||
|
||||
describe('components/QuickInput', () => {
|
||||
@ -14,16 +15,16 @@ describe('components/QuickInput', () => {
|
||||
['when value undefined', {clearable: true, onClear: () => {}}],
|
||||
['when value empty', {value: '', clearable: true, onClear: () => {}}],
|
||||
])('should not render clear button', (description, props) => {
|
||||
const wrapper = mount(
|
||||
renderWithContext(
|
||||
<QuickInput {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.input-clear').exists()).toBe(false);
|
||||
expect(screen.queryByTestId('input-clear')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('should render clear button', () => {
|
||||
test('with default tooltip text', () => {
|
||||
const wrapper = mount(
|
||||
renderWithContext(
|
||||
<QuickInput
|
||||
value='mock'
|
||||
clearable={true}
|
||||
@ -31,11 +32,11 @@ describe('components/QuickInput', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.input-clear')).toMatchSnapshot();
|
||||
expect(screen.queryByTestId('input-clear')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('with customized tooltip text', () => {
|
||||
const wrapper = mount(
|
||||
renderWithContext(
|
||||
<QuickInput
|
||||
value='mock'
|
||||
clearable={true}
|
||||
@ -44,11 +45,11 @@ describe('components/QuickInput', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.input-clear')).toMatchSnapshot();
|
||||
expect(screen.queryByTestId('input-clear')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('with customized tooltip component', () => {
|
||||
const wrapper = mount(
|
||||
renderWithContext(
|
||||
<QuickInput
|
||||
value='mock'
|
||||
clearable={true}
|
||||
@ -59,7 +60,7 @@ describe('components/QuickInput', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.input-clear')).toMatchSnapshot();
|
||||
expect(screen.queryByTestId('input-clear')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -71,7 +72,7 @@ describe('components/QuickInput', () => {
|
||||
return <div/>;
|
||||
}
|
||||
}
|
||||
const wrapper = mount(
|
||||
const {rerender} = renderWithContext(
|
||||
<QuickInput
|
||||
value='mock'
|
||||
clearable={true}
|
||||
@ -80,11 +81,20 @@ describe('components/QuickInput', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.setProps({onClear: () => wrapper.setProps({value: ''})});
|
||||
expect(wrapper.find('.input-clear').exists()).toBe(true);
|
||||
expect(screen.queryByTestId('input-clear')).toBeInTheDocument();
|
||||
|
||||
wrapper.find('.input-clear').simulate('mousedown');
|
||||
expect(wrapper.find('.input-clear').exists()).toBe(false);
|
||||
userEvent.click(screen.getByTestId('input-clear'));
|
||||
|
||||
rerender(
|
||||
<QuickInput
|
||||
value=''
|
||||
clearable={true}
|
||||
onClear={() => {}}
|
||||
inputComponent={MockComp}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('input-clear')).not.toBeInTheDocument();
|
||||
expect(focusFn).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
@ -200,9 +200,11 @@ export class QuickInput extends React.PureComponent<Props> {
|
||||
{inputElement}
|
||||
{showClearButton &&
|
||||
<div
|
||||
data-testid='input-clear'
|
||||
className={classNames(clearClassName, 'input-clear visible')}
|
||||
onMouseDown={this.onClear}
|
||||
onTouchEnd={this.onClear}
|
||||
role='button'
|
||||
>
|
||||
<OverlayTrigger
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
|
@ -45,6 +45,8 @@ exports[`components/search_bar/SearchBar should match snapshot with search 1`] =
|
||||
/>
|
||||
<div
|
||||
class="input-clear visible"
|
||||
data-testid="input-clear"
|
||||
role="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -115,6 +117,8 @@ exports[`components/search_bar/SearchBar should match snapshot with search, with
|
||||
/>
|
||||
<div
|
||||
class="input-clear visible"
|
||||
data-testid="input-clear"
|
||||
role="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
|
@ -1,10 +1,10 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {screen} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {BrowserRouter as Router} from 'react-router-dom';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import TeamButton from './team_button';
|
||||
|
||||
@ -35,14 +35,12 @@ describe('components/TeamSidebar/TeamButton', () => {
|
||||
unread: true,
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<Router>
|
||||
<TeamButton {...props}/>
|
||||
</Router>,
|
||||
renderWithContext(
|
||||
<TeamButton {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.unread-badge').exists()).toBe(true);
|
||||
expect(wrapper.find('.team-container.unread').exists()).toBe(true);
|
||||
expect(screen.queryByTestId('team-badge-')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('team-container-')).toHaveClass('unread');
|
||||
});
|
||||
|
||||
it('should hide unread badge and set no class when unread in a product', () => {
|
||||
@ -53,14 +51,12 @@ describe('components/TeamSidebar/TeamButton', () => {
|
||||
isInProduct: true,
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<Router>
|
||||
<TeamButton {...props}/>
|
||||
</Router>,
|
||||
renderWithContext(
|
||||
<TeamButton {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.unread-badge').exists()).toBe(false);
|
||||
expect(wrapper.find('.team-container.unread').exists()).toBe(false);
|
||||
expect(screen.queryByTestId('team-badge-')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('team-container-')).not.toHaveClass('unread');
|
||||
});
|
||||
|
||||
it('should show mentions badge and set class when mentions in channels', () => {
|
||||
@ -71,14 +67,12 @@ describe('components/TeamSidebar/TeamButton', () => {
|
||||
mentions: 1,
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<Router>
|
||||
<TeamButton {...props}/>
|
||||
</Router>,
|
||||
renderWithContext(
|
||||
<TeamButton {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.badge.badge-max-number').exists()).toBe(true);
|
||||
expect(wrapper.find('.team-container.unread').exists()).toBe(true);
|
||||
expect(screen.queryByTestId('team-badge-')).toHaveClass('badge-max-number');
|
||||
expect(screen.getByTestId('team-container-')).toHaveClass('unread');
|
||||
});
|
||||
|
||||
it('should hide mentions badge and set no class when mentions in product', () => {
|
||||
@ -90,13 +84,11 @@ describe('components/TeamSidebar/TeamButton', () => {
|
||||
isInProduct: true,
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<Router>
|
||||
<TeamButton {...props}/>
|
||||
</Router>,
|
||||
renderWithContext(
|
||||
<TeamButton {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('.badge.badge-max-number').exists()).toBe(false);
|
||||
expect(wrapper.find('.team-container.unread').exists()).toBe(false);
|
||||
expect(screen.queryByTestId('team-badge-')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('team-container-')).not.toHaveClass('unread');
|
||||
});
|
||||
});
|
||||
|
@ -89,7 +89,10 @@ export default function TeamButton({
|
||||
teamClass = 'unread';
|
||||
|
||||
badge = (
|
||||
<span className={'unread-badge'}/>
|
||||
<span
|
||||
data-testid={'team-badge-' + teamId}
|
||||
className={'unread-badge'}
|
||||
/>
|
||||
);
|
||||
} else if (isNotCreateTeamButton) {
|
||||
teamClass = '';
|
||||
@ -115,7 +118,12 @@ export default function TeamButton({
|
||||
});
|
||||
|
||||
badge = (
|
||||
<span className={classNames('badge badge-max-number pull-right small', {urgent: otherProps.hasUrgent})}>{mentions > 99 ? '99+' : mentions}</span>
|
||||
<span
|
||||
data-testid={'team-badge-' + teamId}
|
||||
className={classNames('badge badge-max-number pull-right small', {urgent: otherProps.hasUrgent})}
|
||||
>
|
||||
{mentions > 99 ? '99+' : mentions}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -206,6 +214,7 @@ export default function TeamButton({
|
||||
</Draggable>
|
||||
) : (
|
||||
<div
|
||||
data-testid={'team-container-' + teamId}
|
||||
className={`team-container ${teamClass}`}
|
||||
>
|
||||
{teamButton}
|
||||
|
@ -2,10 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import type {ReactWrapper} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
import Toast from './toast';
|
||||
@ -45,12 +44,14 @@ describe('components/Toast', () => {
|
||||
test('should dismiss', () => {
|
||||
defaultProps.onDismiss = jest.fn();
|
||||
|
||||
const wrapper: ReactWrapper<any, any, React.Component> = mountWithIntl(<Toast {... defaultProps}><span>{'child'}</span></Toast>);
|
||||
const toast = wrapper.find(Toast).instance();
|
||||
renderWithContext(
|
||||
<Toast {... defaultProps}>
|
||||
<span>{'child'}</span>
|
||||
</Toast>,
|
||||
);
|
||||
|
||||
screen.getByTestId('dismissToast').click();
|
||||
|
||||
if (toast instanceof Toast) {
|
||||
toast.handleDismiss();
|
||||
}
|
||||
expect(defaultProps.onDismiss).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ import type {Channel, ChannelMembership} from '@mattermost/types/channels';
|
||||
import type {Theme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ChannelHeaderPlug from 'plugins/channel_header_plug/channel_header_plug';
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import type {PluginComponent} from 'types/store/plugins';
|
||||
|
||||
@ -22,8 +22,8 @@ describe('plugins/ChannelHeaderPlug', () => {
|
||||
tooltipText: 'some tooltip text',
|
||||
} as PluginComponent;
|
||||
|
||||
test('should match snapshot with no extended component', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
test('should not render anything with no extended component', () => {
|
||||
const {asFragment} = renderWithContext(
|
||||
<ChannelHeaderPlug
|
||||
components={[]}
|
||||
channel={{} as Channel}
|
||||
@ -40,11 +40,11 @@ describe('plugins/ChannelHeaderPlug', () => {
|
||||
shouldShowAppBar={false}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with one extended component', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
const {asFragment} = renderWithContext(
|
||||
<ChannelHeaderPlug
|
||||
components={[testPlug]}
|
||||
channel={{} as Channel}
|
||||
@ -61,11 +61,11 @@ describe('plugins/ChannelHeaderPlug', () => {
|
||||
shouldShowAppBar={false}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with six extended components', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
const {asFragment} = renderWithContext(
|
||||
<ChannelHeaderPlug
|
||||
components={[
|
||||
testPlug,
|
||||
@ -98,11 +98,11 @@ describe('plugins/ChannelHeaderPlug', () => {
|
||||
shouldShowAppBar={false}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when the App Bar is visible', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
test('should not render anything when the App Bar is visible', () => {
|
||||
const {asFragment} = renderWithContext(
|
||||
<ChannelHeaderPlug
|
||||
components={[
|
||||
testPlug,
|
||||
@ -124,6 +124,6 @@ describe('plugins/ChannelHeaderPlug', () => {
|
||||
shouldShowAppBar={true}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ import {getPost as getPostAction} from 'mattermost-redux/actions/posts';
|
||||
import {getTeamByName as getTeamByNameAction} from 'mattermost-redux/actions/teams';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {Preferences, General} from 'mattermost-redux/constants';
|
||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||
import {
|
||||
getChannel,
|
||||
getChannelsNameMapInTeam,
|
||||
@ -1630,8 +1631,7 @@ const TrackFlowRoles: Record<string, string> = {
|
||||
su: General.SYSTEM_USER_ROLE,
|
||||
};
|
||||
|
||||
export function getTrackFlowRole() {
|
||||
const state = store.getState();
|
||||
export function getTrackFlowRole(state: GlobalState) {
|
||||
let trackFlowRole = 'su';
|
||||
|
||||
if (isFirstAdmin(state)) {
|
||||
@ -1643,11 +1643,15 @@ export function getTrackFlowRole() {
|
||||
return trackFlowRole;
|
||||
}
|
||||
|
||||
export function getRoleForTrackFlow() {
|
||||
const startedByRole = TrackFlowRoles[getTrackFlowRole()];
|
||||
export const getRoleForTrackFlow = createSelector(
|
||||
'getRoleForTrackFlow',
|
||||
getTrackFlowRole,
|
||||
(trackFlowRole) => {
|
||||
const startedByRole = TrackFlowRoles[trackFlowRole];
|
||||
|
||||
return {started_by_role: startedByRole};
|
||||
}
|
||||
return {started_by_role: startedByRole};
|
||||
},
|
||||
);
|
||||
|
||||
export function getSbr() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
20
webapp/package-lock.json
generated
20
webapp/package-lock.json
generated
@ -210,7 +210,6 @@
|
||||
"jest-cli": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-junit": "16.0.0",
|
||||
"jest-styled-components": "7.2.0",
|
||||
"jest-watch-typeahead": "2.2.2",
|
||||
"mmjstool": "github:mattermost/mattermost-utilities#73e61d2ede0ebf802492df4cfbac481d35efed54",
|
||||
"nock": "13.2.8",
|
||||
@ -250,11 +249,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
@ -15904,20 +15898,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-styled-components": {
|
||||
"version": "7.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"styled-components": ">= 5"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-util": {
|
||||
"version": "28.1.3",
|
||||
"dev": true,
|
||||
|
Loading…
Reference in New Issue
Block a user