* activated draft LHS item on scheduled post tab

* Added tests

* removed unused snapshot

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Harshil Sharma
2025-03-28 15:13:42 +05:30
коммит произвёл GitHub
родитель 6c8235d031
Коммит 619655f567
3 изменённых файлов: 233 добавлений и 49 удалений

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

@@ -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.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
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';
describe('components/drafts/drafts_link', () => {
it('should match snapshot', () => {
const store = mockStore();
// Mock the actions that are dispatched
jest.mock('actions/views/drafts', () => ({
getDrafts: jest.fn(() => ({type: 'MOCK_GET_DRAFTS'})),
}));
const wrapper = shallow(
<Provider store={store}>
jest.mock('mattermost-redux/actions/scheduled_posts', () => ({
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/>
</Provider>,
</Route>
</MemoryRouter>,
state,
);
};
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'],
);
expect(wrapper).toMatchSnapshot();
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.
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 {useSelector, useDispatch} from 'react-redux';
import {NavLink, useRouteMatch} from 'react-router-dom';
@@ -63,7 +63,9 @@ function DraftsLink() {
const isDraftUrlMatch = useRouteMatch('/:team/drafts');
const isScheduledPostUrlMatch = useRouteMatch('/:team/' + SCHEDULED_POST_URL_SUFFIX);
const urlMatches = isDraftUrlMatch || isScheduledPostUrlMatch;
const urlMatches = Boolean(isDraftUrlMatch || isScheduledPostUrlMatch);
const isNavLinkActive = useCallback(() => urlMatches, [urlMatches]);
useEffect(() => {
if (syncedDraftsAllowedAndEnabled) {
@@ -123,6 +125,7 @@ function DraftsLink() {
draggable='false'
className='SidebarLink sidebar-item'
tabIndex={0}
isActive={isNavLinkActive}
>
<i
data-testid='sendPostIcon'