diff --git a/android/app/build.gradle b/android/app/build.gradle index b3b313464..774ca60e6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -123,7 +123,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5" - versionCode 238 + versionCode 239 versionName "1.24.0" multiDexEnabled = true ndk { diff --git a/app/actions/views/channel_add_members.js b/app/actions/views/channel_add_members.js index a560fb797..f09e06616 100644 --- a/app/actions/views/channel_add_members.js +++ b/app/actions/views/channel_add_members.js @@ -4,9 +4,9 @@ import {addChannelMember} from 'mattermost-redux/actions/channels'; export function handleAddChannelMembers(channelId, members) { - return async (dispatch, getState) => { + return async (dispatch) => { try { - const requests = members.map((m) => dispatch(addChannelMember(channelId, m, getState))); + const requests = members.map((m) => dispatch(addChannelMember(channelId, m))); return await Promise.all(requests); } catch (error) { diff --git a/app/components/post/index.js b/app/components/post/index.js index 342fff76a..ae76dd5b6 100644 --- a/app/components/post/index.js +++ b/app/components/post/index.js @@ -41,6 +41,7 @@ function makeMapStateToProps() { return function mapStateToProps(state, ownProps) { const post = ownProps.post || getPost(state, ownProps.postId); const previousPost = getPost(state, ownProps.previousPostId); + const beforePrevPost = getPost(state, ownProps.beforePrevPostId); const myPreferences = getMyPreferences(state); const currentUserId = getCurrentUserId(state); @@ -84,6 +85,8 @@ function makeMapStateToProps() { isFlagged: isPostFlagged(post.id, myPreferences), isCommentMention, isLandscape: isLandscape(state), + previousPostExists: Boolean(previousPost), + beforePrevPostUserId: (beforePrevPost ? beforePrevPost.user_id : null), }; }; } diff --git a/app/components/post/post.js b/app/components/post/post.js index a1a555fb5..1afd68ce4 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -68,6 +68,8 @@ export default class Post extends PureComponent { location: PropTypes.string, isBot: PropTypes.bool, isLandscape: PropTypes.bool.isRequired, + previousPostExists: PropTypes.bool, + beforePrevPostUserId: PropTypes.string, }; static defaultProps = { @@ -250,6 +252,8 @@ export default class Post extends PureComponent { skipPinnedHeader, location, isLandscape, + previousPostExists, + beforePrevPostUserId, } = this.props; if (!post) { @@ -299,6 +303,8 @@ export default class Post extends PureComponent { onUsernamePress={onUsernamePress} renderReplies={renderReplies} theme={theme} + previousPostExists={previousPostExists} + beforePrevPostUserId={beforePrevPostUserId} /> ); } diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js index fdfa461de..db9b20cdb 100644 --- a/app/components/post_header/post_header.js +++ b/app/components/post_header/post_header.js @@ -43,6 +43,9 @@ export default class PostHeader extends PureComponent { isGuest: PropTypes.bool, userTimezone: PropTypes.string, enableTimezone: PropTypes.bool, + previousPostExists: PropTypes.bool, + post: PropTypes.object, + beforePrevPostUserId: PropTypes.string, }; static defaultProps = { @@ -58,11 +61,18 @@ export default class PostHeader extends PureComponent { }; renderCommentedOnMessage = () => { - if (!this.props.renderReplies || !this.props.commentedOnDisplayName) { + const { + beforePrevPostUserId, + commentedOnDisplayName, + post, + previousPostExists, + renderReplies, + theme, + } = this.props; + if (!renderReplies || !commentedOnDisplayName || (!previousPostExists && post.user_id === beforePrevPostUserId)) { return null; } - const {commentedOnDisplayName, theme} = this.props; const style = getStyleSheet(theme); const displayName = commentedOnDisplayName; diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index 2ec97afee..87f228c82 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -260,6 +260,7 @@ export default class PostList extends PureComponent { // Remember that the list is rendered with item 0 at the bottom so the "previous" post // comes after this one in the list const previousPostId = index < this.props.postIds.length - 1 ? this.props.postIds[index + 1] : null; + const beforePrevPostId = index < this.props.postIds.length - 2 ? this.props.postIds[index + 2] : null; const nextPostId = index > 0 ? this.props.postIds[index - 1] : null; const postProps = { @@ -274,6 +275,7 @@ export default class PostList extends PureComponent { onPress: this.props.onPostPress, renderReplies: this.props.renderReplies, shouldRenderReplyButton: this.props.shouldRenderReplyButton, + beforePrevPostId, }; if (PostListUtils.isCombinedUserActivityPost(item)) { diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index 2987e202c..302538d24 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -87,6 +87,7 @@ exports[`ChannelItem should match snapshot 1`] = ` "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -202,6 +203,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -316,6 +318,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -430,6 +433,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -533,6 +537,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -637,6 +642,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -745,6 +751,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = ` "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { @@ -851,6 +858,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = ` "fontFamily": "Open Sans", "fontSize": 16, "lineHeight": 24, + "maxWidth": "80%", "paddingRight": 10, }, Object { diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index bfdf56f88..9d42e742f 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -245,6 +245,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { fontSize: 16, lineHeight: 24, paddingRight: 10, + maxWidth: '80%', flex: 1, alignSelf: 'center', fontFamily: 'Open Sans', diff --git a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap index 5982e5be3..fd0cd0029 100644 --- a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap +++ b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap @@ -27,6 +27,7 @@ exports[`channel_info should match snapshot 1`] = ` isArchived={false} isBot={false} isGroupConstrained={false} + isLandscape={false} isTeammateGuest={false} memberCount={2} onPermalinkPress={[Function]} diff --git a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap index e8f47346b..0122d3937 100644 --- a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap +++ b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap @@ -1,502 +1,545 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`channel_info_header should match snapshot 1`] = ` - + - - - - Channel name - - - - - - - - - - - - - - - - - - + type="O" + /> - - + Channel name - + + + + + + + + + + + + + + + + + + + + + `; exports[`channel_info_header should match snapshot when DM and hasGuests and is the teammate 1`] = ` - + + + + + Channel name + + - - - Channel name - + /> - + + + - - + + + - - - - - - - - - - - - - - - - - - - + > + + + + - + + + + + `; exports[`channel_info_header should match snapshot when DM and hasGuests but its me and not the teammate 1`] = ` - + - - - - Channel name - - - - - - - - - - - - - - - - - - + type="D" + /> - - + Channel name - + + + + + + + + + + + + + + + + + + + + + `; exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = ` - + + + + + Channel name + + - - - Channel name - + /> - + + + - - + + + - - - - - - - - - - - - - - - - - - - + > + + + + - + + + + + `; exports[`channel_info_header should match snapshot when is group constrained 1`] = ` - + - - - - Channel name - - - + - - - - - - - - - - - - - - - - - - - - - + Channel name - -`; - -exports[`channel_info_header should match snapshot when public channel and hasGuests 1`] = ` - - - - - Channel name - - - + + - - + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + +`; + +exports[`channel_info_header should match snapshot when public channel and hasGuests 1`] = ` + + + + + Channel name + + + + - - + - + + + + + + + + + + + + + + + + + + + + + `; diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index 9644e0a7f..5519a65a8 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -603,6 +603,7 @@ export default class ChannelInfo extends PureComponent { isTeammateGuest={isTeammateGuest} hasGuests={currentChannelGuestCount > 0} isGroupConstrained={currentChannel.group_constrained} + isLandscape={isLandscape} /> } diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js index e77f874d2..4e2fc2fab 100644 --- a/app/screens/channel_info/channel_info_header.js +++ b/app/screens/channel_info/channel_info_header.js @@ -9,7 +9,6 @@ import { Text, TouchableHighlight, View, - SafeAreaView, } from 'react-native'; import {intlShape} from 'react-intl'; @@ -19,6 +18,7 @@ import ChannelIcon from 'app/components/channel_icon'; import FormattedDate from 'app/components/formatted_date'; import FormattedText from 'app/components/formatted_text'; import Markdown from 'app/components/markdown'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import mattermostManaged from 'app/mattermost_managed'; import BottomSheet from 'app/utils/bottom_sheet'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; @@ -44,6 +44,7 @@ export default class ChannelInfoHeader extends React.PureComponent { hasGuests: PropTypes.bool.isRequired, isGroupConstrained: PropTypes.bool, timeZone: PropTypes.string, + isLandscape: PropTypes.bool.isRequired, }; static contextTypes = { @@ -142,6 +143,7 @@ export default class ChannelInfoHeader extends React.PureComponent { isBot, isGroupConstrained, timeZone, + isLandscape, } = this.props; const style = getStyleSheet(theme); @@ -152,101 +154,99 @@ export default class ChannelInfoHeader extends React.PureComponent { style.detail; return ( - - - - - - {displayName} - - - {this.renderHasGuestText(style)} - {purpose.length > 0 && - - - - - - - - - } - {header.length > 0 && - - - - - - - - - } - {isGroupConstrained && - - - - } - {creator && - - - - - } + + + + + {displayName} + - + {this.renderHasGuestText(style)} + {purpose.length > 0 && + + + + + + + + + } + {header.length > 0 && + + + + + + + + + } + {isGroupConstrained && + + + + } + {creator && + + + + + } + ); } } diff --git a/app/screens/channel_info/channel_info_header.test.js b/app/screens/channel_info/channel_info_header.test.js index c865d1b3f..94ec048c5 100644 --- a/app/screens/channel_info/channel_info_header.test.js +++ b/app/screens/channel_info/channel_info_header.test.js @@ -43,6 +43,7 @@ describe('channel_info_header', () => { isTeammateGuest: false, hasGuests: false, isGroupConstrained: false, + isLandscape: false, }; test('should match snapshot', async () => { diff --git a/app/screens/settings/section.js b/app/screens/settings/section.js index b132af8d4..b1834cf77 100644 --- a/app/screens/settings/section.js +++ b/app/screens/settings/section.js @@ -87,6 +87,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, footer: { marginTop: 10, + marginHorizontal: 15, fontSize: 12, color: changeOpacity(theme.centerChannelColor, 0.5), }, diff --git a/app/screens/sso/index.js b/app/screens/sso/index.js index f17246b5b..fa9c18f92 100644 --- a/app/screens/sso/index.js +++ b/app/screens/sso/index.js @@ -6,6 +6,7 @@ import {connect} from 'react-redux'; import {handleSuccessfulLogin, scheduleExpiredNotification} from 'app/actions/views/login'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import {isLandscape} from 'app/selectors/device'; import {setStoreFromLocalData} from 'mattermost-redux/actions/general'; @@ -15,6 +16,7 @@ function mapStateToProps(state) { return { ...state.views.selectServer, theme: getTheme(state), + isLandscape: isLandscape(state), }; } diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js index 788a37196..51fb02f38 100644 --- a/app/screens/sso/sso.js +++ b/app/screens/sso/sso.js @@ -18,6 +18,7 @@ import {Client4} from 'mattermost-redux/client'; import {ViewTypes} from 'app/constants'; import Loading from 'app/components/loading'; +import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import StatusBar from 'app/components/status_bar'; import {resetToChannel} from 'app/actions/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -70,6 +71,7 @@ class SSO extends PureComponent { handleSuccessfulLogin: PropTypes.func.isRequired, setStoreFromLocalData: PropTypes.func.isRequired, }).isRequired, + isLandscape: PropTypes.bool.isRequired, }; useWebkit = true; @@ -203,7 +205,7 @@ class SSO extends PureComponent { }; render() { - const {theme} = this.props; + const {theme, isLandscape} = this.props; const {error, messagingEnabled, renderWebView, jsCode} = this.state; const style = getStyleSheet(theme); @@ -236,7 +238,7 @@ class SSO extends PureComponent { } return ( - + {content} diff --git a/app/utils/general.js b/app/utils/general.js index 4352bec08..6365e9380 100644 --- a/app/utils/general.js +++ b/app/utils/general.js @@ -52,7 +52,7 @@ export function emptyFunction() { // eslint-disable-line no-empty-function export function hapticFeedback(method = 'impactLight') { ReactNativeHapticFeedback.trigger(method, { - enableVibrateFallback: true, + enableVibrateFallback: false, ignoreAndroidSystemSettings: false, }); } diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index a99e0da2e..70501c2c2 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -454,6 +454,8 @@ "mobile.share_extension.error_message": "An error has occurred while using the share extension.", "mobile.share_extension.error_title": "Extension Error", "mobile.share_extension.team": "Team", + "mobile.share_extension.too_long_message": "Character count: {count}/{max}", + "mobile.share_extension.too_long_title": "Message is too long", "mobile.sidebar_settings.permanent": "Permanent Sidebar", "mobile.sidebar_settings.permanent_description": "Keep the sidebar open permanently", "mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.", diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index cffbebf3f..d9dad2a12 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2856,7 +2856,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2917,7 +2917,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 238; + CURRENT_PROJECT_VERSION = 239; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 518277cf6..92c47118f 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 238 + 239 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 9b9ffcd1b..b3855e6fc 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.24.0 CFBundleVersion - 238 + 239 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 1553e2ccc..5b10c1430 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 238 + 239 diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 1a5d8a77b..f458ad0fe 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.24.0 CFBundleVersion - 238 + 239 NSExtension NSExtensionPointIdentifier diff --git a/patches/react-native-image-picker+0.28.1.patch b/patches/react-native-image-picker+0.28.1.patch new file mode 100644 index 000000000..46b8ac1b5 --- /dev/null +++ b/patches/react-native-image-picker+0.28.1.patch @@ -0,0 +1,30 @@ +diff --git a/node_modules/react-native-image-picker/ios/ImagePickerManager.m b/node_modules/react-native-image-picker/ios/ImagePickerManager.m +index 28d5870..3f70983 100644 +--- a/node_modules/react-native-image-picker/ios/ImagePickerManager.m ++++ b/node_modules/react-native-image-picker/ios/ImagePickerManager.m +@@ -455,12 +455,19 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPicking + } + + if (videoURL) { // Protect against reported crash +- NSError *error = nil; +- [fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error]; +- if (error) { +- self.callback(@[@{@"error": error.localizedFailureReason}]); +- return; +- } ++ NSError *error = nil; ++ ++ // If we have write access to the source file, move it. Otherwise use copy. ++ if ([fileManager isWritableFileAtPath:[videoURL path]]) { ++ [fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error]; ++ } else { ++ [fileManager copyItemAtURL:videoURL toURL:videoDestinationURL error:&error]; ++ } ++ ++ if (error) { ++ self.callback(@[@{@"error": error.localizedFailureReason}]); ++ return; ++ } + } + } + diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js index b71c13094..ac90bd558 100644 --- a/share_extension/android/extension_post/extension_post.js +++ b/share_extension/android/extension_post/extension_post.js @@ -406,17 +406,34 @@ export default class ExtensionPost extends PureComponent { onPost = () => { const {channelId, files, value} = this.state; const {currentUserId} = this.props; + const {formatMessage} = this.context.intl; - const data = { - channelId, - currentUserId, - files, - token: this.token, - url: this.url, - value, - }; + if (value.length > MAX_MESSAGE_LENGTH) { + Alert.alert( + formatMessage({ + id: 'mobile.share_extension.too_long_title', + defaultMessage: 'Message is too long', + }), + formatMessage({ + id: 'mobile.share_extension.too_long_message', + defaultMessage: 'Character count: {count}/{max}', + }, { + count: value.length, + max: MAX_MESSAGE_LENGTH, + }) + ); + } else { + const data = { + channelId, + currentUserId, + files, + token: this.token, + url: this.url, + value, + }; - this.onClose(data); + this.onClose(data); + } }; renderBody = () => { @@ -435,7 +452,6 @@ export default class ExtensionPost extends PureComponent { { + if (count < 0) { + return styles.textTooLong; + } + return styles.textLengthOk; + } + + renderMessageLengthRemaining = () => { + const {value} = this.state; + const messageLengthRemaining = MAX_MESSAGE_LENGTH - value.length; + + if (value.length === 0) { + return null; + } + + const renderStyle = [styles.messageLengthRemaining, this.lengthCounterColor(messageLengthRemaining)]; + return ( + + {messageLengthRemaining} + + ); + }; + render() { const {formatMessage} = this.context.intl; const {maxFileSize} = this.props; @@ -610,6 +649,7 @@ export default class ExtensionPost extends PureComponent { {this.renderBody()} + {this.renderMessageLengthRemaining()} {this.renderTeamButton()} {this.renderChannelButton()} @@ -654,6 +694,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flex: 1, padding: 15, }, + messageLengthRemaining: { + paddingTop: 5, + paddingBottom: 5, + paddingLeft: 15, + paddingRight: 15, + opacity: 0.5, + }, + textLengthOk: { + color: theme.centerChannelColor, + }, + textTooLong: { + color: theme.errorTextColor, + }, input: { flex: 1, color: theme.centerChannelColor, diff --git a/share_extension/android/extension_post/extension_post.test.js b/share_extension/android/extension_post/extension_post.test.js new file mode 100644 index 000000000..2422be80a --- /dev/null +++ b/share_extension/android/extension_post/extension_post.test.js @@ -0,0 +1,76 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Alert, PermissionsAndroid} from 'react-native'; +import {shallowWithIntl} from 'test/intl-test-helper'; + +import ExtensionPost from './extension_post'; + +jest.spyOn(Alert, 'alert').mockReturnValue(true); +jest.spyOn(PermissionsAndroid, 'check').mockReturnValue(PermissionsAndroid.RESULTS.GRANTED); + +jest.mock('react-navigation-stack/dist/views/TouchableItem', () => {}); + +jest.mock('NativeModules', () => ({ + MattermostShare: { + close: jest.fn(), + }, + RNKeychainManager: { + SECURITY_LEVEL_ANY: 'ANY', + }, +})); + +jest.mock('app/mattermost_managed', () => ({ + getConfig: jest.fn().mockReturnValue(false), +})); + +const MAX_MESSAGE_LENGTH = 4000; + +describe('ExtensionPost', () => { + const baseProps = { + actions: { + getTeamChannels: jest.fn(), + }, + channelId: 'channel-id', + channels: {}, + currentUserId: 'current-user-id', + maxFileSize: 1024, + navigation: { + setParams: jest.fn(), + }, + teamId: 'team-id', + }; + + const wrapper = shallowWithIntl( + + ); + + const instance = wrapper.instance(); + + const postMessage = (message) => { + wrapper.setState({value: message}); + instance.onPost(); + }; + + test('should show Alert dialog if shared message is longer than maximum length permitted', () => { + const longMessage = 'a'.repeat(MAX_MESSAGE_LENGTH + 1); + postMessage(longMessage); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + }); + + test('should not show Alert dialog if shared message is within maximum length permitted', () => { + const message = 'a'.repeat(MAX_MESSAGE_LENGTH - 1); + postMessage(message); + + expect(Alert.alert).not.toHaveBeenCalled(); + }); + + test('should not show Alert dialog if shared message is at maximum length permitted', () => { + const exactLengthMessage = 'a'.repeat(MAX_MESSAGE_LENGTH); + postMessage(exactLengthMessage); + + expect(Alert.alert).not.toHaveBeenCalled(); + }); +});