GH-11952 Adding option to convert public channel to private (#3230)

* Adding option to convert public channel to private

* Added unit tests, feedback alert with error handling, and new alert text

* Ensuring baseProps is never changed between channel_info tests

* Trimming the display_name on alerts

- Preventing extra empty spaces from being displayed on the alerts by trimming the variable
This commit is contained in:
Andre Vasconcelos 2019-09-24 10:39:15 +09:00 committed by Miguel Alatzar
parent 70102d2516
commit 7bd000d794
6 changed files with 811 additions and 2 deletions

View file

@ -0,0 +1,570 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`channel_info should match snapshot 1`] = `
<View
style={
Object {
"backgroundColor": "#ffffff",
"flex": 1,
}
}
>
<Connect(StatusBar) />
<ScrollViewMock
style={
Object {
"backgroundColor": undefined,
"flex": 1,
}
}
>
<ChannelInfoHeader
createAt={123}
creator="Creator"
displayName="Channel Name"
hasGuests={false}
header=""
isArchived={false}
isBot={false}
isGroupConstrained={false}
memberCount={2}
onPermalinkPress={[Function]}
popToRoot={[MockFunction]}
purpose="Purpose"
status="status"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
type="O"
/>
<View
style={
Object {
"backgroundColor": "#ffffff",
"borderBottomColor": undefined,
"borderBottomWidth": 1,
"borderTopColor": undefined,
"borderTopWidth": 1,
}
}
>
<React.Fragment>
<channelInfoRow
action={[Function]}
defaultMessage="Favorite"
detail={false}
icon="star-o"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="mobile.routes.channelInfo.favorite"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={true}
/>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Mute channel"
detail={false}
icon="bell-slash-o"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="channel_notifications.muteChannel.settings"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={true}
/>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Ignore @channel, @here, @all"
detail={false}
icon="at"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="channel_notifications.ignoreChannelMentions.settings"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={true}
/>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Pinned Posts"
image={1}
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="channel_header.pinnedPosts"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
<React.Fragment>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Manage Members"
detail={2}
icon="users"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="channel_header.manageMembers"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
</React.Fragment>
<React.Fragment>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Add Members"
icon="user-plus"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="channel_header.addMembers"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
</React.Fragment>
<React.Fragment>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Convert to Private Channel"
icon="lock"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="mobile.channel_info.convert"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
</React.Fragment>
<React.Fragment>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Edit Channel"
icon="edit"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="mobile.channel_info.edit"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
</React.Fragment>
</React.Fragment>
<React.Fragment>
<View
style={
Object {
"backgroundColor": undefined,
"height": 1,
"marginHorizontal": 15,
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Leave Channel"
icon="sign-out"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textId="navbar.leave"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
</React.Fragment>
</View>
<View
style={
Array [
Object {
"backgroundColor": "#ffffff",
"borderBottomColor": undefined,
"borderBottomWidth": 1,
"borderTopColor": undefined,
"borderTopWidth": 1,
},
Object {
"borderBottomColor": undefined,
"borderBottomWidth": 1,
"borderTopColor": undefined,
"borderTopWidth": 1,
"marginTop": 40,
},
]
}
>
<channelInfoRow
action={[Function]}
defaultMessage="Archive Channel"
icon="archive"
iconColor="#CA3B27"
isLandscape={false}
rightArrow={true}
shouldRender={true}
textColor="#CA3B27"
textId="mobile.routes.channelInfo.delete_channel"
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
togglable={false}
/>
</View>
</ScrollViewMock>
</View>
`;

View file

