...
 
Commits (2)
import {Alert} from 'react-native';
import remoteAction from '../../src/common/RemoteAction';
import connectivityService from '../../src/common/services/connectivity.service';
import { ApiError } from '../../src/common/services/api.service';
jest.mock('../../src/common/services/connectivity.service');
describe('remote action', () => {
Alert.alert = jest.fn();
beforeEach(() => {
Alert.alert.mockClear();
connectivityService.isConnected = true;
});
it('should not auto retry on generic error', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new Error('boom');
});
await remoteAction(action);
// should have been called
expect(action).toHaveBeenCalledTimes(1);
});
it('should auto retry on net error', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new TypeError('Network request failed');
});
await remoteAction(action);
// should have been called
expect(action).toHaveBeenCalledTimes(2);
});
it('should auto retry on net error n times', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new TypeError('Network request failed');
});
await remoteAction(action, '', 2);
// should have been called
expect(action).toHaveBeenCalledTimes(3);
});
it('should stop auto retry on success', async () => {
const action = jest.fn();
let tries = 0;
action.mockImplementation(async () => {
tries++;
if (tries > 1) {
return;
}
throw new TypeError('Network request failed');
});
await remoteAction(action, '', 2);
// should have been called
expect(action).toHaveBeenCalledTimes(2);
});
it('should show offline error message', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new TypeError('Network request failed');
});
connectivityService.isConnected = false;
await remoteAction(action, '', 0);
// should have been called
expect(action).toHaveBeenCalledTimes(1);
// should call alert with the correct messages
expect(Alert.alert.mock.calls[0][0]).toBe('Sorry!');
expect(Alert.alert.mock.calls[0][1]).toBe('No Internet Connection');
expect(Alert.alert.mock.calls[0][2][0].text).toBe('Ok');
expect(Alert.alert.mock.calls[0][2][1].text).toBe('Try again');
});
it('should show api errors message', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new ApiError('Some Error');
});
await remoteAction(action, '', 0);
// should have been called
expect(action).toHaveBeenCalledTimes(1);
// should call alert with the correct messages
expect(Alert.alert.mock.calls[0][0]).toBe('Sorry!');
expect(Alert.alert.mock.calls[0][1]).toBe('Some Error');
expect(Alert.alert.mock.calls[0][2][0].text).toBe('Ok');
expect(Alert.alert.mock.calls[0][2][1].text).toBe('Try again');
});
it('should show error message with retry on failure', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new TypeError('Network request failed');
});
await remoteAction(action, '', 0);
// should have been called
expect(action).toHaveBeenCalledTimes(1);
// should call alert with the correct messages
expect(Alert.alert.mock.calls[0][0]).toBe('Sorry!');
expect(Alert.alert.mock.calls[0][1]).toBe('Can\'t reach the server');
expect(Alert.alert.mock.calls[0][2][0].text).toBe('Ok');
expect(Alert.alert.mock.calls[0][2][1].text).toBe('Try again');
});
it('should call the action again if the user tap retry', async () => {
const action = jest.fn();
action.mockImplementation(async () => {
throw new TypeError('Network request failed');
});
await remoteAction(action, '', 0);
// should have been called
expect(action).toHaveBeenCalledTimes(1);
Alert.alert.mock.calls[0][2][1].onPress();
// should have been called again
expect(action).toHaveBeenCalledTimes(2);
});
});
import connectivityService from './services/connectivity.service';
import {isNetworkFail} from './helpers/abortableFetch';
import i18nService from './services/i18n.service';
import {Alert} from 'react-native';
import { isApiError } from './services/api.service';
/**
* Remote action with auto and manual retry
*
* @param {function} action async function that runs the action
* @param {string} actionName translation term (optional)
* @param {number} retries number of auto-retries (0 for no auto retry)
*/
async function remoteAction(action, actionName = '', retries = 1) {
try {
await action();
} catch (error) {
let message;
if (isNetworkFail(error)) {
if (retries > 0) {
remoteAction(action, actionName, --retries);
return;
}
message = connectivityService.isConnected
? i18nService.t('cantReachServer')
: i18nService.t('noInternet');
} else if (isApiError(error)) {
message = error.message;
} else {
message = i18nService.t('errorMessage');
}
if (actionName) {
message = i18nService.t(actionName) + '\n' + message;
}
Alert.alert(
i18nService.t('sorry'),
message,
[
{text: i18nService.t('ok')},
{
text: i18nService.t('tryAgain'),
onPress: () => remoteAction(action, actionName, retries),
},
],
{cancelable: true},
);
}
}
export default remoteAction;
import { jsxEmptyExpression } from "@babel/types";
export default {
connectionInfo : {
connectionInfo: {
type: 'unknown',
effectiveType: 'unknown',
},
isConnected: true,
init: jsxEmptyExpression.fn(),
}
\ No newline at end of file
init: jest.fn(),
};
import api from './../../common/services/api.service';
import logService from './log.service';
import i18n from './i18n.service';
import { UserError } from '../UserError';
export function vote(guid, direction, data) {
return api.put('api/v1/votes/' + guid + '/' + direction, data)
.then((data) => {
return { data }
})
.catch(err => {
logService.exception('[VotesService]', err);
throw new UserError(i18n.t('errorMessage'));
})
/**
* Vote an activity
* @param {string} guid
* @param {string} direction up|down
* @param {*} data extra data
*/
export async function vote(guid, direction, data) {
const response = await api.put(
'api/v1/votes/' + guid + '/' + direction,
data,
);
return {data: response};
}
......@@ -22,6 +22,7 @@ import testID from '../../../common/helpers/testID';
import i18n from '../../../common/services/i18n.service';
import logService from '../../../common/services/log.service';
import { FLAG_VOTE } from '../../../common/Permissions';
import remoteAction from '../../../common/RemoteAction';
// prevent double tap in touchable
const TouchableOpacityCustom = withPreventDoubleTap(TouchableOpacity);
......@@ -82,22 +83,12 @@ class ThumbUpAction extends Component {
* Toggle thumb
*/
toggleThumb = async () => {
if (!this.props.entity.can(FLAG_VOTE, true)) return;
try {
await this.props.entity.toggleVote(this.direction);
} catch (err) {
logService.exception(`[Thumb${this.direction}Action]`, err)
Alert.alert(
i18n.t('sorry'),
i18n.t('errorMessage') + '\n' + i18n.t('activity.tryAgain'),
[
{text: i18n.t('ok'), onPress: () => {}},
],
{ cancelable: false }
);
if (!this.props.entity.can(FLAG_VOTE, true)) {
return;
}
}
remoteAction(async () => {
await this.props.entity.toggleVote(this.direction);
});
};
}