Commit 7c6f815d authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch...

Merge branch 'ag/321645-ci-minutes-create-a-banner-to-display-success-from-purchasing-ci-minutes' into 'master'

[CI Minutes] Show the banner after successful purchase

See merge request gitlab-org/gitlab!66451
parents 75491057 091ea700
...@@ -10,6 +10,7 @@ import Checkout from './checkout.vue'; ...@@ -10,6 +10,7 @@ import Checkout from './checkout.vue';
import OrderSummary from './order_summary.vue'; import OrderSummary from './order_summary.vue';
export default { export default {
name: 'BuyCIMinutesApp',
components: { components: {
Checkout, Checkout,
GlEmptyState, GlEmptyState,
......
...@@ -30,13 +30,16 @@ export default { ...@@ -30,13 +30,16 @@ export default {
}, },
confirmOrderParams: { confirmOrderParams: {
query: stateQuery, query: stateQuery,
skip() {
return !this.isActive;
},
update(data) { update(data) {
const { customer } = data; const { customer } = data;
return { return {
setup_for_company: data.isSetupForCompany, setup_for_company: data.isSetupForCompany,
selected_group: data.subscription.namespaceId, selected_group: data.subscription.namespaceId,
new_user: data.isNewUser, new_user: data.isNewUser,
redirect_after_success: data.redirectAfterSuccess,
customer: { customer: {
country: customer.country, country: customer.country,
address_1: customer.address1, address_1: customer.address1,
...@@ -58,7 +61,6 @@ export default { ...@@ -58,7 +61,6 @@ export default {
methods: { methods: {
confirmOrder() { confirmOrder() {
this.isLoading = true; this.isLoading = true;
return Api.confirmOrder(this.confirmOrderParams) return Api.confirmOrder(this.confirmOrderParams)
.then(({ data }) => { .then(({ data }) => {
if (data.location) { if (data.location) {
...@@ -82,7 +84,7 @@ export default { ...@@ -82,7 +84,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div v-if="isActive" class="full-width gl-mb-7"> <div v-if="isActive" class="full-width gl-mb-7" data-testid="confirm-order-root">
<gl-button :disabled="isLoading" variant="success" category="primary" @click="confirmOrder"> <gl-button :disabled="isLoading" variant="success" category="primary" @click="confirmOrder">
<gl-loading-icon v-if="isLoading" inline size="sm" /> <gl-loading-icon v-if="isLoading" inline size="sm" />
{{ isLoading ? $options.i18n.confirming : $options.i18n.confirm }} {{ isLoading ? $options.i18n.confirming : $options.i18n.confirm }}
......
...@@ -11,7 +11,7 @@ function arrayToGraphqlArray(arr, typename) { ...@@ -11,7 +11,7 @@ function arrayToGraphqlArray(arr, typename) {
} }
export function writeInitialDataToApolloCache(apolloProvider, dataset) { export function writeInitialDataToApolloCache(apolloProvider, dataset) {
const { groupData, namespaceId } = dataset; const { groupData, namespaceId, redirectAfterSuccess } = dataset;
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
const namespaces = arrayToGraphqlArray(JSON.parse(groupData), 'Namespace'); const namespaces = arrayToGraphqlArray(JSON.parse(groupData), 'Namespace');
...@@ -23,6 +23,7 @@ export function writeInitialDataToApolloCache(apolloProvider, dataset) { ...@@ -23,6 +23,7 @@ export function writeInitialDataToApolloCache(apolloProvider, dataset) {
isSetupForCompany: false, isSetupForCompany: false,
selectedPlanId: null, selectedPlanId: null,
namespaces, namespaces,
redirectAfterSuccess,
subscription: { subscription: {
quantity: 1, quantity: 1,
namespaceId, namespaceId,
......
...@@ -8,6 +8,7 @@ query State { ...@@ -8,6 +8,7 @@ query State {
fullName @client fullName @client
isSetupForCompany @client isSetupForCompany @client
selectedPlanId @client selectedPlanId @client
redirectAfterSuccess @client
customer @client { customer @client {
country country
address1 address1
......
import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Api from 'ee/api'; import Api from 'ee/api';
import ConfirmOrder from 'ee/subscriptions/buy_minutes/components/checkout/confirm_order.vue';
import { STEPS } from 'ee/subscriptions/constants'; import { STEPS } from 'ee/subscriptions/constants';
import ConfirmOrder from 'ee/subscriptions/new/components/checkout/confirm_order.vue'; import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import createStore from 'ee/subscriptions/new/store';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants'; import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import { stateData as initialStateData } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper'; import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import flash from '~/flash'; import flash from '~/flash';
import * as UrlUtility from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('ee/api.js');
describe('Confirm Order', () => { const flushPromises = () => new Promise(setImmediate);
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('Confirm Order', () => {
let mockApolloProvider;
let wrapper; let wrapper;
jest.mock('ee/api.js'); const findRootElement = () => wrapper.findByTestId('confirm-order-root');
const findConfirmButton = () => wrapper.find(GlButton);
const store = createStore(); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
function createComponent(options = {}) { const localVue = createLocalVue();
return shallowMount(ConfirmOrder, { localVue.use(VueApollo);
const createComponent = (options = {}) => {
wrapper = extendedWrapper(
shallowMount(ConfirmOrder, {
localVue, localVue,
store,
...options, ...options,
}); }),
} );
};
const findConfirmButton = () => wrapper.find(GlButton);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -39,8 +44,8 @@ describe('Confirm Order', () => { ...@@ -39,8 +44,8 @@ describe('Confirm Order', () => {
describe('Active', () => { describe('Active', () => {
describe('when receiving proper step data', () => { describe('when receiving proper step data', () => {
beforeEach(() => { beforeEach(() => {
const mockApolloProvider = createMockApolloProvider(STEPS, 3); mockApolloProvider = createMockApolloProvider(STEPS, 3);
wrapper = createComponent({ apolloProvider: mockApolloProvider }); createComponent({ apolloProvider: mockApolloProvider });
}); });
it('button should be visible', () => { it('button should be visible', () => {
...@@ -58,15 +63,38 @@ describe('Confirm Order', () => { ...@@ -58,15 +63,38 @@ describe('Confirm Order', () => {
describe('Clicking the button', () => { describe('Clicking the button', () => {
beforeEach(() => { beforeEach(() => {
const mockApolloProvider = createMockApolloProvider(STEPS, 3); mockApolloProvider = createMockApolloProvider([]);
wrapper = createComponent({ apolloProvider: mockApolloProvider }); mockApolloProvider.clients.defaultClient.cache.writeQuery({
query: stateQuery,
data: { ...initialStateData, stepList: STEPS, activeStep: STEPS[3] },
});
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');
}); });
it('calls the confirmOrder API method', () => { it('calls the confirmOrder API method with the correct params', () => {
expect(Api.confirmOrder).toHaveBeenCalled(); expect(Api.confirmOrder).toHaveBeenCalledTimes(1);
expect(Api.confirmOrder.mock.calls[0][0]).toMatchObject({
setup_for_company: true,
selected_group: null,
new_user: false,
redirect_after_success: '/path/to/redirect/',
customer: {
country: null,
address_1: null,
address_2: null,
city: null,
state: null,
zip_code: null,
company: null,
},
subscription: {
plan_id: null,
payment_method_id: null,
quantity: 1,
},
});
}); });
it('shows the text "Confirming..."', () => { it('shows the text "Confirming..."', () => {
...@@ -78,11 +106,44 @@ describe('Confirm Order', () => { ...@@ -78,11 +106,44 @@ describe('Confirm Order', () => {
}); });
}); });
describe('Order confirmation', () => {
describe('when the confirmation succeeds', () => {
const location = 'group/location/path';
beforeEach(() => {
mockApolloProvider = createMockApolloProvider(STEPS, 3);
createComponent({ apolloProvider: mockApolloProvider });
});
it('should redirect to the location', async () => {
Api.confirmOrder = jest.fn().mockResolvedValueOnce({ data: { location } });
findConfirmButton().vm.$emit('click');
await flushPromises();
expect(UrlUtility.redirectTo).toHaveBeenCalledTimes(1);
expect(UrlUtility.redirectTo).toHaveBeenCalledWith(location);
});
it('shows an error', async () => {
const errors = 'an error';
Api.confirmOrder = jest.fn().mockResolvedValueOnce({ data: { errors } });
findConfirmButton().vm.$emit('click');
await flushPromises();
expect(flash.mock.calls[0][0]).toMatchObject({
message: GENERAL_ERROR_MESSAGE,
captureError: true,
error: new Error(JSON.stringify(errors)),
});
});
});
});
describe('when failing to receive step data', () => { describe('when failing to receive step data', () => {
beforeEach(() => { beforeEach(() => {
const mockApolloProvider = createMockApolloProvider([]); mockApolloProvider = createMockApolloProvider([]);
createComponent({ apolloProvider: mockApolloProvider });
mockApolloProvider.clients.defaultClient.clearStore(); mockApolloProvider.clients.defaultClient.clearStore();
wrapper = createComponent({ apolloProvider: mockApolloProvider });
}); });
afterEach(() => { afterEach(() => {
...@@ -96,16 +157,18 @@ describe('Confirm Order', () => { ...@@ -96,16 +157,18 @@ describe('Confirm Order', () => {
error: expect.any(Error), error: expect.any(Error),
}); });
}); });
it('does not render the root element', () => {
expect(findRootElement().exists()).toBe(false);
});
}); });
}); });
describe('Inactive', () => { describe('Inactive', () => {
beforeEach(() => { it('does not show buttons', () => {
const mockApolloProvider = createMockApolloProvider(STEPS, 1); mockApolloProvider = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApolloProvider }); createComponent({ apolloProvider: mockApolloProvider });
});
it('button should not be visible', () => {
expect(findConfirmButton().exists()).toBe(false); expect(findConfirmButton().exists()).toBe(false);
}); });
}); });
......
...@@ -28,6 +28,7 @@ export const stateData = { ...@@ -28,6 +28,7 @@ export const stateData = {
namespaceId: null, namespaceId: null,
__typename: 'Subscription', __typename: 'Subscription',
}, },
redirectAfterSuccess: '/path/to/redirect/',
selectedPlanId: null, selectedPlanId: null,
paymentMethod: { paymentMethod: {
id: null, id: null,
......
...@@ -9,6 +9,7 @@ const DEFAULT_DATA = { ...@@ -9,6 +9,7 @@ const DEFAULT_DATA = {
newUser: false, newUser: false,
fullName: null, fullName: null,
setupForCompany: false, setupForCompany: false,
redirectAfterSuccess: null,
}; };
describe('utils', () => { describe('utils', () => {
......
...@@ -14,6 +14,5 @@ export function createMockApolloProvider(stepList, initialStepIndex = 0, additio ...@@ -14,6 +14,5 @@ export function createMockApolloProvider(stepList, initialStepIndex = 0, additio
query: activeStepQuery, query: activeStepQuery,
data: { activeStep: stepList[initialStepIndex] }, data: { activeStep: stepList[initialStepIndex] },
}); });
return mockApollo; return mockApollo;
} }
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