From 619655f5674e5472bcd68516b825819096d5696d Mon Sep 17 00:00:00 2001 From: Harshil Sharma <18575143+harshilsharma63@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:13:42 +0530 Subject: [PATCH] Draft link activation (#30547) * activated draft LHS item on scheduled post tab * Added tests * removed unused snapshot --------- Co-authored-by: Mattermost Build --- .../__snapshots__/drafts_link.test.tsx.snap | 37 --- .../drafts/drafts_link/drafts_link.test.tsx | 238 +++++++++++++++++- .../drafts/drafts_link/drafts_link.tsx | 7 +- 3 files changed, 233 insertions(+), 49 deletions(-) delete mode 100644 webapp/channels/src/components/drafts/drafts_link/__snapshots__/drafts_link.test.tsx.snap diff --git a/webapp/channels/src/components/drafts/drafts_link/__snapshots__/drafts_link.test.tsx.snap b/webapp/channels/src/components/drafts/drafts_link/__snapshots__/drafts_link.test.tsx.snap deleted file mode 100644 index e9da093d4d..0000000000 --- a/webapp/channels/src/components/drafts/drafts_link/__snapshots__/drafts_link.test.tsx.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/drafts/drafts_link should match snapshot 1`] = ` - - - -`; diff --git a/webapp/channels/src/components/drafts/drafts_link/drafts_link.test.tsx b/webapp/channels/src/components/drafts/drafts_link/drafts_link.test.tsx index 08382c7bff..769365d1da 100644 --- a/webapp/channels/src/components/drafts/drafts_link/drafts_link.test.tsx +++ b/webapp/channels/src/components/drafts/drafts_link/drafts_link.test.tsx @@ -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( - +jest.mock('mattermost-redux/actions/scheduled_posts', () => ({ + fetchTeamScheduledPosts: jest.fn(() => ({type: 'MOCK_FETCH_SCHEDULED_POSTS'})), +})); + +// Base state with all required properties +const baseState: DeepPartial = { + 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( + + - , + + , + 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 = { + ...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 = { + ...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 = { + ...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 = { + ...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 = { + ...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'); }); }); diff --git a/webapp/channels/src/components/drafts/drafts_link/drafts_link.tsx b/webapp/channels/src/components/drafts/drafts_link/drafts_link.tsx index d6713e4eed..3a14b0d50d 100644 --- a/webapp/channels/src/components/drafts/drafts_link/drafts_link.tsx +++ b/webapp/channels/src/components/drafts/drafts_link/drafts_link.tsx @@ -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} >