Draft link activation (#30547)
* activated draft LHS item on scheduled post tab * Added tests * removed unused snapshot --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6c8235d031
Коммит
619655f567
@@ -1,37 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`components/drafts/drafts_link should match snapshot 1`] = `
|
|
||||||
<ContextProvider
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"store": Object {
|
|
||||||
"clearActions": [Function],
|
|
||||||
"dispatch": [Function],
|
|
||||||
"getActions": [Function],
|
|
||||||
"getState": [Function],
|
|
||||||
"replaceReducer": [Function],
|
|
||||||
"subscribe": [Function],
|
|
||||||
},
|
|
||||||
"subscription": Subscription {
|
|
||||||
"handleChangeWrapper": [Function],
|
|
||||||
"listeners": Object {
|
|
||||||
"notify": [Function],
|
|
||||||
},
|
|
||||||
"onStateChange": [Function],
|
|
||||||
"parentSub": undefined,
|
|
||||||
"store": Object {
|
|
||||||
"clearActions": [Function],
|
|
||||||
"dispatch": [Function],
|
|
||||||
"getActions": [Function],
|
|
||||||
"getState": [Function],
|
|
||||||
"replaceReducer": [Function],
|
|
||||||
"subscribe": [Function],
|
|
||||||
},
|
|
||||||
"unsubscribe": null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Memo(DraftsLink) />
|
|
||||||
</ContextProvider>
|
|
||||||
`;
|
|
||||||
@@ -1,23 +1,241 @@
|
|||||||
// 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 {shallow} from 'enzyme';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Provider} from 'react-redux';
|
import {MemoryRouter, Route} from 'react-router-dom';
|
||||||
|
|
||||||
import mockStore from 'tests/test_store';
|
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||||
|
|
||||||
|
import {renderWithContext, screen, waitFor} from 'tests/react_testing_utils';
|
||||||
|
import {SCHEDULED_POST_URL_SUFFIX} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import DraftsLink from './drafts_link';
|
import DraftsLink from './drafts_link';
|
||||||
|
|
||||||
describe('components/drafts/drafts_link', () => {
|
// Mock the actions that are dispatched
|
||||||
it('should match snapshot', () => {
|
jest.mock('actions/views/drafts', () => ({
|
||||||
const store = mockStore();
|
getDrafts: jest.fn(() => ({type: 'MOCK_GET_DRAFTS'})),
|
||||||
|
}));
|
||||||
|
|
||||||
const wrapper = shallow(
|
jest.mock('mattermost-redux/actions/scheduled_posts', () => ({
|
||||||
<Provider store={store}>
|
fetchTeamScheduledPosts: jest.fn(() => ({type: 'MOCK_FETCH_SCHEDULED_POSTS'})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Base state with all required properties
|
||||||
|
const baseState: DeepPartial<GlobalState> = {
|
||||||
|
entities: {
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
ScheduledPosts: 'true',
|
||||||
|
},
|
||||||
|
license: {
|
||||||
|
IsLicensed: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
myPreferences: {},
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
currentTeamId: 'team1',
|
||||||
|
teams: {
|
||||||
|
team1: TestHelper.getTeamMock({id: 'team1'}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
currentUserId: 'user1',
|
||||||
|
profiles: {
|
||||||
|
user1: TestHelper.getUserMock({id: 'user1'}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scheduledPosts: {},
|
||||||
|
channels: {
|
||||||
|
channels: {
|
||||||
|
channel_id_1: TestHelper.getChannelMock({id: 'channel_id_1', type: 'O'}),
|
||||||
|
},
|
||||||
|
channelsInTeam: {
|
||||||
|
team1: new Set(['channel_id_1']),
|
||||||
|
},
|
||||||
|
myMembers: {
|
||||||
|
channel_id_1: {channel_id: 'channel_id_1', user_id: 'user1'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
drafts: {
|
||||||
|
remotes: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to render the component with router
|
||||||
|
const renderWithRouter = (state: any, initialEntries = ['/team1/channels/town-square']) => {
|
||||||
|
return renderWithContext(
|
||||||
|
<MemoryRouter initialEntries={initialEntries}>
|
||||||
|
<Route path='/:team'>
|
||||||
<DraftsLink/>
|
<DraftsLink/>
|
||||||
</Provider>,
|
</Route>
|
||||||
|
</MemoryRouter>,
|
||||||
|
state,
|
||||||
);
|
);
|
||||||
expect(wrapper).toMatchSnapshot();
|
};
|
||||||
|
|
||||||
|
describe('components/drafts/drafts_link', () => {
|
||||||
|
it('should not render when no drafts or scheduled posts exist', () => {
|
||||||
|
renderWithRouter(baseState);
|
||||||
|
|
||||||
|
expect(screen.queryByText('Drafts')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render when drafts exist', () => {
|
||||||
|
const state: DeepPartial<GlobalState> = {
|
||||||
|
...baseState,
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
draft_draft1: {timestamp: new Date(), value: {message: 'Draft message', show: true, channelId: 'channel_id_1'}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(state);
|
||||||
|
|
||||||
|
expect(screen.getByText('Drafts')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('draftIcon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render when scheduled posts exist', () => {
|
||||||
|
const state: DeepPartial<GlobalState> = {
|
||||||
|
...baseState,
|
||||||
|
entities: {
|
||||||
|
...baseState.entities,
|
||||||
|
scheduledPosts: {
|
||||||
|
byTeamId: {
|
||||||
|
team1: ['scheduled_post1', 'scheduled_post2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(state);
|
||||||
|
|
||||||
|
expect(screen.getByText('Drafts')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('scheduledPostIcon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show scheduled posts badge when scheduled posts are disabled', () => {
|
||||||
|
const state = {
|
||||||
|
...baseState,
|
||||||
|
entities: {
|
||||||
|
...baseState.entities,
|
||||||
|
scheduledPosts: {
|
||||||
|
byTeamId: {
|
||||||
|
team1: ['scheduled_post1', 'scheduled_post2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
general: {
|
||||||
|
config: {
|
||||||
|
ScheduledPosts: 'false',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(state);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('scheduledPostIcon')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show scheduled posts badge when not licensed', () => {
|
||||||
|
const state = {
|
||||||
|
...baseState,
|
||||||
|
entities: {
|
||||||
|
...baseState.entities,
|
||||||
|
scheduledPosts: {
|
||||||
|
byTeamId: {
|
||||||
|
team1: ['scheduled_post1', 'scheduled_post2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
general: {
|
||||||
|
license: {
|
||||||
|
IsLicensed: 'false',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(state);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('scheduledPostIcon')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error indicator when scheduled posts have errors', () => {
|
||||||
|
const state: DeepPartial<GlobalState> = {
|
||||||
|
...baseState,
|
||||||
|
entities: {
|
||||||
|
...baseState.entities,
|
||||||
|
scheduledPosts: {
|
||||||
|
byTeamId: {
|
||||||
|
team1: ['scheduled_post1', 'scheduled_post2'],
|
||||||
|
},
|
||||||
|
errorsByTeamId: {
|
||||||
|
team1: ['scheduled_post1'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(state);
|
||||||
|
|
||||||
|
const badge = screen.getByTestId('scheduledPostIcon').closest('.scheduledPostBadge');
|
||||||
|
expect(badge).toHaveClass('persistent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch scheduled posts when component mounts', async () => {
|
||||||
|
const fetchTeamScheduledPosts = require('mattermost-redux/actions/scheduled_posts').fetchTeamScheduledPosts;
|
||||||
|
|
||||||
|
renderWithRouter(baseState);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(fetchTeamScheduledPosts).toHaveBeenCalledWith('team1', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be active when on drafts route', () => {
|
||||||
|
const state: DeepPartial<GlobalState> = {
|
||||||
|
...baseState,
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
draft_draft1: {timestamp: new Date(), value: {message: 'Draft message', show: true, channelId: 'channel_id_1'}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(
|
||||||
|
state,
|
||||||
|
['/team1/drafts'],
|
||||||
|
);
|
||||||
|
|
||||||
|
const navLink = screen.getByText('Drafts').closest('a');
|
||||||
|
expect(navLink).toHaveClass('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be active when on scheduled posts route', () => {
|
||||||
|
const state: DeepPartial<GlobalState> = {
|
||||||
|
...baseState,
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
draft_draft1: {timestamp: new Date(), value: {message: 'Draft message', show: true, channelId: 'channel_id_1'}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(
|
||||||
|
state,
|
||||||
|
[`/team1/${SCHEDULED_POST_URL_SUFFIX}`],
|
||||||
|
);
|
||||||
|
|
||||||
|
const navLink = screen.getByText('Drafts').closest('a');
|
||||||
|
expect(navLink).toHaveClass('active');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, {memo, useEffect, useMemo, useRef} from 'react';
|
import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {useSelector, useDispatch} from 'react-redux';
|
import {useSelector, useDispatch} from 'react-redux';
|
||||||
import {NavLink, useRouteMatch} from 'react-router-dom';
|
import {NavLink, useRouteMatch} from 'react-router-dom';
|
||||||
@@ -63,7 +63,9 @@ function DraftsLink() {
|
|||||||
const isDraftUrlMatch = useRouteMatch('/:team/drafts');
|
const isDraftUrlMatch = useRouteMatch('/:team/drafts');
|
||||||
const isScheduledPostUrlMatch = useRouteMatch('/:team/' + SCHEDULED_POST_URL_SUFFIX);
|
const isScheduledPostUrlMatch = useRouteMatch('/:team/' + SCHEDULED_POST_URL_SUFFIX);
|
||||||
|
|
||||||
const urlMatches = isDraftUrlMatch || isScheduledPostUrlMatch;
|
const urlMatches = Boolean(isDraftUrlMatch || isScheduledPostUrlMatch);
|
||||||
|
|
||||||
|
const isNavLinkActive = useCallback(() => urlMatches, [urlMatches]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (syncedDraftsAllowedAndEnabled) {
|
if (syncedDraftsAllowedAndEnabled) {
|
||||||
@@ -123,6 +125,7 @@ function DraftsLink() {
|
|||||||
draggable='false'
|
draggable='false'
|
||||||
className='SidebarLink sidebar-item'
|
className='SidebarLink sidebar-item'
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
isActive={isNavLinkActive}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
data-testid='sendPostIcon'
|
data-testid='sendPostIcon'
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user