Commit adef44af authored by Michael Lunøe's avatar Michael Lunøe Committed by David O'Regan

Feat(Purchase flow): Add steps error handling

parent dc0858ff
<script> <script>
import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql'; import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql';
import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { STEPS } from '../../constants'; import { STEPS } from '../../constants';
...@@ -18,7 +20,10 @@ export default { ...@@ -18,7 +20,10 @@ export default {
apollo: { apollo: {
isActive: { isActive: {
query: activeStepQuery, query: activeStepQuery,
update: ({ activeStep }) => activeStep.id === STEPS[3].id, update: ({ activeStep }) => activeStep?.id === STEPS[3].id,
error: (error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
},
}, },
}, },
computed: { computed: {
......
import Api from 'ee/api'; import Api from 'ee/api';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql'; import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
...@@ -169,9 +170,13 @@ export const fetchPaymentMethodDetails = ({ state, dispatch, commit }) => ...@@ -169,9 +170,13 @@ export const fetchPaymentMethodDetails = ({ state, dispatch, commit }) =>
export const fetchPaymentMethodDetailsSuccess = ({ commit }, creditCardDetails) => { export const fetchPaymentMethodDetailsSuccess = ({ commit }, creditCardDetails) => {
commit(types.UPDATE_CREDIT_CARD_DETAILS, creditCardDetails); commit(types.UPDATE_CREDIT_CARD_DETAILS, creditCardDetails);
defaultClient.mutate({ defaultClient
mutation: activateNextStepMutation, .mutate({
}); mutation: activateNextStepMutation,
})
.catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
});
}; };
export const fetchPaymentMethodDetailsError = () => { export const fetchPaymentMethodDetailsError = () => {
......
...@@ -4,7 +4,9 @@ import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutati ...@@ -4,7 +4,9 @@ import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutati
import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql'; import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql';
import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql'; import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql';
import stepListQuery from 'ee/vue_shared/purchase_flow/graphql/queries/step_list.query.graphql'; import stepListQuery from 'ee/vue_shared/purchase_flow/graphql/queries/step_list.query.graphql';
import createFlash from '~/flash';
import { convertToSnakeCase, dasherize } from '~/lib/utils/text_utility'; import { convertToSnakeCase, dasherize } from '~/lib/utils/text_utility';
import { GENERAL_ERROR_MESSAGE } from '../constants';
import StepHeader from './step_header.vue'; import StepHeader from './step_header.vue';
import StepSummary from './step_summary.vue'; import StepSummary from './step_summary.vue';
...@@ -44,6 +46,9 @@ export default { ...@@ -44,6 +46,9 @@ export default {
apollo: { apollo: {
activeStep: { activeStep: {
query: activeStepQuery, query: activeStepQuery,
error(error) {
this.handleError(error);
},
}, },
stepList: { stepList: {
query: stepListQuery, query: stepListQuery,
...@@ -66,6 +71,9 @@ export default { ...@@ -66,6 +71,9 @@ export default {
}, },
}, },
methods: { methods: {
handleError(error) {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
},
async nextStep() { async nextStep() {
if (!this.isValid) { if (!this.isValid) {
return; return;
...@@ -75,6 +83,9 @@ export default { ...@@ -75,6 +83,9 @@ export default {
.mutate({ .mutate({
mutation: activateNextStepMutation, mutation: activateNextStepMutation,
}) })
.catch((error) => {
this.handleError(error);
})
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
}); });
...@@ -86,6 +97,9 @@ export default { ...@@ -86,6 +97,9 @@ export default {
mutation: updateStepMutation, mutation: updateStepMutation,
variables: { id: this.stepId }, variables: { id: this.stepId },
}) })
.catch((error) => {
this.handleError(error);
})
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
}); });
......
import { s__ } from '~/locale';
export const GENERAL_ERROR_MESSAGE = s__(
'PurchaseStep|An error occured in the purchase step. If the problem persists please contact support@gitlab.com.',
);
---
title: Add error handling feedback inside purchase flow
merge_request: 58084
author:
type: changed
...@@ -6,8 +6,11 @@ import Api from 'ee/api'; ...@@ -6,8 +6,11 @@ import Api from 'ee/api';
import ConfirmOrder from 'ee/subscriptions/new/components/checkout/confirm_order.vue'; import ConfirmOrder from 'ee/subscriptions/new/components/checkout/confirm_order.vue';
import { STEPS } from 'ee/subscriptions/new/constants'; import { STEPS } from 'ee/subscriptions/new/constants';
import createStore from 'ee/subscriptions/new/store'; import createStore from 'ee/subscriptions/new/store';
import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql'; import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper'; import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
import flash from '~/flash';
jest.mock('~/flash');
describe('Confirm Order', () => { describe('Confirm Order', () => {
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -15,19 +18,11 @@ describe('Confirm Order', () => { ...@@ -15,19 +18,11 @@ describe('Confirm Order', () => {
localVue.use(VueApollo); localVue.use(VueApollo);
let wrapper; let wrapper;
let mockApolloProvider;
jest.mock('ee/api.js'); jest.mock('ee/api.js');
const store = createStore(); const store = createStore();
function activateStep(stepId) {
return mockApolloProvider.clients.defaultClient.mutate({
mutation: updateStepMutation,
variables: { id: stepId },
});
}
function createComponent(options = {}) { function createComponent(options = {}) {
return shallowMount(ConfirmOrder, { return shallowMount(ConfirmOrder, {
localVue, localVue,
...@@ -39,34 +34,34 @@ describe('Confirm Order', () => { ...@@ -39,34 +34,34 @@ describe('Confirm Order', () => {
const findConfirmButton = () => wrapper.find(GlButton); const findConfirmButton = () => wrapper.find(GlButton);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
beforeEach(() => {
mockApolloProvider = createMockApolloProvider(STEPS);
wrapper = createComponent({ apolloProvider: mockApolloProvider });
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('Active', () => { describe('Active', () => {
beforeEach(async () => { describe('when receiving proper step data', () => {
await activateStep(STEPS[3].id); beforeEach(async () => {
}); const mockApolloProvider = createMockApolloProvider(STEPS, 3);
wrapper = createComponent({ apolloProvider: mockApolloProvider });
});
it('button should be visible', () => { it('button should be visible', () => {
expect(findConfirmButton().exists()).toBe(true); expect(findConfirmButton().exists()).toBe(true);
}); });
it('shows the text "Confirm purchase"', () => { it('shows the text "Confirm purchase"', () => {
expect(findConfirmButton().text()).toBe('Confirm purchase'); expect(findConfirmButton().text()).toBe('Confirm purchase');
}); });
it('the loading indicator should not be visible', () => { it('the loading indicator should not be visible', () => {
expect(findLoadingIcon().exists()).toBe(false); expect(findLoadingIcon().exists()).toBe(false);
});
}); });
describe('Clicking the button', () => { describe('Clicking the button', () => {
beforeEach(() => { beforeEach(() => {
const mockApolloProvider = createMockApolloProvider(STEPS, 3);
wrapper = createComponent({ apolloProvider: mockApolloProvider });
Api.confirmOrder = jest.fn().mockReturnValue(new Promise(jest.fn())); Api.confirmOrder = jest.fn().mockReturnValue(new Promise(jest.fn()));
findConfirmButton().vm.$emit('click'); findConfirmButton().vm.$emit('click');
...@@ -84,11 +79,32 @@ describe('Confirm Order', () => { ...@@ -84,11 +79,32 @@ describe('Confirm Order', () => {
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
}); });
describe('when failing to receive step data', () => {
beforeEach(async () => {
const mockApolloProvider = createMockApolloProvider([]);
mockApolloProvider.clients.defaultClient.clearStore();
wrapper = createComponent({ apolloProvider: mockApolloProvider });
});
afterEach(() => {
flash.mockClear();
});
it('displays an error', () => {
expect(flash.mock.calls[0][0]).toMatchObject({
message: GENERAL_ERROR_MESSAGE,
captureError: true,
error: expect.any(Error),
});
});
});
}); });
describe('Inactive', () => { describe('Inactive', () => {
beforeEach(async () => { beforeEach(async () => {
await activateStep(STEPS[1].id); const mockApolloProvider = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApolloProvider });
}); });
it('button should not be visible', () => { it('button should not be visible', () => {
......
...@@ -3,6 +3,7 @@ import Api from 'ee/api'; ...@@ -3,6 +3,7 @@ import Api from 'ee/api';
import * as constants from 'ee/subscriptions/new/constants'; import * as constants from 'ee/subscriptions/new/constants';
import defaultClient from 'ee/subscriptions/new/graphql'; import defaultClient from 'ee/subscriptions/new/graphql';
import * as actions from 'ee/subscriptions/new/store/actions'; import * as actions from 'ee/subscriptions/new/store/actions';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql'; import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
...@@ -33,21 +34,20 @@ describe('Subscriptions Actions', () => { ...@@ -33,21 +34,20 @@ describe('Subscriptions Actions', () => {
}); });
describe('updateSelectedPlan', () => { describe('updateSelectedPlan', () => {
it('updates the selected plan', (done) => { it('updates the selected plan', async () => {
testAction( await testAction(
actions.updateSelectedPlan, actions.updateSelectedPlan,
'planId', 'planId',
{}, {},
[{ type: 'UPDATE_SELECTED_PLAN', payload: 'planId' }], [{ type: 'UPDATE_SELECTED_PLAN', payload: 'planId' }],
[], [],
done,
); );
}); });
}); });
describe('updateSelectedGroup', () => { describe('updateSelectedGroup', () => {
it('updates the selected group, resets the organization name and updates the number of users', (done) => { it('updates the selected group, resets the organization name and updates the number of users', async () => {
testAction( await testAction(
actions.updateSelectedGroup, actions.updateSelectedGroup,
'groupId', 'groupId',
{ selectedGroupUsers: 3 }, { selectedGroupUsers: 3 },
...@@ -57,191 +57,175 @@ describe('Subscriptions Actions', () => { ...@@ -57,191 +57,175 @@ describe('Subscriptions Actions', () => {
{ type: 'UPDATE_NUMBER_OF_USERS', payload: 3 }, { type: 'UPDATE_NUMBER_OF_USERS', payload: 3 },
], ],
[], [],
done,
); );
}); });
}); });
describe('toggleIsSetupForCompany', () => { describe('toggleIsSetupForCompany', () => {
it('toggles the isSetupForCompany value', (done) => { it('toggles the isSetupForCompany value', async () => {
testAction( await testAction(
actions.toggleIsSetupForCompany, actions.toggleIsSetupForCompany,
{}, {},
{ isSetupForCompany: true }, { isSetupForCompany: true },
[{ type: 'UPDATE_IS_SETUP_FOR_COMPANY', payload: false }], [{ type: 'UPDATE_IS_SETUP_FOR_COMPANY', payload: false }],
[], [],
done,
); );
}); });
}); });
describe('updateNumberOfUsers', () => { describe('updateNumberOfUsers', () => {
it('updates numberOfUsers to 0 when no value is provided', (done) => { it('updates numberOfUsers to 0 when no value is provided', async () => {
testAction( await testAction(
actions.updateNumberOfUsers, actions.updateNumberOfUsers,
null, null,
{}, {},
[{ type: 'UPDATE_NUMBER_OF_USERS', payload: 0 }], [{ type: 'UPDATE_NUMBER_OF_USERS', payload: 0 }],
[], [],
done,
); );
}); });
it('updates numberOfUsers when a value is provided', (done) => { it('updates numberOfUsers when a value is provided', async () => {
testAction( await testAction(
actions.updateNumberOfUsers, actions.updateNumberOfUsers,
2, 2,
{}, {},
[{ type: 'UPDATE_NUMBER_OF_USERS', payload: 2 }], [{ type: 'UPDATE_NUMBER_OF_USERS', payload: 2 }],
[], [],
done,
); );
}); });
}); });
describe('updateOrganizationName', () => { describe('updateOrganizationName', () => {
it('updates organizationName to the provided value', (done) => { it('updates organizationName to the provided value', async () => {
testAction( await testAction(
actions.updateOrganizationName, actions.updateOrganizationName,
'name', 'name',
{}, {},
[{ type: 'UPDATE_ORGANIZATION_NAME', payload: 'name' }], [{ type: 'UPDATE_ORGANIZATION_NAME', payload: 'name' }],
[], [],
done,
); );
}); });
}); });
describe('fetchCountries', () => { describe('fetchCountries', () => {
it('calls fetchCountriesSuccess with the returned data on success', (done) => { it('calls fetchCountriesSuccess with the returned data on success', async () => {
mock.onGet(countriesPath).replyOnce(200, ['Netherlands', 'NL']); mock.onGet(countriesPath).replyOnce(200, ['Netherlands', 'NL']);
testAction( await testAction(
actions.fetchCountries, actions.fetchCountries,
null, null,
{}, {},
[], [],
[{ type: 'fetchCountriesSuccess', payload: ['Netherlands', 'NL'] }], [{ type: 'fetchCountriesSuccess', payload: ['Netherlands', 'NL'] }],
done,
); );
}); });
it('calls fetchCountriesError on error', (done) => { it('calls fetchCountriesError on error', async () => {
mock.onGet(countriesPath).replyOnce(500); mock.onGet(countriesPath).replyOnce(500);
testAction(actions.fetchCountries, null, {}, [], [{ type: 'fetchCountriesError' }], done); await testAction(actions.fetchCountries, null, {}, [], [{ type: 'fetchCountriesError' }]);
}); });
}); });
describe('fetchCountriesSuccess', () => { describe('fetchCountriesSuccess', () => {
it('transforms and adds fetched countryOptions', (done) => { it('transforms and adds fetched countryOptions', async () => {
testAction( await testAction(
actions.fetchCountriesSuccess, actions.fetchCountriesSuccess,
[['Netherlands', 'NL']], [['Netherlands', 'NL']],
{}, {},
[{ type: 'UPDATE_COUNTRY_OPTIONS', payload: [{ text: 'Netherlands', value: 'NL' }] }], [{ type: 'UPDATE_COUNTRY_OPTIONS', payload: [{ text: 'Netherlands', value: 'NL' }] }],
[], [],
done,
); );
}); });
it('adds an empty array when no data provided', (done) => { it('adds an empty array when no data provided', async () => {
testAction( await testAction(
actions.fetchCountriesSuccess, actions.fetchCountriesSuccess,
undefined, undefined,
{}, {},
[{ type: 'UPDATE_COUNTRY_OPTIONS', payload: [] }], [{ type: 'UPDATE_COUNTRY_OPTIONS', payload: [] }],
[], [],
done,
); );
}); });
}); });
describe('fetchCountriesError', () => { describe('fetchCountriesError', () => {
it('creates a flash', (done) => { it('creates a flash', async () => {
testAction(actions.fetchCountriesError, null, {}, [], [], () => { await testAction(actions.fetchCountriesError, null, {}, [], []);
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to load countries. Please try again.', message: 'Failed to load countries. Please try again.',
});
done();
}); });
}); });
}); });
describe('fetchStates', () => { describe('fetchStates', () => {
it('calls resetStates and fetchStatesSuccess with the returned data on success', (done) => { it('calls resetStates and fetchStatesSuccess with the returned data on success', async () => {
mock mock
.onGet(countryStatesPath, { params: { country: 'NL' } }) .onGet(countryStatesPath, { params: { country: 'NL' } })
.replyOnce(200, { utrecht: 'UT' }); .replyOnce(200, { utrecht: 'UT' });
testAction( await testAction(
actions.fetchStates, actions.fetchStates,
null, null,
{ country: 'NL' }, { country: 'NL' },
[], [],
[{ type: 'resetStates' }, { type: 'fetchStatesSuccess', payload: { utrecht: 'UT' } }], [{ type: 'resetStates' }, { type: 'fetchStatesSuccess', payload: { utrecht: 'UT' } }],
done,
); );
}); });
it('only calls resetStates when no country selected', (done) => { it('only calls resetStates when no country selected', async () => {
mock.onGet(countryStatesPath).replyOnce(500); mock.onGet(countryStatesPath).replyOnce(500);
testAction(actions.fetchStates, null, { country: null }, [], [{ type: 'resetStates' }], done); await testAction(actions.fetchStates, null, { country: null }, [], [{ type: 'resetStates' }]);
}); });
it('calls resetStates and fetchStatesError on error', (done) => { it('calls resetStates and fetchStatesError on error', async () => {
mock.onGet(countryStatesPath).replyOnce(500); mock.onGet(countryStatesPath).replyOnce(500);
testAction( await testAction(
actions.fetchStates, actions.fetchStates,
null, null,
{ country: 'NL' }, { country: 'NL' },
[], [],
[{ type: 'resetStates' }, { type: 'fetchStatesError' }], [{ type: 'resetStates' }, { type: 'fetchStatesError' }],
done,
); );
}); });
}); });
describe('fetchStatesSuccess', () => { describe('fetchStatesSuccess', () => {
it('transforms and adds received stateOptions', (done) => { it('transforms and adds received stateOptions', async () => {
testAction( await testAction(
actions.fetchStatesSuccess, actions.fetchStatesSuccess,
{ Utrecht: 'UT' }, { Utrecht: 'UT' },
{}, {},
[{ type: 'UPDATE_STATE_OPTIONS', payload: [{ text: 'Utrecht', value: 'UT' }] }], [{ type: 'UPDATE_STATE_OPTIONS', payload: [{ text: 'Utrecht', value: 'UT' }] }],
[], [],
done,
); );
}); });
it('adds an empty array when no data provided', (done) => { it('adds an empty array when no data provided', async () => {
testAction( await testAction(
actions.fetchStatesSuccess, actions.fetchStatesSuccess,
undefined, undefined,
{}, {},
[{ type: 'UPDATE_STATE_OPTIONS', payload: [] }], [{ type: 'UPDATE_STATE_OPTIONS', payload: [] }],
[], [],
done,
); );
}); });
}); });
describe('fetchStatesError', () => { describe('fetchStatesError', () => {
it('creates a flash', (done) => { it('creates a flash', async () => {
testAction(actions.fetchStatesError, null, {}, [], [], () => { await testAction(actions.fetchStatesError, null, {}, [], []);
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to load states. Please try again.', message: 'Failed to load states. Please try again.',
});
done();
}); });
}); });
}); });
describe('resetStates', () => { describe('resetStates', () => {
it('resets the selected state and sets the stateOptions to the initial value', (done) => { it('resets the selected state and sets the stateOptions to the initial value', async () => {
testAction( await testAction(
actions.resetStates, actions.resetStates,
null, null,
{}, {},
...@@ -250,357 +234,343 @@ describe('Subscriptions Actions', () => { ...@@ -250,357 +234,343 @@ describe('Subscriptions Actions', () => {
{ type: 'UPDATE_STATE_OPTIONS', payload: [] }, { type: 'UPDATE_STATE_OPTIONS', payload: [] },
], ],
[], [],
done,
); );
}); });
}); });
describe('updateCountry', () => { describe('updateCountry', () => {
it('updates country to the provided value', (done) => { it('updates country to the provided value', async () => {
testAction( await testAction(
actions.updateCountry, actions.updateCountry,
'country', 'country',
{}, {},
[{ type: 'UPDATE_COUNTRY', payload: 'country' }], [{ type: 'UPDATE_COUNTRY', payload: 'country' }],
[], [],
done,
); );
}); });
}); });
describe('updateStreetAddressLine1', () => { describe('updateStreetAddressLine1', () => {
it('updates streetAddressLine1 to the provided value', (done) => { it('updates streetAddressLine1 to the provided value', async () => {
testAction( await testAction(
actions.updateStreetAddressLine1, actions.updateStreetAddressLine1,
'streetAddressLine1', 'streetAddressLine1',
{}, {},
[{ type: 'UPDATE_STREET_ADDRESS_LINE_ONE', payload: 'streetAddressLine1' }], [{ type: 'UPDATE_STREET_ADDRESS_LINE_ONE', payload: 'streetAddressLine1' }],
[], [],
done,
); );
}); });
}); });
describe('updateStreetAddressLine2', () => { describe('updateStreetAddressLine2', () => {
it('updates streetAddressLine2 to the provided value', (done) => { it('updates streetAddressLine2 to the provided value', async () => {
testAction( await testAction(
actions.updateStreetAddressLine2, actions.updateStreetAddressLine2,
'streetAddressLine2', 'streetAddressLine2',
{}, {},
[{ type: 'UPDATE_STREET_ADDRESS_LINE_TWO', payload: 'streetAddressLine2' }], [{ type: 'UPDATE_STREET_ADDRESS_LINE_TWO', payload: 'streetAddressLine2' }],
[], [],
done,
); );
}); });
}); });
describe('updateCity', () => { describe('updateCity', () => {
it('updates city to the provided value', (done) => { it('updates city to the provided value', async () => {
testAction( await testAction(
actions.updateCity, actions.updateCity,
'city', 'city',
{}, {},
[{ type: 'UPDATE_CITY', payload: 'city' }], [{ type: 'UPDATE_CITY', payload: 'city' }],
[], [],
done,
); );
}); });
}); });
describe('updateCountryState', () => { describe('updateCountryState', () => {
it('updates countryState to the provided value', (done) => { it('updates countryState to the provided value', async () => {
testAction( await testAction(
actions.updateCountryState, actions.updateCountryState,
'countryState', 'countryState',
{}, {},
[{ type: 'UPDATE_COUNTRY_STATE', payload: 'countryState' }], [{ type: 'UPDATE_COUNTRY_STATE', payload: 'countryState' }],
[], [],
done,
); );
}); });
}); });
describe('updateZipCode', () => { describe('updateZipCode', () => {
it('updates zipCode to the provided value', (done) => { it('updates zipCode to the provided value', async () => {
testAction( await testAction(
actions.updateZipCode, actions.updateZipCode,
'zipCode', 'zipCode',
{}, {},
[{ type: 'UPDATE_ZIP_CODE', payload: 'zipCode' }], [{ type: 'UPDATE_ZIP_CODE', payload: 'zipCode' }],
[], [],
done,
); );
}); });
}); });
describe('startLoadingZuoraScript', () => { describe('startLoadingZuoraScript', () => {
it('updates isLoadingPaymentMethod to true', (done) => { it('updates isLoadingPaymentMethod to true', async () => {
testAction( await testAction(
actions.startLoadingZuoraScript, actions.startLoadingZuoraScript,
undefined, undefined,
{}, {},
[{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: true }], [{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: true }],
[], [],
done,
); );
}); });
}); });
describe('fetchPaymentFormParams', () => { describe('fetchPaymentFormParams', () => {
it('fetches paymentFormParams and calls fetchPaymentFormParamsSuccess with the returned data on success', (done) => { it('fetches paymentFormParams and calls fetchPaymentFormParamsSuccess with the returned data on success', async () => {
mock mock
.onGet(paymentFormPath, { params: { id: constants.PAYMENT_FORM_ID } }) .onGet(paymentFormPath, { params: { id: constants.PAYMENT_FORM_ID } })
.replyOnce(200, { token: 'x' }); .replyOnce(200, { token: 'x' });
testAction( await testAction(
actions.fetchPaymentFormParams, actions.fetchPaymentFormParams,
null, null,
{}, {},
[], [],
[{ type: 'fetchPaymentFormParamsSuccess', payload: { token: 'x' } }], [{ type: 'fetchPaymentFormParamsSuccess', payload: { token: 'x' } }],
done,
); );
}); });
it('calls fetchPaymentFormParamsError on error', (done) => { it('calls fetchPaymentFormParamsError on error', async () => {
mock.onGet(paymentFormPath).replyOnce(500); mock.onGet(paymentFormPath).replyOnce(500);
testAction( await testAction(
actions.fetchPaymentFormParams, actions.fetchPaymentFormParams,
null, null,
{}, {},
[], [],
[{ type: 'fetchPaymentFormParamsError' }], [{ type: 'fetchPaymentFormParamsError' }],
done,
); );
}); });
}); });
describe('fetchPaymentFormParamsSuccess', () => { describe('fetchPaymentFormParamsSuccess', () => {
it('updates paymentFormParams to the provided value when no errors are present', (done) => { it('updates paymentFormParams to the provided value when no errors are present', async () => {
testAction( await testAction(
actions.fetchPaymentFormParamsSuccess, actions.fetchPaymentFormParamsSuccess,
{ token: 'x' }, { token: 'x' },
{}, {},
[{ type: 'UPDATE_PAYMENT_FORM_PARAMS', payload: { token: 'x' } }], [{ type: 'UPDATE_PAYMENT_FORM_PARAMS', payload: { token: 'x' } }],
[], [],
done,
); );
}); });
it('creates a flash when errors are present', (done) => { it('creates a flash when errors are present', async () => {
testAction( await testAction(
actions.fetchPaymentFormParamsSuccess, actions.fetchPaymentFormParamsSuccess,
{ errors: 'error message' }, { errors: 'error message' },
{}, {},
[], [],
[], [],
() => {
expect(createFlash).toHaveBeenCalledWith({
message: 'Credit card form failed to load: error message',
});
done();
},
); );
expect(createFlash).toHaveBeenCalledWith({
message: 'Credit card form failed to load: error message',
});
}); });
}); });
describe('fetchPaymentFormParamsError', () => { describe('fetchPaymentFormParamsError', () => {
it('creates a flash', (done) => { it('creates a flash', async () => {
testAction(actions.fetchPaymentFormParamsError, null, {}, [], [], () => { await testAction(actions.fetchPaymentFormParamsError, null, {}, [], []);
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: 'Credit card form failed to load. Please try again.', message: 'Credit card form failed to load. Please try again.',
});
done();
}); });
}); });
}); });
describe('zuoraIframeRendered', () => { describe('zuoraIframeRendered', () => {
it('updates isLoadingPaymentMethod to false', (done) => { it('updates isLoadingPaymentMethod to false', async () => {
testAction( await testAction(
actions.zuoraIframeRendered, actions.zuoraIframeRendered,
undefined, undefined,
{}, {},
[{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: false }], [{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: false }],
[], [],
done,
); );
}); });
}); });
describe('paymentFormSubmitted', () => { describe('paymentFormSubmitted', () => {
describe('on success', () => { describe('on success', () => {
it('calls paymentFormSubmittedSuccess with the refID from the response and updates isLoadingPaymentMethod to true', (done) => { it('calls paymentFormSubmittedSuccess with the refID from the response and updates isLoadingPaymentMethod to true', async () => {
testAction( await testAction(
actions.paymentFormSubmitted, actions.paymentFormSubmitted,
{ success: true, refId: 'id' }, { success: true, refId: 'id' },
{}, {},
[{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: true }], [{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: true }],
[{ type: 'paymentFormSubmittedSuccess', payload: 'id' }], [{ type: 'paymentFormSubmittedSuccess', payload: 'id' }],
done,
); );
}); });
}); });
describe('on failure', () => { describe('on failure', () => {
it('calls paymentFormSubmittedError with the response', (done) => { it('calls paymentFormSubmittedError with the response', async () => {
testAction( await testAction(
actions.paymentFormSubmitted, actions.paymentFormSubmitted,
{ error: 'foo' }, { error: 'foo' },
{}, {},
[], [],
[{ type: 'paymentFormSubmittedError', payload: { error: 'foo' } }], [{ type: 'paymentFormSubmittedError', payload: { error: 'foo' } }],
done,
); );
}); });
}); });
}); });
describe('paymentFormSubmittedSuccess', () => { describe('paymentFormSubmittedSuccess', () => {
it('updates paymentMethodId to the provided value and calls fetchPaymentMethodDetails', (done) => { it('updates paymentMethodId to the provided value and calls fetchPaymentMethodDetails', async () => {
testAction( await testAction(
actions.paymentFormSubmittedSuccess, actions.paymentFormSubmittedSuccess,
'id', 'id',
{}, {},
[{ type: 'UPDATE_PAYMENT_METHOD_ID', payload: 'id' }], [{ type: 'UPDATE_PAYMENT_METHOD_ID', payload: 'id' }],
[{ type: 'fetchPaymentMethodDetails' }], [{ type: 'fetchPaymentMethodDetails' }],
done,
); );
}); });
}); });
describe('paymentFormSubmittedError', () => { describe('paymentFormSubmittedError', () => {
it('creates a flash', (done) => { it('creates a flash', async () => {
testAction( await testAction(
actions.paymentFormSubmittedError, actions.paymentFormSubmittedError,
{ errorCode: 'codeFromResponse', errorMessage: 'messageFromResponse' }, { errorCode: 'codeFromResponse', errorMessage: 'messageFromResponse' },
{}, {},
[], [],
[], [],
() => {
expect(createFlash).toHaveBeenCalledWith({
message:
'Submitting the credit card form failed with code codeFromResponse: messageFromResponse',
});
done();
},
); );
expect(createFlash).toHaveBeenCalledWith({
message:
'Submitting the credit card form failed with code codeFromResponse: messageFromResponse',
});
}); });
}); });
describe('fetchPaymentMethodDetails', () => { describe('fetchPaymentMethodDetails', () => {
it('fetches paymentMethodDetails and calls fetchPaymentMethodDetailsSuccess with the returned data on success and updates isLoadingPaymentMethod to false', (done) => { it('fetches paymentMethodDetails and calls fetchPaymentMethodDetailsSuccess with the returned data on success and updates isLoadingPaymentMethod to false', async () => {
mock mock
.onGet(paymentMethodPath, { params: { id: 'paymentMethodId' } }) .onGet(paymentMethodPath, { params: { id: 'paymentMethodId' } })
.replyOnce(200, { token: 'x' }); .replyOnce(200, { token: 'x' });
testAction( await testAction(
actions.fetchPaymentMethodDetails, actions.fetchPaymentMethodDetails,
null, null,
{ paymentMethodId: 'paymentMethodId' }, { paymentMethodId: 'paymentMethodId' },
[{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: false }], [{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: false }],
[{ type: 'fetchPaymentMethodDetailsSuccess', payload: { token: 'x' } }], [{ type: 'fetchPaymentMethodDetailsSuccess', payload: { token: 'x' } }],
done,
); );
}); });
it('calls fetchPaymentMethodDetailsError on error and updates isLoadingPaymentMethod to false', (done) => { it('calls fetchPaymentMethodDetailsError on error and updates isLoadingPaymentMethod to false', async () => {
mock.onGet(paymentMethodPath).replyOnce(500); mock.onGet(paymentMethodPath).replyOnce(500);
testAction( await testAction(
actions.fetchPaymentMethodDetails, actions.fetchPaymentMethodDetails,
null, null,
{}, {},
[{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: false }], [{ type: 'UPDATE_IS_LOADING_PAYMENT_METHOD', payload: false }],
[{ type: 'fetchPaymentMethodDetailsError' }], [{ type: 'fetchPaymentMethodDetailsError' }],
done,
); );
}); });
}); });
describe('fetchPaymentMethodDetailsSuccess', () => { describe('fetchPaymentMethodDetailsSuccess', () => {
it('updates creditCardDetails to the provided data and calls defaultClient with activateNextStepMutation', (done) => { const creditCardDetails = {
testAction( credit_card_type: 'cc_type',
credit_card_mask_number: '************4242',
credit_card_expiration_month: 12,
credit_card_expiration_year: 2019,
};
it('updates creditCardDetails to the provided data and calls defaultClient with activateNextStepMutation', async () => {
await testAction(
actions.fetchPaymentMethodDetailsSuccess,
creditCardDetails,
{},
[
{
type: 'UPDATE_CREDIT_CARD_DETAILS',
payload: creditCardDetails,
},
],
[],
);
expect(defaultClient.mutate).toHaveBeenCalledWith({
mutation: activateNextStepMutation,
});
});
it('displays an error if activateNextStepMutation fails', async () => {
const error = new Error('An error happened!');
jest.spyOn(defaultClient, 'mutate').mockRejectedValue(error);
await testAction(
actions.fetchPaymentMethodDetailsSuccess, actions.fetchPaymentMethodDetailsSuccess,
{ creditCardDetails,
credit_card_type: 'cc_type',
credit_card_mask_number: '************4242',
credit_card_expiration_month: 12,
credit_card_expiration_year: 2019,
},
{}, {},
[ [
{ {
type: 'UPDATE_CREDIT_CARD_DETAILS', type: 'UPDATE_CREDIT_CARD_DETAILS',
payload: { payload: creditCardDetails,
credit_card_type: 'cc_type',
credit_card_mask_number: '************4242',
credit_card_expiration_month: 12,
credit_card_expiration_year: 2019,
},
}, },
], ],
[], [],
() => {
expect(defaultClient.mutate).toHaveBeenCalledWith({
mutation: activateNextStepMutation,
});
done();
},
); );
expect(createFlash).toHaveBeenCalledWith({
message: GENERAL_ERROR_MESSAGE,
error,
captureError: true,
});
}); });
}); });
describe('fetchPaymentMethodDetailsError', () => { describe('fetchPaymentMethodDetailsError', () => {
it('creates a flash', (done) => { it('creates a flash', async () => {
testAction(actions.fetchPaymentMethodDetailsError, null, {}, [], [], () => { await testAction(actions.fetchPaymentMethodDetailsError, null, {}, [], []);
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to register credit card. Please try again.', message: 'Failed to register credit card. Please try again.',
});
done();
}); });
}); });
}); });
describe('confirmOrder', () => { describe('confirmOrder', () => {
it('calls confirmOrderSuccess with a redirect location on success', (done) => { it('calls confirmOrderSuccess with a redirect location on success', async () => {
const response = { location: 'x' }; const response = { location: 'x' };
mock.onPost(confirmOrderPath).replyOnce(200, response); mock.onPost(confirmOrderPath).replyOnce(200, response);
testAction( await testAction(
actions.confirmOrder, actions.confirmOrder,
null, null,
{}, {},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }], [{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }],
[{ type: 'confirmOrderSuccess', payload: response }], [{ type: 'confirmOrderSuccess', payload: response }],
done,
); );
}); });
it('calls confirmOrderError with the errors on error', (done) => { it('calls confirmOrderError with the errors on error', async () => {
mock.onPost(confirmOrderPath).replyOnce(200, { errors: 'errors' }); mock.onPost(confirmOrderPath).replyOnce(200, { errors: 'errors' });
testAction( await testAction(
actions.confirmOrder, actions.confirmOrder,
null, null,
{}, {},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }], [{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }],
[{ type: 'confirmOrderError', payload: '"errors"' }], [{ type: 'confirmOrderError', payload: '"errors"' }],
done,
); );
}); });
it('calls confirmOrderError on failure', (done) => { it('calls confirmOrderError on failure', async () => {
mock.onPost(confirmOrderPath).replyOnce(500); mock.onPost(confirmOrderPath).replyOnce(500);
testAction( await testAction(
actions.confirmOrder, actions.confirmOrder,
null, null,
{}, {},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }], [{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }],
[{ type: 'confirmOrderError' }], [{ type: 'confirmOrderError' }],
done,
); );
}); });
}); });
...@@ -610,45 +580,37 @@ describe('Subscriptions Actions', () => { ...@@ -610,45 +580,37 @@ describe('Subscriptions Actions', () => {
const params = { location: 'http://example.com', plan_id: 'x', quantity: 10 }; const params = { location: 'http://example.com', plan_id: 'x', quantity: 10 };
it('changes the window location', (done) => { it('changes the window location', async () => {
testAction(actions.confirmOrderSuccess, params, {}, [], [], () => { await testAction(actions.confirmOrderSuccess, params, {}, [], []);
expect(window.location.assign).toHaveBeenCalledWith('http://example.com'); expect(window.location.assign).toHaveBeenCalledWith('http://example.com');
done();
});
}); });
}); });
describe('confirmOrderError', () => { describe('confirmOrderError', () => {
it('creates a flash with a default message when no error given', (done) => { it('creates a flash with a default message when no error given', async () => {
testAction( await testAction(
actions.confirmOrderError, actions.confirmOrderError,
null, null,
{}, {},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: false }], [{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: false }],
[], [],
() => {
expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to confirm your order! Please try again.',
});
done();
},
); );
expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to confirm your order! Please try again.',
});
}); });
it('creates a flash with a the error message when an error is given', (done) => { it('creates a flash with a the error message when an error is given', async () => {
testAction( await testAction(
actions.confirmOrderError, actions.confirmOrderError,
'"Error"', '"Error"',
{}, {},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: false }], [{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: false }],
[], [],
() => {
expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to confirm your order: "Error". Please try again.',
});
done();
},
); );
expect(createFlash).toHaveBeenCalledWith({
message: 'Failed to confirm your order: "Error". Please try again.',
});
}); });
}); });
}); });
...@@ -3,13 +3,18 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; ...@@ -3,13 +3,18 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import StepSummary from 'ee/vue_shared/purchase_flow/components/step_summary.vue'; import StepSummary from 'ee/vue_shared/purchase_flow/components/step_summary.vue';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql'; import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import flash from '~/flash';
import { STEPS } from '../mock_data'; import { STEPS } from '../mock_data';
import { createMockApolloProvider } from '../spec_helper'; import { createMockApolloProvider } from '../spec_helper';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
jest.mock('~/flash');
describe('Step', () => { describe('Step', () => {
let wrapper; let wrapper;
...@@ -32,11 +37,15 @@ describe('Step', () => { ...@@ -32,11 +37,15 @@ describe('Step', () => {
localVue, localVue,
propsData: { ...initialProps, ...propsData }, propsData: { ...initialProps, ...propsData },
apolloProvider, apolloProvider,
stubs: {
StepSummary,
},
}); });
} }
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
flash.mockClear();
}); });
describe('Step Body', () => { describe('Step Body', () => {
...@@ -61,7 +70,27 @@ describe('Step', () => { ...@@ -61,7 +70,27 @@ describe('Step', () => {
const mockApollo = createMockApolloProvider(STEPS, 1); const mockApollo = createMockApolloProvider(STEPS, 1);
await activateFirstStep(mockApollo); await activateFirstStep(mockApollo);
wrapper = createComponent({ apolloProvider: mockApollo }); wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(true); expect(wrapper.findComponent(StepSummary).exists()).toBe(true);
});
it('displays an error when editing a wrong step', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
await activateFirstStep(mockApollo);
wrapper = createComponent({
propsData: { stepId: 'does not exist' },
apolloProvider: mockApollo,
});
wrapper.findComponent(StepSummary).findComponent(GlButton).vm.$emit('click');
await waitForPromises();
expect(flash.mock.calls).toHaveLength(1);
expect(flash.mock.calls[0][0]).toMatchObject({
message: GENERAL_ERROR_MESSAGE,
captureError: true,
error: expect.any(Error),
});
}); });
it('should not be shown when this step is not valid and not active', async () => { it('should not be shown when this step is not valid and not active', async () => {
...@@ -69,21 +98,21 @@ describe('Step', () => { ...@@ -69,21 +98,21 @@ describe('Step', () => {
await activateFirstStep(mockApollo); await activateFirstStep(mockApollo);
wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo }); wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false); expect(wrapper.findComponent(StepSummary).exists()).toBe(false);
}); });
it('should not be shown when this step is valid and active', () => { it('should not be shown when this step is valid and active', () => {
const mockApollo = createMockApolloProvider(STEPS, 1); const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo }); wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false); expect(wrapper.findComponent(StepSummary).exists()).toBe(false);
}); });
it('should not be shown when this step is not valid and active', () => { it('should not be shown when this step is not valid and active', () => {
const mockApollo = createMockApolloProvider(STEPS, 1); const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo }); wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false); expect(wrapper.findComponent(StepSummary).exists()).toBe(false);
}); });
}); });
...@@ -92,7 +121,7 @@ describe('Step', () => { ...@@ -92,7 +121,7 @@ describe('Step', () => {
const mockApollo = createMockApolloProvider(STEPS, 1); const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { stepId: STEPS[0].id }, apolloProvider: mockApollo }); wrapper = createComponent({ propsData: { stepId: STEPS[0].id }, apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).props('isEditable')).toBe(true); expect(wrapper.findComponent(StepSummary).props('isEditable')).toBe(true);
}); });
}); });
...@@ -102,14 +131,14 @@ describe('Step', () => { ...@@ -102,14 +131,14 @@ describe('Step', () => {
await activateFirstStep(mockApollo); await activateFirstStep(mockApollo);
wrapper = createComponent({ apolloProvider: mockApollo }); wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(true); expect(wrapper.findComponent(StepSummary).exists()).toBe(true);
}); });
it('does not show the summary when this step is not finished', () => { it('does not show the summary when this step is not finished', () => {
const mockApollo = createMockApolloProvider(STEPS, 1); const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo }); wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false); expect(wrapper.findComponent(StepSummary).exists()).toBe(false);
}); });
}); });
...@@ -135,7 +164,7 @@ describe('Step', () => { ...@@ -135,7 +164,7 @@ describe('Step', () => {
const mockApollo = createMockApolloProvider(STEPS, 1); const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo }); wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo });
expect(wrapper.find(GlButton).attributes('disabled')).toBe('true'); expect(wrapper.findComponent(GlButton).attributes('disabled')).toBe('true');
}); });
it('is enabled when this step is valid', () => { it('is enabled when this step is valid', () => {
...@@ -144,5 +173,20 @@ describe('Step', () => { ...@@ -144,5 +173,20 @@ describe('Step', () => {
expect(wrapper.find(GlButton).attributes('disabled')).toBeUndefined(); expect(wrapper.find(GlButton).attributes('disabled')).toBeUndefined();
}); });
it('displays an error if navigating too far', async () => {
const mockApollo = createMockApolloProvider(STEPS, 2);
wrapper = createComponent({ propsData: { stepId: STEPS[2].id }, apolloProvider: mockApollo });
wrapper.find(GlButton).vm.$emit('click');
await waitForPromises();
expect(flash.mock.calls).toHaveLength(1);
expect(flash.mock.calls[0][0]).toMatchObject({
message: GENERAL_ERROR_MESSAGE,
captureError: true,
error: expect.any(Error),
});
});
}); });
}); });
import { createMockClient } from 'mock-apollo-client';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql'; import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql'; import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql';
import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql'; import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql';
import stepListQuery from 'ee/vue_shared/purchase_flow/graphql/queries/step_list.query.graphql'; import stepListQuery from 'ee/vue_shared/purchase_flow/graphql/queries/step_list.query.graphql';
import resolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import typeDefs from 'ee/vue_shared/purchase_flow/graphql/typedefs.graphql';
import { STEPS } from '../mock_data'; import { STEPS } from '../mock_data';
import { createMockApolloProvider } from '../spec_helper';
describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
let mockClient; let mockApolloClient;
beforeEach(async () => { describe('Query', () => {
mockClient = createMockClient({ resolvers, typeDefs }); beforeEach(async () => {
mockClient.cache.writeQuery({ const mockApollo = createMockApolloProvider(STEPS, 0);
query: stepListQuery, mockApolloClient = mockApollo.clients.defaultClient;
data: {
stepList: STEPS,
},
});
mockClient.cache.writeQuery({
query: activeStepQuery,
data: {
activeStep: STEPS[0],
},
}); });
});
describe('Query', () => {
describe('stepListQuery', () => { describe('stepListQuery', () => {
it('stores the stepList', async () => { it('stores the stepList', async () => {
const queryResult = await mockClient.query({ query: stepListQuery }); const queryResult = await mockApolloClient.query({ query: stepListQuery });
expect(queryResult.data.stepList).toMatchObject( expect(queryResult.data.stepList).toMatchObject(
STEPS.map(({ id }) => { STEPS.map(({ id }) => {
return { id }; return { id };
...@@ -38,8 +25,8 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -38,8 +25,8 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
}); });
it('throws an error when cache is not initiated properly', async () => { it('throws an error when cache is not initiated properly', async () => {
mockClient.clearStore(); mockApolloClient.clearStore();
await mockClient.query({ query: stepListQuery }).catch((e) => { await mockApolloClient.query({ query: stepListQuery }).catch((e) => {
expect(e instanceof Error).toBe(true); expect(e instanceof Error).toBe(true);
}); });
}); });
...@@ -47,13 +34,13 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -47,13 +34,13 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
describe('activeStepQuery', () => { describe('activeStepQuery', () => {
it('stores the activeStep', async () => { it('stores the activeStep', async () => {
const queryResult = await mockClient.query({ query: activeStepQuery }); const queryResult = await mockApolloClient.query({ query: activeStepQuery });
expect(queryResult.data.activeStep).toMatchObject({ id: STEPS[0].id }); expect(queryResult.data.activeStep).toMatchObject({ id: STEPS[0].id });
}); });
it('throws an error when cache is not initiated properly', async () => { it('throws an error when cache is not initiated properly', async () => {
mockClient.clearStore(); mockApolloClient.clearStore();
await mockClient.query({ query: activeStepQuery }).catch((e) => { await mockApolloClient.query({ query: activeStepQuery }).catch((e) => {
expect(e instanceof Error).toBe(true); expect(e instanceof Error).toBe(true);
}); });
}); });
...@@ -62,18 +49,23 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -62,18 +49,23 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
describe('Mutation', () => { describe('Mutation', () => {
describe('updateActiveStep', () => { describe('updateActiveStep', () => {
beforeEach(async () => {
const mockApollo = createMockApolloProvider(STEPS, 0);
mockApolloClient = mockApollo.clients.defaultClient;
});
it('updates the active step', async () => { it('updates the active step', async () => {
await mockClient.mutate({ await mockApolloClient.mutate({
mutation: updateStepMutation, mutation: updateStepMutation,
variables: { id: STEPS[1].id }, variables: { id: STEPS[1].id },
}); });
const queryResult = await mockClient.query({ query: activeStepQuery }); const queryResult = await mockApolloClient.query({ query: activeStepQuery });
expect(queryResult.data.activeStep).toMatchObject({ id: STEPS[1].id }); expect(queryResult.data.activeStep).toMatchObject({ id: STEPS[1].id });
}); });
it('throws an error when STEP is not present', async () => { it('throws an error when STEP is not present', async () => {
const id = 'does not exist'; const id = 'does not exist';
await mockClient await mockApolloClient
.mutate({ .mutate({
mutation: updateStepMutation, mutation: updateStepMutation,
variables: { id }, variables: { id },
...@@ -84,8 +76,8 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -84,8 +76,8 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
}); });
it('throws an error when cache is not initiated properly', async () => { it('throws an error when cache is not initiated properly', async () => {
mockClient.clearStore(); mockApolloClient.clearStore();
await mockClient await mockApolloClient
.mutate({ .mutate({
mutation: updateStepMutation, mutation: updateStepMutation,
variables: { id: STEPS[1].id }, variables: { id: STEPS[1].id },
...@@ -98,19 +90,20 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -98,19 +90,20 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
describe('activateNextStep', () => { describe('activateNextStep', () => {
it('updates the active step to the next', async () => { it('updates the active step to the next', async () => {
await mockClient.mutate({ const mockApollo = createMockApolloProvider(STEPS, 0);
mockApolloClient = mockApollo.clients.defaultClient;
await mockApolloClient.mutate({
mutation: activateNextStepMutation, mutation: activateNextStepMutation,
}); });
const queryResult = await mockClient.query({ query: activeStepQuery }); const queryResult = await mockApolloClient.query({ query: activeStepQuery });
expect(queryResult.data.activeStep).toMatchObject({ id: STEPS[1].id }); expect(queryResult.data.activeStep).toMatchObject({ id: STEPS[1].id });
}); });
it('throws an error when out of bounds', async () => { it('throws an error when out of bounds', async () => {
await mockClient.mutate({ const mockApollo = createMockApolloProvider(STEPS, 2);
mutation: activateNextStepMutation, mockApolloClient = mockApollo.clients.defaultClient;
});
await mockClient await mockApolloClient
.mutate({ .mutate({
mutation: activateNextStepMutation, mutation: activateNextStepMutation,
}) })
...@@ -120,8 +113,8 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -120,8 +113,8 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
}); });
it('throws an error when cache is not initiated properly', async () => { it('throws an error when cache is not initiated properly', async () => {
mockClient.clearStore(); mockApolloClient.clearStore();
await mockClient await mockApolloClient
.mutate({ .mutate({
mutation: activateNextStepMutation, mutation: activateNextStepMutation,
}) })
......
export const STEPS = [ export const STEPS = [
{ __typename: 'Step', id: 'firstStep' }, { __typename: 'Step', id: 'firstStep' },
{ __typename: 'Step', id: 'secondStep' }, { __typename: 'Step', id: 'secondStep' },
{ __typename: 'Step', id: 'finalStep' },
]; ];
...@@ -25443,6 +25443,9 @@ msgstr "" ...@@ -25443,6 +25443,9 @@ msgstr ""
msgid "Purchase more storage" msgid "Purchase more storage"
msgstr "" msgstr ""
msgid "PurchaseStep|An error occured in the purchase step. If the problem persists please contact support@gitlab.com."
msgstr ""
msgid "Push" msgid "Push"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment