summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/components/Support.js136
-rw-r--r--app/containers/SupportPage.js21
-rw-r--r--test/components/Support.spec.js152
3 files changed, 171 insertions, 138 deletions
diff --git a/app/components/Support.js b/app/components/Support.js
index 7897f594ec..1f03d67375 100644
--- a/app/components/Support.js
+++ b/app/components/Support.js
@@ -6,14 +6,8 @@ import { Layout, Container } from './Layout';
import styles from './SupportStyles';
import Img from './Img';
-import type { AccountReduxState } from '../redux/account/reducers';
-
-export type SupportReport = {
- email: string,
- message: string,
- savedReport: ?string,
-};
-
+import type { AccountToken } from '../lib/daemon-rpc';
+import type { SupportReportForm } from '../redux/support/actions';
type SupportState = {
email: string,
message: string,
@@ -22,11 +16,15 @@ type SupportState = {
};
export type SupportProps = {
- account: AccountReduxState,
+ defaultEmail: string,
+ defaultMessage: string,
+ accountHistory: Array<AccountToken>,
onClose: () => void,
- onViewLog: (string) => void,
- onCollectLog: (Array<string>) => Promise<string>,
- onSend: (email: string, message: string, savedReport: string) => void,
+ viewLog: (path: string) => void,
+ saveReportForm: (form: SupportReportForm) => void,
+ clearReportForm: () => void,
+ collectProblemReport: (accountsToRedact: Array<string>) => Promise<string>,
+ sendProblemReport: (email: string, message: string, savedReport: string) => Promise<void>,
};
export default class Support extends Component<SupportProps, SupportState> {
@@ -37,68 +35,102 @@ export default class Support extends Component<SupportProps, SupportState> {
sendState: 'INITIAL',
};
+ _collectLogPromise: ?Promise<string>;
+
+ constructor(props: SupportProps) {
+ super(props);
+
+ // seed initial data from props
+ this.state.email = props.defaultEmail;
+ this.state.message = props.defaultMessage;
+ }
+
validate() {
return this.state.message.trim().length > 0;
}
onChangeEmail = (email: string) => {
- this.setState({ email: email });
+ this.setState({ email: email }, () => {
+ this._saveFormData();
+ });
};
onChangeDescription = (description: string) => {
- this.setState({ message: description });
+ this.setState({ message: description }, () => {
+ this._saveFormData();
+ });
};
- onViewLog = () => {
- this._getLog().then((path) => {
- this.props.onViewLog(path);
- });
+ onViewLog = async (): Promise<void> => {
+ try {
+ const reportPath = await this._collectLog();
+ this.props.viewLog(reportPath);
+ } catch (error) {
+ // TODO: handle error
+ }
};
- _getLog(): Promise<string> {
- const accountsToRedact = this.props.account.accountHistory;
- const { savedReport } = this.state;
- return savedReport
- ? Promise.resolve(savedReport)
- : this.props.onCollectLog(accountsToRedact).then((path) => {
- return new Promise((resolve) =>
- this.setState({ savedReport: path }, () => resolve(path)),
- );
+ _saveFormData() {
+ this.props.saveReportForm({
+ email: this.state.email,
+ message: this.state.message,
+ });
+ }
+
+ async _collectLog(): Promise<string> {
+ if (this._collectLogPromise) {
+ return this._collectLogPromise;
+ } else {
+ const collectPromise = this.props.collectProblemReport(this.props.accountHistory);
+
+ // save promise to prevent subsequent requests
+ this._collectLogPromise = collectPromise;
+
+ try {
+ const reportPath = await collectPromise;
+ return new Promise((resolve) => {
+ this.setState({ savedReport: reportPath }, () => resolve(reportPath));
});
+ } catch (error) {
+ this._collectLogPromise = null;
+
+ throw error;
+ }
+ }
}
- onSend = () => {
+ onSend = async (): Promise<void> => {
if (this.state.sendState === 'INITIAL' && this.state.email.length === 0) {
- this.setState({
- sendState: 'CONFIRM_NO_EMAIL',
+ return new Promise((resolve) => {
+ this.setState({ sendState: 'CONFIRM_NO_EMAIL' }, () => resolve());
});
} else {
- this._sendProblemReport();
+ try {
+ await this._sendReport();
+ } catch (error) {
+ // No-op
+ }
}
};
- _sendProblemReport() {
- this.setState(
- {
- sendState: 'LOADING',
- },
- () => {
- this._getLog()
- .then((path) => {
- return this.props.onSend(this.state.email, this.state.message, path);
- })
- .then(() => {
- this.setState({
- sendState: 'SUCCESS',
- });
- })
- .catch(() => {
- this.setState({
- sendState: 'FAILED',
- });
+ _sendReport(): Promise<void> {
+ return new Promise((resolve, reject) => {
+ this.setState({ sendState: 'LOADING' }, async () => {
+ try {
+ const { email, message } = this.state;
+ const reportPath = await this._collectLog();
+ await this.props.sendProblemReport(email, message, reportPath);
+ this.props.clearReportForm();
+ this.setState({ sendState: 'SUCCESS' }, () => {
+ resolve();
});
- },
- );
+ } catch (error) {
+ this.setState({ sendState: 'FAILED' }, () => {
+ reject(error);
+ });
+ }
+ });
+ });
}
render() {
diff --git a/app/containers/SupportPage.js b/app/containers/SupportPage.js
index d9ce8ad6ec..c80e9508de 100644
--- a/app/containers/SupportPage.js
+++ b/app/containers/SupportPage.js
@@ -8,26 +8,25 @@ import { collectProblemReport, sendProblemReport } from '../lib/problem-report';
import type { ReduxState, ReduxDispatch } from '../redux/store';
import type { SharedRouteProps } from '../routes';
+import supportActions from '../redux/support/actions';
const mapStateToProps = (state: ReduxState) => ({
- account: state.account,
+ defaultEmail: state.support.email,
+ defaultMessage: state.support.message,
+ accountHistory: state.account.accountHistory,
});
const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) => {
+ const { saveReportForm, clearReportForm } = bindActionCreators(supportActions, dispatch);
const { push: pushHistory } = bindActionCreators({ push }, dispatch);
return {
onClose: () => pushHistory('/settings'),
-
- onCollectLog: (toRedact) => {
- return collectProblemReport(toRedact);
- },
-
- onViewLog: (path) => openItem(path),
-
- onSend: (email, message, savedReport) => {
- return sendProblemReport(email, message, savedReport);
- },
+ viewLog: (path) => openItem(path),
+ saveReportForm,
+ clearReportForm,
+ collectProblemReport,
+ sendProblemReport,
};
};
diff --git a/test/components/Support.spec.js b/test/components/Support.spec.js
index ce1efb2b75..59ddc25606 100644
--- a/test/components/Support.spec.js
+++ b/test/components/Support.spec.js
@@ -6,113 +6,115 @@ import { shallow } from 'enzyme';
import type { SupportProps } from '../../app/components/Support';
describe('components/Support', () => {
- const makeProps = (mergeProps: $Shape<SupportProps> = {}): SupportProps => {
- const defaultProps: SupportProps = {
- account: {
- accountToken: null,
- accountHistory: [],
- error: null,
- expiry: null,
- status: 'none',
- },
- onClose: () => {},
- onViewLog: (_path) => {},
- onCollectLog: () => Promise.resolve('/tmp/mullvad_problem_report.log'),
- onSend: (_report) => {},
- };
- return Object.assign({}, defaultProps, mergeProps);
- };
+ it('should call close callback', () => {
+ const props = makeProps({ onClose: spy() });
+ const component = shallow(<Support {...props} />);
+ const closeButton = component.find({ testName: 'support__close' });
- it('should call close callback', (done) => {
- const props = makeProps({
- onClose: () => done(),
- });
- const component = getComponent(render(props), 'support__close');
- click(component);
+ click(closeButton);
+ expect(props.onClose).to.have.been.called.once;
});
- it('should call view logs callback', (done) => {
- const props = makeProps({
- onViewLog: (_path) => done(),
- });
- const component = getComponent(render(props), 'support__view_logs');
- click(component);
+ it('should call view logs callback', async () => {
+ const props = makeProps({ viewLog: spy() });
+ const component = shallow(<Support {...props} />);
+ const viewButton = component.find({ testName: 'support__view_logs' });
+
+ await click(viewButton);
+ expect(props.viewLog).to.have.been.called.once;
});
- it('should call send callback when description filled in', (done) => {
+ it('should call send callback when description filled in', async () => {
const props = makeProps({
- onSend: (_report) => done(),
+ defaultEmail: 'foo',
+ defaultMessage: 'abc',
+ sendProblemReport: spy((_report) => Promise.resolve()),
});
+ const component = shallow(<Support {...props} />);
+ const sendButton = component.find({ testName: 'support__send_logs' });
- const component = render(props);
- component.setState({ message: 'abc', email: 'foo' });
-
- const sendButton = getComponent(component, 'support__send_logs');
expect(sendButton.prop('disabled')).to.be.false;
- click(sendButton);
+ await click(sendButton);
+ expect(props.sendProblemReport).to.have.been.called.once;
});
it('should not call send callback when description is empty', () => {
- const component = render(makeProps());
- component.setState({ message: '' });
+ const props = makeProps({ defaultMessage: '' });
+ const component = shallow(<Support {...props} />);
+ const sendButton = component.find({ testName: 'support__send_logs' });
- const sendButton = getComponent(render(makeProps()), 'support__send_logs');
expect(sendButton.prop('disabled')).to.be.true;
});
- it('should not collect report twice', (done) => {
- const collectCallback = spy(() => Promise.resolve('non-falsy'));
+ it('should not collect report twice', async () => {
const props = makeProps({
- onCollectLog: collectCallback,
+ collectProblemReport: spy(() => Promise.resolve('/path/to/problem/report')),
});
+ const component = shallow(<Support {...props} />);
+ const viewButton = component.find({ testName: 'support__view_logs' });
- const viewLogButton = getComponent(render(props), 'support__view_logs');
- click(viewLogButton);
+ await Promise.all([click(viewButton), click(viewButton)]);
+ expect(props.collectProblemReport).to.have.been.called.once;
+ });
- setTimeout(() => {
- click(viewLogButton);
+ it('should collect report on submission', async () => {
+ const props = makeProps({
+ defaultMessage: '',
+ defaultEmail: 'foo',
+ collectProblemReport: spy(() => Promise.resolve('/path/to/problem/report')),
+ sendProblemReport: spy(() => Promise.resolve()),
});
+ const component = shallow(<Support {...props} />);
+ const sendButton = component.find({ testName: 'support__send_logs' });
- setTimeout(() => {
- try {
- expect(collectCallback).to.have.been.called.once;
- done();
- } catch (e) {
- done(e);
- }
- });
+ await click(sendButton);
+ expect(props.collectProblemReport).to.have.been.called.once;
+ expect(props.sendProblemReport).to.have.been.called.once;
});
- it('should collect report on submission', (done) => {
- const collectCallback = spy(() => Promise.resolve(''));
+ it('should save the report form on change', () => {
const props = makeProps({
- onCollectLog: collectCallback,
- onSend: (_report) => {
- try {
- expect(collectCallback).to.have.been.called.once;
- done();
- } catch (e) {
- done(e);
- }
- },
+ defaultEmail: 'email@domain',
+ defaultMessage: 'test message',
+ sendProblemReport: () => Promise.reject(new Error('Simulation')),
+ saveReportForm: spy(),
});
+ const component = shallow(<Support {...props} />);
+ const input = component.find({ testName: 'support__form_message' });
+ input.simulate('changeText', 'new message');
+ expect(props.saveReportForm).to.have.been.called.once;
+ });
- const component = render(props);
- component.setState({ message: '', email: 'foo' });
+ it('should clear the report form upon successful submission', async () => {
+ const props = makeProps({
+ defaultEmail: 'email@domain',
+ defaultMessage: 'test message',
+ sendProblemReport: () => Promise.resolve(),
+ clearReportForm: spy(),
+ });
+ const component = shallow(<Support {...props} />);
+ const sendButton = component.find({ testName: 'support__send_logs' });
- const sendButton = getComponent(component, 'support__send_logs');
- click(sendButton);
+ await click(sendButton);
+ expect(props.clearReportForm).to.have.been.called.once;
});
});
-function render(props) {
- return shallow(<Support {...props} />);
-}
-
-function getComponent(container, testName) {
- return container.findWhere((n) => n.prop('testName') === testName);
+function makeProps(mergeProps: $Shape<SupportProps> = {}): SupportProps {
+ const defaultProps: SupportProps = {
+ defaultEmail: '',
+ defaultMessage: '',
+ accountHistory: [],
+ onClose: () => {},
+ viewLog: (_path) => {},
+ collectProblemReport: () => Promise.resolve('/path/to/problem/report'),
+ sendProblemReport: (_report) => Promise.resolve(),
+ saveReportForm: (_form) => {},
+ clearReportForm: () => {},
+ };
+ return { ...defaultProps, ...mergeProps };
}
function click(component) {
- component.prop('onPress')();
+ return component.prop('onPress')();
}