MM-22090 Fix iOS photo/camera denied permissions (#3860)

* Fix iOS photo/camera denied permissions

* Add unit test and rename files

* Request for permission if returns denied
This commit is contained in:
Elias Nahum 2020-01-28 16:53:14 -03:00 committed by GitHub
parent 99a551df6d
commit ee25cf49eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 290 additions and 38 deletions

View file

@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CameraButton should match snapshot 1`] = `
<TouchableWithFeedbackIOS
onPress={[Function]}
style={Object {}}
type="opacity"
>
<Icon
allowFontScaling={false}
color="rgba(61,60,64,0.64)"
name="camera-outline"
size={24}
/>
</TouchableWithFeedbackIOS>
`;

View file

@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FileUploadButton should match snapshot 1`] = `
<TouchableWithFeedbackIOS
onPress={[Function]}
style={Object {}}
type="opacity"
>
<Icon
allowFontScaling={false}
color="rgba(61,60,64,0.64)"
name="file-document-outline"
size={24}
/>
</TouchableWithFeedbackIOS>
`;

View file

@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ImageUploadButton should match snapshot 1`] = `
<TouchableWithFeedbackIOS
onPress={[Function]}
style={Object {}}
type="opacity"
>
<Icon
allowFontScaling={false}
color="rgba(61,60,64,0.64)"
name="image-outline"
size={24}
/>
</TouchableWithFeedbackIOS>
`;

View file

@ -107,9 +107,10 @@ export default class AttachmentButton extends PureComponent {
const {formatMessage} = this.context.intl;
let permissionRequest;
const targetSource = Permissions.PERMISSIONS.IOS.CAMERA;
const hasPermissionToStorage = await Permissions.check(targetSource);
const hasPermission = await Permissions.check(targetSource);
switch (hasPermissionToStorage) {
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE:
permissionRequest = await Permissions.request(targetSource);
if (permissionRequest !== Permissions.RESULTS.AUTHORIZED) {
@ -117,17 +118,13 @@ export default class AttachmentButton extends PureComponent {
}
break;
case Permissions.RESULTS.BLOCKED: {
const canOpenSettings = await Permissions.canOpenSettings();
let grantOption = null;
if (canOpenSettings) {
grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
}
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = this.getPermissionDeniedMessage();

View file

@ -0,0 +1,66 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Alert} from 'react-native';
import {shallow} from 'enzyme';
import Permissions from 'react-native-permissions';
import Preferences from 'mattermost-redux/constants/preferences';
import CameraButton from './camera_button';
jest.mock('react-intl');
jest.mock('react-native-image-picker', () => ({
launchCamera: jest.fn(),
}));
describe('CameraButton', () => {
const formatMessage = jest.fn();
const baseProps = {
fileCount: 0,
maxFileCount: 5,
onShowFileMaxWarning: jest.fn(),
theme: Preferences.THEMES.default,
uploadFiles: jest.fn(),
buttonContainerStyle: {},
};
test('should match snapshot', () => {
const wrapper = shallow(<CameraButton {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should return permission false if permission is denied in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.UNAVAILABLE);
jest.spyOn(Permissions, 'request').mockReturnValue(Permissions.RESULTS.DENIED);
const wrapper = shallow(
<CameraButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
const hasPermission = await wrapper.instance().hasCameraPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).toHaveBeenCalled();
expect(Alert.alert).not.toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('should show permission denied alert and return permission false if permission is blocked in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.BLOCKED);
jest.spyOn(Alert, 'alert').mockReturnValue(true);
const wrapper = shallow(
<CameraButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
const hasPermission = await wrapper.instance().hasCameraPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).not.toHaveBeenCalled();
expect(Alert.alert).toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
});

View file

@ -100,6 +100,7 @@ export default class FileUploadButton extends PureComponent {
const hasPermissionToStorage = await Permissions.check(storagePermission);
switch (hasPermissionToStorage) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE:
permissionRequest = await Permissions.request(storagePermission);
if (permissionRequest !== Permissions.RESULTS.AUTHORIZED) {

View file

@ -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, Platform} from 'react-native';
import {shallow} from 'enzyme';
import Permissions from 'react-native-permissions';
import Preferences from 'mattermost-redux/constants/preferences';
import FileUploadButton from './file_upload_button';
jest.mock('react-intl');
jest.mock('react-native-image-picker', () => ({
launchCamera: jest.fn(),
}));
describe('FileUploadButton', () => {
const formatMessage = jest.fn();
const baseProps = {
blurTextBox: jest.fn(),
browseFileTypes: '*',
fileCount: 0,
maxFileCount: 5,
onShowFileMaxWarning: jest.fn(),
theme: Preferences.THEMES.default,
uploadFiles: jest.fn(),
buttonContainerStyle: {},
};
beforeAll(() => {
Platform.OS = 'android';
});
afterAll(() => {
Platform.OS = 'ios';
});
test('should match snapshot', () => {
const wrapper = shallow(<FileUploadButton {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should return permission false if permission is denied in Android', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.UNAVAILABLE);
jest.spyOn(Permissions, 'request').mockReturnValue(Permissions.RESULTS.DENIED);
const wrapper = shallow(
<FileUploadButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
const hasPermission = await wrapper.instance().hasStoragePermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).toHaveBeenCalled();
expect(Alert.alert).not.toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('should show permission denied alert and return permission false if permission is blocked in Android', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.BLOCKED);
jest.spyOn(Alert, 'alert').mockReturnValue(true);
const wrapper = shallow(
<FileUploadButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
const hasPermission = await wrapper.instance().hasStoragePermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).not.toHaveBeenCalled();
expect(Alert.alert).toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
});

View file

@ -109,9 +109,10 @@ export default class ImageUploadButton extends PureComponent {
const {formatMessage} = this.context.intl;
let permissionRequest;
const targetSource = Permissions.PERMISSIONS.IOS.PHOTO_LIBRARY;
const hasPermissionToStorage = await Permissions.check(targetSource);
const hasPermission = await Permissions.check(targetSource);
switch (hasPermissionToStorage) {
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE:
permissionRequest = await Permissions.request(targetSource);
if (permissionRequest !== Permissions.RESULTS.AUTHORIZED) {
@ -119,17 +120,13 @@ export default class ImageUploadButton extends PureComponent {
}
break;
case Permissions.RESULTS.BLOCKED: {
const canOpenSettings = await Permissions.canOpenSettings();
let grantOption = null;
if (canOpenSettings) {
grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
}
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = this.getPermissionDeniedMessage();

View file

@ -0,0 +1,67 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Alert} from 'react-native';
import {shallow} from 'enzyme';
import Permissions from 'react-native-permissions';
import Preferences from 'mattermost-redux/constants/preferences';
import ImageUploadButton from './image_upload_button';
jest.mock('react-intl');
jest.mock('react-native-image-picker', () => ({
launchCamera: jest.fn(),
}));
describe('ImageUploadButton', () => {
const formatMessage = jest.fn();
const baseProps = {
blurTextBox: jest.fn(),
fileCount: 0,
maxFileCount: 5,
onShowFileMaxWarning: jest.fn(),
theme: Preferences.THEMES.default,
uploadFiles: jest.fn(),
buttonContainerStyle: {},
};
test('should match snapshot', () => {
const wrapper = shallow(<ImageUploadButton {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should return permission false if permission is denied in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.UNAVAILABLE);
jest.spyOn(Permissions, 'request').mockReturnValue(Permissions.RESULTS.DENIED);
const wrapper = shallow(
<ImageUploadButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
const hasPermission = await wrapper.instance().hasPhotoPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).toHaveBeenCalled();
expect(Alert.alert).not.toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
test('should show permission denied alert and return permission false if permission is blocked in iOS', async () => {
jest.spyOn(Permissions, 'check').mockReturnValue(Permissions.RESULTS.BLOCKED);
jest.spyOn(Alert, 'alert').mockReturnValue(true);
const wrapper = shallow(
<ImageUploadButton {...baseProps}/>,
{context: {intl: {formatMessage}}},
);
const hasPermission = await wrapper.instance().hasPhotoPermission();
expect(Permissions.check).toHaveBeenCalled();
expect(Permissions.request).not.toHaveBeenCalled();
expect(Alert.alert).toHaveBeenCalled();
expect(hasPermission).toBe(false);
});
});

View file

@ -14,9 +14,9 @@ import PasteableTextInput from 'app/components/pasteable_text_input';
import EphemeralStore from 'app/store/ephemeral_store';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import FileUploadButton from './components/fileUploadButton';
import ImageUploadButton from './components/imageUploadButton';
import CameraButton from './components/cameraButton';
import FileUploadButton from './components/file_upload_button';
import ImageUploadButton from './components/image_upload_button';
import CameraButton from './components/camera_button';
import PostTextbox from './post_textbox.ios';

View file

@ -28,9 +28,9 @@ import {General, RequestStatus} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
import FileUploadButton from './components/fileUploadButton';
import ImageUploadButton from './components/imageUploadButton';
import CameraButton from './components/cameraButton';
import FileUploadButton from './components/file_upload_button';
import ImageUploadButton from './components/image_upload_button';
import CameraButton from './components/camera_button';
import FormattedMarkdownText from 'app/components/formatted_markdown_text';
import FormattedText from 'app/components/formatted_text';
import PasteableTextInput from 'app/components/pasteable_text_input';

View file

@ -119,9 +119,9 @@ module.exports = [
'app/components/post_list/post_list.js',
'app/components/post_profile_picture/index.js',
'app/components/post_profile_picture/post_profile_picture.js',
'app/components/post_textbox/components/cameraButton.js',
'app/components/post_textbox/components/fileUploadButton.js',
'app/components/post_textbox/components/imageUploadButton.js',
'app/components/post_textbox/components/camera_button.js',
'app/components/post_textbox/components/file_upload_button.js',
'app/components/post_textbox/components/image_upload_button.js',
'app/components/post_textbox/components/typing/index.js',
'app/components/post_textbox/components/typing/typing.js',
'app/components/post_textbox/index.js',

View file

@ -91,9 +91,9 @@ module.exports = [
'./node_modules/app/components/post_list/post_list.js',
'./node_modules/app/components/post_profile_picture/index.js',
'./node_modules/app/components/post_profile_picture/post_profile_picture.js',
'./node_modules/app/components/post_textbox/components/cameraButton.js',
'./node_modules/app/components/post_textbox/components/fileUploadButton.js',
'./node_modules/app/components/post_textbox/components/imageUploadButton.js',
'./node_modules/app/components/post_textbox/components/camera_button.js',
'./node_modules/app/components/post_textbox/components/file_upload_button.js',
'./node_modules/app/components/post_textbox/components/image_upload_button.js',
'./node_modules/app/components/post_textbox/components/typing/index.js',
'./node_modules/app/components/post_textbox/components/typing/typing.js',
'./node_modules/app/components/post_textbox/index.js',