Hide Download Apps link when running in Desktop app (#36614)

* Hide Download Apps UI when running in Desktop app

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* Fix ESLint import order for Desktop app visibility changes

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Maria A Nunez
2026-05-19 09:55:31 -04:00
committed by GitHub
co-authored by Cursor Agent
parent 7bb6fb347b
commit 5cd26002d3
8 changed files with 117 additions and 23 deletions
@@ -3,6 +3,7 @@
import React from 'react';
import * as UserAgent from '@mattermost/shared/utils/user_agent';
import type {UserProfile} from '@mattermost/types/users';
import type {DeepPartial} from '@mattermost/types/utilities';
@@ -14,6 +15,11 @@ import type {GlobalState} from 'types/store';
import ProductMenuList from './product_menu_list';
import type {Props as ProductMenuListProps} from './product_menu_list';
const isDesktopAppMock = jest.mocked(UserAgent.isDesktopApp);
jest.mock('@mattermost/shared/utils/user_agent', () => ({
isDesktopApp: jest.fn(() => false),
}));
jest.mock('components/widgets/menu/menu_items/menu_cloud_trial', () => () => null);
jest.mock('components/widgets/menu/menu_items/menu_item_cloud_limit', () => () => null);
jest.mock('components/permissions_gates/system_permission_gate', () => ({children}: {children: React.ReactNode}) => <>{children}</>);
@@ -238,4 +244,17 @@ describe('components/global/product_switcher_menu', () => {
expect(container).toMatchSnapshot();
});
});
test('shows Download Apps link when appDownloadLink configured and not in desktop app', () => {
isDesktopAppMock.mockReturnValue(false);
const {container} = renderWithContext(<ProductMenuList {...defaultProps}/>, adminState);
expect(container.querySelector('#nativeAppLink')).not.toBeNull();
});
test('hides Download Apps link when in desktop app', () => {
isDesktopAppMock.mockReturnValue(true);
const {container} = renderWithContext(<ProductMenuList {...defaultProps}/>, adminState);
expect(container.querySelector('#nativeAppLink')).toBeNull();
isDesktopAppMock.mockReturnValue(false);
});
});
@@ -13,6 +13,7 @@ import {
ViewGridPlusOutlineIcon,
WebhookIncomingIcon,
} from '@mattermost/compass-icons/components';
import {isDesktopApp} from '@mattermost/shared/utils/user_agent';
import type {UserProfile} from '@mattermost/types/users';
import {Permissions} from 'mattermost-redux/constants';
@@ -208,7 +209,7 @@ const ProductMenuList = (props: Props): JSX.Element | null => {
</TeamPermissionGate>
<Menu.ItemExternalLink
id='nativeAppLink'
show={appDownloadLink}
show={Boolean(appDownloadLink) && !isDesktopApp()}
url={makeUrlSafe(appDownloadLink)}
text={formatMessage({id: 'navbar_dropdown.nativeApps', defaultMessage: 'Download Apps'})}
icon={<DownloadOutlineIcon size={18}/>}
@@ -3,6 +3,8 @@
import React from 'react';
import * as UserAgent from '@mattermost/shared/utils/user_agent';
import {Permissions} from 'mattermost-redux/constants';
import type {MockIntl} from 'tests/helpers/intl-test-helper';
@@ -11,6 +13,12 @@ import {renderWithContext, screen} from 'tests/react_testing_utils';
import {MobileSidebarRightItems} from './mobile_sidebar_right_items';
import type {Props} from './mobile_sidebar_right_items';
const isDesktopAppMock = jest.mocked(UserAgent.isDesktopApp);
jest.mock('@mattermost/shared/utils/user_agent', () => ({
isDesktopApp: jest.fn(() => false),
}));
describe('MobileSidebarRightItems', () => {
const defaultProps: Props = {
teamId: 'team-id',
@@ -160,14 +168,28 @@ describe('MobileSidebarRightItems', () => {
expect(screen.getByText('Help')).toBeInTheDocument();
});
test('should show report link when provided', () => {
test('should show Download Apps link when appDownloadLink is set and not in desktop app', () => {
isDesktopAppMock.mockReturnValue(false);
renderWithContext(
<MobileSidebarRightItems
{...defaultProps}
reportAProblemLink='https://report.example.com'
appDownloadLink='https://downloads.example.com'
/>,
defaultState,
);
expect(screen.getByText('Report a Problem')).toBeInTheDocument();
expect(screen.getByText('Download Apps')).toBeInTheDocument();
});
test('should hide Download Apps link when in desktop app', () => {
isDesktopAppMock.mockReturnValue(true);
renderWithContext(
<MobileSidebarRightItems
{...defaultProps}
appDownloadLink='https://downloads.example.com'
/>,
defaultState,
);
expect(screen.queryByText('Download Apps')).not.toBeInTheDocument();
isDesktopAppMock.mockReturnValue(false);
});
});
@@ -5,6 +5,8 @@ import React from 'react';
import {injectIntl} from 'react-intl';
import type {WrappedComponentProps} from 'react-intl';
import {isDesktopApp} from '@mattermost/shared/utils/user_agent';
import {Permissions} from 'mattermost-redux/constants';
import {emitUserLoggedOutEvent} from 'actions/global_actions';
@@ -407,7 +409,7 @@ export class MobileSidebarRightItems extends React.PureComponent<Props> {
/>
<Menu.ItemExternalLink
id='nativeAppLink'
show={this.props.appDownloadLink}
show={Boolean(this.props.appDownloadLink) && !isDesktopApp()}
url={safeAppDownloadLink}
text={formatMessage({id: 'navbar_dropdown.nativeApps', defaultMessage: 'Download Apps'})}
icon={
@@ -3,10 +3,18 @@
import React from 'react';
import * as UserAgent from '@mattermost/shared/utils/user_agent';
import {renderWithContext, userEvent} from 'tests/react_testing_utils';
import Completed from './onboarding_tasklist_completed';
const isDesktopAppMock = jest.mocked(UserAgent.isDesktopApp);
jest.mock('@mattermost/shared/utils/user_agent', () => ({
isDesktopApp: jest.fn(() => false),
}));
jest.mock('mattermost-redux/actions/admin', () => ({
...jest.requireActual('mattermost-redux/actions/admin'),
getPrevTrialLicense: () => ({type: 'MOCK_GET_PREV_TRIAL_LICENSE'}),
@@ -67,4 +75,17 @@ describe('components/onboarding_tasklist/onboarding_tasklist_completed.tsx', ()
await userEvent.click(noThanksLink[0]);
expect(dismissMockFn).toHaveBeenCalledTimes(1);
});
test('displays download apps link when not in desktop app', () => {
isDesktopAppMock.mockReturnValue(false);
const {container} = renderWithContext(<Completed {...props}/>, initialState);
expect(container.querySelectorAll('.download-apps')).toHaveLength(1);
});
test('hides download apps link when in desktop app', () => {
isDesktopAppMock.mockReturnValue(true);
const {container} = renderWithContext(<Completed {...props}/>, initialState);
expect(container.querySelectorAll('.download-apps')).toHaveLength(0);
isDesktopAppMock.mockReturnValue(false);
});
});
@@ -7,6 +7,7 @@ import {useSelector, useDispatch} from 'react-redux';
import {CSSTransition} from 'react-transition-group';
import styled from 'styled-components';
import {isDesktopApp} from '@mattermost/shared/utils/user_agent';
import type {GlobalState} from '@mattermost/types/store';
import {getPrevTrialLicense} from 'mattermost-redux/actions/admin';
@@ -212,24 +213,26 @@ const Completed = (props: Props): JSX.Element => {
/>
</button>
)}
<div className='download-apps'>
<span>
<FormattedMessage
id='onboardingTask.checklist.downloads'
defaultMessage='Now that youre all set up, <link>download our apps.</link>'
values={{
link: (msg: React.ReactNode) => (
<ExternalLink
location='onboarding_tasklist_completed'
href='https://mattermost.com/download#desktop'
>
{msg}
</ExternalLink>
),
}}
/>
</span>
</div>
{!isDesktopApp() && (
<div className='download-apps'>
<span>
<FormattedMessage
id='onboardingTask.checklist.downloads'
defaultMessage='Now that youre all set up, <link>download our apps.</link>'
values={{
link: (msg: React.ReactNode) => (
<ExternalLink
location='onboarding_tasklist_completed'
href='https://mattermost.com/download#desktop'
>
{msg}
</ExternalLink>
),
}}
/>
</span>
</div>
)}
{showStartTrialBtn && <div className='disclaimer'>
<span>
<FormattedMessage
@@ -3,10 +3,18 @@
import React from 'react';
import * as UserAgent from '@mattermost/shared/utils/user_agent';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import {useTasksList} from './onboarding_tasks_manager';
const isDesktopAppMock = jest.mocked(UserAgent.isDesktopApp);
jest.mock('@mattermost/shared/utils/user_agent', () => ({
isDesktopApp: jest.fn(() => false),
}));
const WrapperComponent = (): JSX.Element => {
const taskList = useTasksList();
return (
@@ -87,4 +95,16 @@ describe('onboarding tasks manager', () => {
// verify visit_system_console and start_trial were removed
expect(screen.queryByText('invite_people')).not.toBeInTheDocument();
});
it('Removes download_app task when running in desktop app', () => {
isDesktopAppMock.mockReturnValue(true);
renderWithContext(
<WrapperComponent/>,
initialState,
);
expect(screen.getAllByRole('listitem')).toHaveLength(5);
expect(screen.queryByText('download_app')).not.toBeInTheDocument();
isDesktopAppMock.mockReturnValue(false);
});
});
@@ -6,6 +6,8 @@ import {useIntl} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux';
import {matchPath, useLocation} from 'react-router-dom';
import {isDesktopApp} from '@mattermost/shared/utils/user_agent';
import {savePreferences} from 'mattermost-redux/actions/preferences';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common';
import {getLicense} from 'mattermost-redux/selectors/entities/general';
@@ -138,6 +140,10 @@ export const useTasksList = () => {
delete list.INVITE_PEOPLE;
}
if (isDesktopApp()) {
delete list.DOWNLOAD_APP;
}
return Object.values(list);
};