From 48dbb75471d7982874867b24644595abb02dac8c Mon Sep 17 00:00:00 2001 From: Joseph Baylon Date: Thu, 12 Nov 2020 16:29:16 -0800 Subject: [PATCH] MM-29024 Detox/E2E: Add unit and e2e tests for disabled mobile upload (#4954) * MM-29024 Detox/E2E: Add unit and e2e tests for disabled mobile upload * Simplified element call * Remove extra lines * Added e2e for mobile upload enabled - quick actions * Fix upload item snap file * Added check for license * Fix lint * Update function export Co-authored-by: Mattermod --- .../__snapshots__/post_draft.test.js.snap | 3 + .../camera_quick_action.test.js.snap | 1 + .../camera_quick_action/index.js | 4 + .../file_quick_action.test.js.snap | 1 + .../quick_actions/file_quick_action/index.js | 4 + .../image_quick_action.test.js.snap | 1 + .../quick_actions/image_quick_action/index.js | 4 + .../__snapshots__/upload_item.test.js.snap | 110 ++++++++++++++++++ .../__snapshots__/upload_remove.test.js.snap | 37 ++++++ .../uploads/upload_item/upload_item.test.js | 10 ++ .../uploads/upload_item/upload_remove.test.js | 25 ++++ .../touchable_with_feedback.android.js | 6 +- .../touchable_with_feedback.ios.js | 10 +- app/reducers/views/channel.js | 4 +- app/reducers/views/channel.test.js | 66 ++++++----- app/reducers/views/thread.js | 2 +- app/reducers/views/thread.test.js | 40 +++++++ .../ui/component/camera_quick_action.js | 25 ++++ .../support/ui/component/file_quick_action.js | 25 ++++ .../ui/component/image_quick_action.js | 25 ++++ detox/e2e/support/ui/component/index.js | 6 + .../disable_upload.e2e.js | 43 +++++++ .../enable_upload.e2e.js | 43 +++++++ 23 files changed, 461 insertions(+), 34 deletions(-) create mode 100644 app/components/post_draft/uploads/upload_item/__snapshots__/upload_item.test.js.snap create mode 100644 app/components/post_draft/uploads/upload_item/__snapshots__/upload_remove.test.js.snap create mode 100644 app/components/post_draft/uploads/upload_item/upload_remove.test.js create mode 100644 app/reducers/views/thread.test.js create mode 100644 detox/e2e/support/ui/component/camera_quick_action.js create mode 100644 detox/e2e/support/ui/component/file_quick_action.js create mode 100644 detox/e2e/support/ui/component/image_quick_action.js create mode 100644 detox/e2e/test/files_and_attachments/disable_upload.e2e.js create mode 100644 detox/e2e/test/files_and_attachments/enable_upload.e2e.js diff --git a/app/components/post_draft/__snapshots__/post_draft.test.js.snap b/app/components/post_draft/__snapshots__/post_draft.test.js.snap index 5426b89c7..dd6789986 100644 --- a/app/components/post_draft/__snapshots__/post_draft.test.js.snap +++ b/app/components/post_draft/__snapshots__/post_draft.test.js.snap @@ -466,6 +466,7 @@ exports[`PostDraft Should render the DraftInput 1`] = ` "padding": 10, } } + testID="post_draft.file_quick_action" > + + + + + + + + + +`; diff --git a/app/components/post_draft/uploads/upload_item/__snapshots__/upload_remove.test.js.snap b/app/components/post_draft/uploads/upload_item/__snapshots__/upload_remove.test.js.snap new file mode 100644 index 000000000..fd4ba768a --- /dev/null +++ b/app/components/post_draft/uploads/upload_item/__snapshots__/upload_remove.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UploadRemove should match, full snapshot 1`] = ` + + + + + +`; diff --git a/app/components/post_draft/uploads/upload_item/upload_item.test.js b/app/components/post_draft/uploads/upload_item/upload_item.test.js index c247df34f..833ff9f96 100644 --- a/app/components/post_draft/uploads/upload_item/upload_item.test.js +++ b/app/components/post_draft/uploads/upload_item/upload_item.test.js @@ -1,7 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. + import React from 'react'; import {shallow} from 'enzyme'; +import {shallowWithIntl} from 'test/intl-test-helper'; import {Preferences} from '@mm-redux/constants'; import ImageCacheManager from '@utils/image_cache_manager'; @@ -21,6 +23,14 @@ describe('UploadItem', () => { }; describe('downloadAndUploadFile', () => { + test('should match, full snapshot', () => { + const wrapper = shallowWithIntl( + , + ); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); + test('should upload file', async () => { const component = shallow(); component.instance().uploadFile = jest.fn(); diff --git a/app/components/post_draft/uploads/upload_item/upload_remove.test.js b/app/components/post_draft/uploads/upload_item/upload_remove.test.js new file mode 100644 index 000000000..6d6a2cc81 --- /dev/null +++ b/app/components/post_draft/uploads/upload_item/upload_remove.test.js @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallowWithIntl} from 'test/intl-test-helper'; + +import {Preferences} from '@mm-redux/constants'; +import UploadRemove from './upload_remove'; + +describe('UploadRemove', () => { + const props = { + onPress: jest.fn(), + channelId: 'channel-id', + clientId: 'client-id', + theme: Preferences.THEMES.default, + }; + + test('should match, full snapshot', () => { + const wrapper = shallowWithIntl( + , + ); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/touchable_with_feedback/touchable_with_feedback.android.js b/app/components/touchable_with_feedback/touchable_with_feedback.android.js index fe5e1fbb7..27c1c1593 100644 --- a/app/components/touchable_with_feedback/touchable_with_feedback.android.js +++ b/app/components/touchable_with_feedback/touchable_with_feedback.android.js @@ -11,6 +11,7 @@ import CustomPropTypes from 'app/constants/custom_prop_types'; export default class TouchableWithFeedbackAndroid extends PureComponent { static propTypes = { + testID: PropTypes.string, children: CustomPropTypes.Children, underlayColor: PropTypes.string, type: PropTypes.oneOf(['native', 'opacity', 'none']), @@ -21,12 +22,13 @@ export default class TouchableWithFeedbackAndroid extends PureComponent { }; render() { - const {children, underlayColor, type, ...props} = this.props; + const {testID, children, underlayColor, type, ...props} = this.props; switch (type) { case 'native': return ( @@ -38,6 +40,7 @@ export default class TouchableWithFeedbackAndroid extends PureComponent { case 'opacity': return ( {children} @@ -46,6 +49,7 @@ export default class TouchableWithFeedbackAndroid extends PureComponent { case 'none': return ( {children} diff --git a/app/components/touchable_with_feedback/touchable_with_feedback.ios.js b/app/components/touchable_with_feedback/touchable_with_feedback.ios.js index 3c250d6ce..c4e39fdec 100644 --- a/app/components/touchable_with_feedback/touchable_with_feedback.ios.js +++ b/app/components/touchable_with_feedback/touchable_with_feedback.ios.js @@ -9,6 +9,7 @@ import CustomPropTypes from 'app/constants/custom_prop_types'; export default class TouchableWithFeedbackIOS extends PureComponent { static propTypes = { + testID: PropTypes.string, children: CustomPropTypes.Children, cancelTouchOnPanning: PropTypes.bool, type: PropTypes.oneOf(['native', 'opacity', 'none']), @@ -29,12 +30,15 @@ export default class TouchableWithFeedbackIOS extends PureComponent { } render() { - const {children, type, ...props} = this.props; + const {testID, children, type, ...props} = this.props; switch (type) { case 'native': return ( - + @@ -45,6 +49,7 @@ export default class TouchableWithFeedbackIOS extends PureComponent { case 'opacity': return ( {children} @@ -53,6 +58,7 @@ export default class TouchableWithFeedbackIOS extends PureComponent { case 'none': return ( {children} diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js index b4edd8e4f..2e4f75b48 100644 --- a/app/reducers/views/channel.js +++ b/app/reducers/views/channel.js @@ -55,7 +55,7 @@ function handleSelectChannel(state, action) { return data; } -function handleSetTempUploadFileForPostDraft(state, action) { +export function handleSetTempUploadFilesForPostDraft(state, action) { if (action.rootId) { return state; } @@ -203,7 +203,7 @@ function drafts(state = {}, action) { // eslint-disable-line complexity case ChannelTypes.SELECT_CHANNEL: return handleSelectChannel(state, action); case ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT: - return handleSetTempUploadFileForPostDraft(state, action); + return handleSetTempUploadFilesForPostDraft(state, action); case ViewTypes.RETRY_UPLOAD_FILE_FOR_POST: return handleRetryUploadFileForPost(state, action); case FileTypes.RECEIVED_UPLOAD_FILES: diff --git a/app/reducers/views/channel.test.js b/app/reducers/views/channel.test.js index 88940a057..c3cd93480 100644 --- a/app/reducers/views/channel.test.js +++ b/app/reducers/views/channel.test.js @@ -1,41 +1,51 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import channelReducer from './channel'; +import channelReducer, {handleSetTempUploadFilesForPostDraft} from './channel'; +import {ViewTypes} from '@constants'; describe('Reducers.channel', () => { - const initialState = { - displayName: '', - drafts: {}, - loading: false, - refreshing: false, - loadingPosts: {}, - lastGetPosts: {}, - retryFailed: false, - loadMorePostsVisible: true, - lastChannelViewTime: {}, - keepChannelIdAsUnread: null, - unreadMessageCount: {}, - }; - test('Initial state', () => { + const initialState = { + displayName: '', + drafts: {}, + loading: false, + refreshing: false, + loadingPosts: {}, + lastGetPosts: {}, + retryFailed: false, + loadMorePostsVisible: true, + lastChannelViewTime: {}, + keepChannelIdAsUnread: null, + unreadMessageCount: {}, + }; + const nextState = channelReducer( - { - displayName: '', - drafts: {}, - loading: false, - refreshing: false, - loadingPosts: {}, - lastGetPosts: {}, - retryFailed: false, - loadMorePostsVisible: true, - lastChannelViewTime: {}, - keepChannelIdAsUnread: null, - unreadMessageCount: {}, - }, + initialState, {}, ); expect(nextState).toEqual(initialState); }); + + test('handleSetTempUploadFilesForPostDraft - should not throw error when state[action.channelId] is null', () => { + const action = { + channelId: 'channel-id', + clientIds: [], + rootId: null, + type: ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT, + }; + + const initialState = { + [action.channelId]: null, + }; + + const expectedState = { + [action.channelId]: { + files: [], + }, + }; + + expect(handleSetTempUploadFilesForPostDraft(initialState, action)).toEqual(expectedState); + }); }); diff --git a/app/reducers/views/thread.js b/app/reducers/views/thread.js index 6e51c4d3e..a1bc1fed2 100644 --- a/app/reducers/views/thread.js +++ b/app/reducers/views/thread.js @@ -66,7 +66,7 @@ function handleReceivedPostSelected(state, action) { return data; } -function handleSetTempUploadFilesForPostDraft(state, action) { +export function handleSetTempUploadFilesForPostDraft(state, action) { if (!action.rootId) { return state; } diff --git a/app/reducers/views/thread.test.js b/app/reducers/views/thread.test.js new file mode 100644 index 000000000..68d0e1d3e --- /dev/null +++ b/app/reducers/views/thread.test.js @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import channelReducer, {handleSetTempUploadFilesForPostDraft} from './thread'; +import {ViewTypes} from '@constants'; + +describe('Reducers.thread', () => { + test('Initial state', () => { + const initialState = { + drafts: {}, + }; + + const nextState = channelReducer( + initialState, + {}, + ); + + expect(nextState).toEqual(initialState); + }); + + test('handleSetTempUploadFilesForPostDraft - should not throw error when state[action.rootId] is null', () => { + const action = { + clientIds: [], + rootId: 'root-id', + type: ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT, + }; + + const initialState = { + [action.rootId]: null, + }; + + const expectedState = { + [action.rootId]: { + files: [], + }, + }; + + expect(handleSetTempUploadFilesForPostDraft(initialState, action)).toEqual(expectedState); + }); +}); diff --git a/detox/e2e/support/ui/component/camera_quick_action.js b/detox/e2e/support/ui/component/camera_quick_action.js new file mode 100644 index 000000000..72cd6ca3c --- /dev/null +++ b/detox/e2e/support/ui/component/camera_quick_action.js @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class CameraQuickAction { + testID = { + cameraQuickAction: 'post_draft.camera_quick_action', + cameraQuickActionDisabled: 'post_draft.camera_quick_action.disabled', + } + + cameraQuickAction = element(by.id(this.testID.cameraQuickAction)); + cameraQuickActionDisabled = element(by.id(this.testID.cameraQuickActionDisabled)); + + toBeVisible = async (options = {disabled: false}) => { + if (options.disabled) { + await expect(this.cameraQuickActionDisabled).toBeVisible(); + return this.cameraQuickActionDisabled; + } + + await expect(this.cameraQuickAction).toBeVisible(); + return this.cameraQuickAction; + } +} + +const cameraQuickAction = new CameraQuickAction(); +export default cameraQuickAction; diff --git a/detox/e2e/support/ui/component/file_quick_action.js b/detox/e2e/support/ui/component/file_quick_action.js new file mode 100644 index 000000000..410e1bdad --- /dev/null +++ b/detox/e2e/support/ui/component/file_quick_action.js @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class FileQuickAction { + testID = { + fileQuickAction: 'post_draft.file_quick_action', + fileQuickActionDisabled: 'post_draft.file_quick_action.disabled', + } + + fileQuickAction = element(by.id(this.testID.fileQuickAction)); + fileQuickActionDisabled = element(by.id(this.testID.fileQuickActionDisabled)); + + toBeVisible = async (options = {disabled: false}) => { + if (options.disabled) { + await expect(this.fileQuickActionDisabled).toBeVisible(); + return this.fileQuickActionDisabled; + } + + await expect(this.fileQuickAction).toBeVisible(); + return this.fileQuickAction; + } +} + +const fileQuickAction = new FileQuickAction(); +export default fileQuickAction; diff --git a/detox/e2e/support/ui/component/image_quick_action.js b/detox/e2e/support/ui/component/image_quick_action.js new file mode 100644 index 000000000..b041ffec2 --- /dev/null +++ b/detox/e2e/support/ui/component/image_quick_action.js @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class ImageQuickAction { + testID = { + imageQuickAction: 'post_draft.image_quick_action', + imageQuickActionDisabled: 'post_draft.image_quick_action.disabled', + } + + imageQuickAction = element(by.id(this.testID.imageQuickAction)); + imageQuickActionDisabled = element(by.id(this.testID.imageQuickActionDisabled)); + + toBeVisible = async (options = {disabled: false}) => { + if (options.disabled) { + await expect(this.imageQuickActionDisabled).toBeVisible(); + return this.imageQuickActionDisabled; + } + + await expect(this.imageQuickAction).toBeVisible(); + return this.imageQuickAction; + } +} + +const imageQuickAction = new ImageQuickAction(); +export default imageQuickAction; diff --git a/detox/e2e/support/ui/component/index.js b/detox/e2e/support/ui/component/index.js index 4fdad11a5..9d41dc115 100644 --- a/detox/e2e/support/ui/component/index.js +++ b/detox/e2e/support/ui/component/index.js @@ -2,12 +2,18 @@ // See LICENSE.txt for license information. import Autocomplete from './autocomplete'; +import CameraQuickAction from './camera_quick_action'; +import FileQuickAction from './file_quick_action'; +import ImageQuickAction from './image_quick_action'; import MainSidebar from './main_sidebar'; import PostOptions from './post_options'; import SettingsSidebar from './settings_sidebar'; export { Autocomplete, + CameraQuickAction, + FileQuickAction, + ImageQuickAction, MainSidebar, PostOptions, SettingsSidebar, diff --git a/detox/e2e/test/files_and_attachments/disable_upload.e2e.js b/detox/e2e/test/files_and_attachments/disable_upload.e2e.js new file mode 100644 index 000000000..11aa83577 --- /dev/null +++ b/detox/e2e/test/files_and_attachments/disable_upload.e2e.js @@ -0,0 +1,43 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + CameraQuickAction, + FileQuickAction, + ImageQuickAction, +} from '@support/ui/component'; +import {ChannelScreen} from '@support/ui/screen'; +import { + Setup, + System, +} from '@support/server_api'; + +describe('Disable Upload', () => { + beforeAll(async () => { + // * Verify that the server has license + await System.apiRequireLicense(); + + // # Disable mobile upload + await System.apiUpdateConfig({FileSettings: {EnableMobileUpload: false}}); + + const {user} = await Setup.apiInit(); + await ChannelScreen.open(user); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T3453 should disable file, image, camera icons when mobile file upload is disabled', async () => { + // * Verify disabled file, image, camera icons are visible + await expect(CameraQuickAction.cameraQuickActionDisabled).toBeVisible(); + await expect(FileQuickAction.fileQuickActionDisabled).toBeVisible(); + await expect(ImageQuickAction.imageQuickActionDisabled).toBeVisible(); + }); +}); diff --git a/detox/e2e/test/files_and_attachments/enable_upload.e2e.js b/detox/e2e/test/files_and_attachments/enable_upload.e2e.js new file mode 100644 index 000000000..139e51db9 --- /dev/null +++ b/detox/e2e/test/files_and_attachments/enable_upload.e2e.js @@ -0,0 +1,43 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + CameraQuickAction, + FileQuickAction, + ImageQuickAction, +} from '@support/ui/component'; +import {ChannelScreen} from '@support/ui/screen'; +import { + Setup, + System, +} from '@support/server_api'; + +describe('Enable Upload', () => { + beforeAll(async () => { + // * Verify that the server has license + await System.apiRequireLicense(); + + // # Enable mobile upload + await System.apiUpdateConfig({FileSettings: {EnableMobileUpload: true}}); + + const {user} = await Setup.apiInit(); + await ChannelScreen.open(user); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T3483 should enable file, image, camera icons when mobile file upload is enabled', async () => { + // * Verify enabled file, image, camera icons are visible + await expect(CameraQuickAction.cameraQuickAction).toBeVisible(); + await expect(FileQuickAction.fileQuickAction).toBeVisible(); + await expect(ImageQuickAction.imageQuickAction).toBeVisible(); + }); +});