@ -28,6 +28,7 @@ export default class ChannelInfo extends PureComponent {
clearPinnedPosts: PropTypes.func.isRequired,
closeDMChannel: PropTypes.func.isRequired,
closeGMChannel: PropTypes.func.isRequired,
convertChannelToPrivate: PropTypes.func.isRequired,
deleteChannel: PropTypes.func.isRequired,
getChannelStats: PropTypes.func.isRequired,
getChannel: PropTypes.func.isRequired,
@ -60,6 +61,7 @@ export default class ChannelInfo extends PureComponent {
isChannelMuted: PropTypes.bool.isRequired,
isCurrent: PropTypes.bool.isRequired,
isFavorite: PropTypes.bool.isRequired,
canConvertChannel: PropTypes.bool.isRequired,
canManageUsers: PropTypes.bool.isRequired,
canEditChannel: PropTypes.bool.isRequired,
ignoreChannelMentions: PropTypes.bool.isRequired,
@ -176,6 +178,59 @@ export default class ChannelInfo extends PureComponent {
this.handleDeleteOrLeave('delete');
};
handleConfirmConvertToPrivate = preventDoubleTap(async () => {
const {actions, currentChannel} = this.props;
const result = await actions.convertChannelToPrivate(currentChannel.id);
const displayName = {displayName: currentChannel.display_name.trim()};
const {formatMessage} = this.context.intl;
if (result.error) {
alertErrorWithFallback(
this.context.intl,
result.error,
{
id: t('mobile.channel_info.convert_failed'),
defaultMessage: 'We were unable to convert {displayName} to a private channel.',
},
{
displayName,
},
[{
text: formatMessage({id: 'mobile.share_extension.error_close', defaultMessage: 'Close'}),
}, {
text: formatMessage({id: 'mobile.terms_of_service.alert_retry', defaultMessage: 'Try Again'}),
onPress: this.handleConfirmConvertToPrivate,
}]
);
} else {
Alert.alert(
'',
formatMessage({id: t('mobile.channel_info.convert_success'), defaultMessage: '{displayName} is now a private channel.'}, displayName),
);
}
})
handleConvertToPrivate = preventDoubleTap(() => {
const {currentChannel} = this.props;
const {formatMessage} = this.context.intl;
const displayName = {displayName: currentChannel.display_name.trim()};
const title = {id: t('mobile.channel_info.alertTitleConvertChannel'), defaultMessage: 'Convert {displayName} to a private channel?'};
const message = {
id: t('mobile.channel_info.alertMessageConvertChannel'),
defaultMessage: 'When you convert {displayName} to a private channel, history and membership are preserved. Publicly shared files remain accessible to anyone with the link. Membership in a private channel is by invitation only.\n\nThe change is permanent and cannot be undone.\n\nAre you sure you want to convert {displayName} to a private channel?',
};
Alert.alert(
formatMessage(title, displayName),
formatMessage(message, displayName),
[{
text: formatMessage({id: 'mobile.channel_info.alertNo', defaultMessage: 'No'}),
}, {
text: formatMessage({id: 'mobile.channel_info.alertYes', defaultMessage: 'Yes'}),
onPress: this.handleConfirmConvertToPrivate,
}],
);
});
handleDeleteOrLeave = preventDoubleTap((eventType) => {
const {formatMessage} = this.context.intl;
const channel = this.props.currentChannel;
@ -352,6 +407,13 @@ export default class ChannelInfo extends PureComponent {
return isDirectMessage || isGroupMessage;
};
renderConvertToPrivateRow = () => {
const {currentChannel, canConvertChannel} = this.props;
const isDefaultChannel = currentChannel.name === General.DEFAULT_CHANNEL;
const isPublicChannel = currentChannel.type === General.OPEN_CHANNEL;
return !isDefaultChannel && isPublicChannel && canConvertChannel;
}
actionsRows = (style, channelIsArchived) => {
const {
currentChannelMemberCount,
@ -461,6 +523,19 @@ export default class ChannelInfo extends PureComponent {
/>
</React.Fragment>
}
{this.renderConvertToPrivateRow() && (
<React.Fragment>
<View style={style.separator}/>
<ChannelInfoRow
action={this.handleConvertToPrivate}
defaultMessage='Convert to Private Channel'
icon='lock'
textId={t('mobile.channel_info.convert')}
theme={theme}
isLandscape={isLandscape}
/>
</React.Fragment>
)}
{canEditChannel && (
<React.Fragment>
<View style={style.separator}/>

View file

@ -0,0 +1,156 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
import {General} from 'mattermost-redux/constants';
import ChannelInfo from './channel_info';
// ChannelInfoRow expects to receive the pinIcon as a number
jest.mock('assets/images/channel_info/pin.png', () => {
return 1;
});
jest.mock('app/utils/theme', () => {
const original = require.requireActual('app/utils/theme');
return {
...original,
changeOpacity: jest.fn(),
};
});
describe('channel_info', () => {
const intlMock = {
formatMessage: jest.fn(),
formatDate: jest.fn(),
formatTime: jest.fn(),
formatRelative: jest.fn(),
formatNumber: jest.fn(),
formatPlural: jest.fn(),
formatHTMLMessage: jest.fn(),
now: jest.fn(),
};
const baseProps = {
canDeleteChannel: true,
canConvertChannel: true,
canManageUsers: true,
viewArchivedChannels: true,
canEditChannel: true,
currentChannel: {
id: '1234',
display_name: 'Channel Name',
type: General.OPEN_CHANNEL,
create_at: 123,
delete_at: 0,
header: '',
purpose: 'Purpose',
group_constrained: false,
},
currentChannelCreatorName: 'Creator',
currentChannelMemberCount: 2,
currentChannelGuestCount: 0,
currentUserId: '1234',
currentUserIsGuest: false,
isChannelMuted: false,
ignoreChannelMentions: false,
isCurrent: true,
isFavorite: false,
status: 'status',
theme: Preferences.THEMES.default,
isBot: false,
isLandscape: false,
actions: {
clearPinnedPosts: jest.fn(),
closeDMChannel: jest.fn(),
closeGMChannel: jest.fn(),
convertChannelToPrivate: jest.fn(),
deleteChannel: jest.fn(),
getChannelStats: jest.fn(),
getChannel: jest.fn(),
leaveChannel: jest.fn(),
loadChannelsByTeamName: jest.fn(),
favoriteChannel: jest.fn(),
unfavoriteChannel: jest.fn(),
getCustomEmojisInText: jest.fn(),
selectFocusedPostId: jest.fn(),
updateChannelNotifyProps: jest.fn(),
selectPenultimateChannel: jest.fn(),
setChannelDisplayName: jest.fn(),
handleSelectChannel: jest.fn(),
popTopScreen: jest.fn(),
goToScreen: jest.fn(),
popToRoot: jest.fn(),
dismissModal: jest.fn(),
showModalOverCurrentContext: jest.fn(),
},
};
test('should match snapshot', async () => {
const wrapper = shallow(
<ChannelInfo
{...baseProps}
/>,
{context: {intl: intlMock}},
);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should render convert to private button when user has team admin permissions', async () => {
const wrapper = shallow(
<ChannelInfo
{...baseProps}
/>,
{context: {intl: intlMock}},
);
const instance = wrapper.instance();
const render = instance.renderConvertToPrivateRow();
expect(render).toBeTruthy();
});
test('should not render convert to private button when user is not team admin or above', async () => {
const wrapper = shallow(
<ChannelInfo
{...baseProps}
canConvertChannel={false}
/>,
{context: {intl: intlMock}},
);
const instance = wrapper.instance();
const render = instance.renderConvertToPrivateRow();
expect(render).toBeFalsy();
});
test('should not render convert to private button currentChannel is already private', async () => {
const props = Object.assign({}, baseProps);
props.currentChannel.type = General.PRIVATE_CHANNEL;
const wrapper = shallow(
<ChannelInfo
{...props}
/>,
{context: {intl: intlMock}},
);
const instance = wrapper.instance();
const render = instance.renderConvertToPrivateRow();
expect(render).toBeFalsy();
});
test('should not render convert to private button when currentChannel is a default channel', async () => {
const props = Object.assign({}, baseProps);
props.currentChannel.name = General.DEFAULT_CHANNEL;
const wrapper = shallow(
<ChannelInfo
{...props}
/>,
{context: {intl: intlMock}},
);
const instance = wrapper.instance();
const render = instance.renderConvertToPrivateRow();
expect(render).toBeFalsy();
});
});

View file

@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {
convertChannelToPrivate,
favoriteChannel,
getChannelStats,
getChannel,
@ -105,6 +106,7 @@ function mapStateToProps(state) {
return {
canDeleteChannel: showDeleteOption(state, config, license, currentChannel, isAdmin, isSystemAdmin, isChannelAdmin),
canConvertChannel: isAdmin,
viewArchivedChannels,
canEditChannel,
currentChannel,
@ -132,6 +134,7 @@ function mapDispatchToProps(dispatch) {
clearPinnedPosts,
closeDMChannel,
closeGMChannel,
convertChannelToPrivate,
deleteChannel,
getChannelStats,
getChannel,

View file

@ -17,12 +17,12 @@ export function toTitleCase(str) {
return str.replace(/\w\S*/g, doTitleCase);
}
export function alertErrorWithFallback(intl, error, fallback, values) {
export function alertErrorWithFallback(intl, error, fallback, values, buttons) {
let msg = error.message;
if (!msg || msg === 'Network request failed') {
msg = intl.formatMessage(fallback, values);
}
Alert.alert('', msg);
Alert.alert('', msg, buttons);
}
export function alertErrorIfInvalidPermissions(result) {

View file

@ -156,12 +156,17 @@
"mobile.camera_video_permission_denied_description": "Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.",
"mobile.camera_video_permission_denied_title": "{applicationName} would like to access your camera",
"mobile.channel_drawer.search": "Jump to...",
"mobile.channel_info.alertMessageConvertChannel": "When you convert {displayName} to a private channel, history and membership are preserved. Publicly shared files remain accessible to anyone with the link. Membership in a private channel is by invitation only.\n\nThe change is permanent and cannot be undone.\n\nAre you sure you want to convert {displayName} to a private channel?",
"mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to archive the {term} {name}?",
"mobile.channel_info.alertMessageLeaveChannel": "Are you sure you want to leave the {term} {name}?",
"mobile.channel_info.alertNo": "No",
"mobile.channel_info.alertTitleConvertChannel": "Convert {displayName} to a private channel?",
"mobile.channel_info.alertTitleDeleteChannel": "Archive {term}",
"mobile.channel_info.alertTitleLeaveChannel": "Leave {term}",
"mobile.channel_info.alertYes": "Yes",
"mobile.channel_info.convert": "Convert to Private Channel",
"mobile.channel_info.convert_failed": "We were unable to convert {displayName} to a private channel.",
"mobile.channel_info.convert_success": "{displayName} is now a private channel.",
"mobile.channel_info.copy_header": "Copy Header",
"mobile.channel_info.copy_purpose": "Copy Purpose",
"mobile.channel_info.delete_failed": "We couldn't archive the channel {displayName}. Please check your connection and try again.",