Commit 4223146f authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch '321650-mlunoe-migrate-step-components-to-use-local-graphql-state' into 'master'

Feat(Purchase Flow): migrate step components to use GraphQL

See merge request gitlab-org/gitlab!57331
parents 8aa6760b 40455082
<script>
import StepOrderApp from 'ee/vue_shared/components/step_order_app.vue';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
export default {
components: {
......
<script>
import StepOrderApp from 'ee/vue_shared/components/step_order_app.vue';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import Checkout from './checkout.vue';
import OrderSummary from './order_summary.vue';
......
......@@ -2,9 +2,10 @@
import { GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import Step from './step.vue';
import { STEPS } from '../../constants';
export default {
components: {
......@@ -128,11 +129,12 @@ export default {
stateSelectPrompt: s__('Checkout|Please select a state'),
zipCodeLabel: s__('Checkout|Zip code'),
},
stepId: STEPS[1].id,
};
</script>
<template>
<step
step="billingAddress"
:step-id="$options.stepId"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
......
<script>
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { mapState, mapActions } from 'vuex';
import activeStepQuery from 'ee/vue_shared/purchase_flow/graphql/queries/active_step.query.graphql';
import { s__ } from '~/locale';
import { STEPS } from '../../constants';
export default {
components: {
GlButton,
GlLoadingIcon,
},
data() {
return {
isActive: {},
};
},
apollo: {
isActive: {
query: activeStepQuery,
update: ({ activeStep }) => activeStep.id === STEPS[3].id,
},
},
computed: {
...mapState(['isConfirmingOrder']),
...mapGetters(['currentStep']),
isActive() {
return this.currentStep === 'confirmOrder';
},
},
methods: {
...mapActions(['confirmOrder']),
......
<script>
import { GlSprintf } from '@gitlab/ui';
import { mapState } from 'vuex';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__ } from '~/locale';
import Step from './step.vue';
import { STEPS } from '../../constants';
import Zuora from './zuora.vue';
export default {
......@@ -28,10 +29,11 @@ export default {
creditCardDetails: s__('Checkout|%{cardType} ending in %{lastFourDigits}'),
expirationDate: s__('Checkout|Exp %{expirationMonth}/%{expirationYear}'),
},
stepId: STEPS[2].id,
};
</script>
<template>
<step step="paymentMethod" :title="$options.i18n.stepTitle" :is-valid="isValid">
<step :step-id="$options.stepId" :title="$options.i18n.stepTitle" :is-valid="isValid">
<template #body="props">
<zuora :active="props.active" />
</template>
......
......@@ -2,10 +2,10 @@
import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapState, mapGetters, mapActions } from 'vuex';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import { NEW_GROUP, STEPS } from 'ee/subscriptions/new/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import Step from './step.vue';
export default {
components: {
......@@ -133,11 +133,12 @@ export default {
group: s__('Checkout|Group'),
users: s__('Checkout|Users'),
},
stepId: STEPS[0].id,
};
</script>
<template>
<step
step="subscriptionDetails"
:step-id="$options.stepId"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
......
// The order of the steps in this array determines the flow of the application
export const STEPS = ['subscriptionDetails', 'billingAddress', 'paymentMethod', 'confirmOrder'];
/* eslint-disable @gitlab/require-i18n-strings */
export const STEPS = [
{ id: 'subscriptionDetails', __typename: 'Step' },
{ id: 'billingAddress', __typename: 'Step' },
{ id: 'paymentMethod', __typename: 'Step' },
{ id: 'confirmOrder', __typename: 'Step' },
];
/* eslint-enable @gitlab/require-i18n-strings */
export const ZUORA_SCRIPT_URL = 'https://static.zuora.com/Resources/libs/hosted/1.3.1/zuora-min.js';
......
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 resolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import typeDefs from 'ee/vue_shared/purchase_flow/graphql/typedefs.graphql';
import createDefaultClient from '~/lib/graphql';
export default function createClient(stepList) {
const client = createDefaultClient(resolvers, {
typeDefs,
assumeImmutableResults: true,
});
client.cache.writeQuery({
query: stepListQuery,
data: {
stepList,
},
});
client.cache.writeQuery({
query: activeStepQuery,
data: {
activeStep: stepList[0],
},
});
return client;
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import App from './components/app.vue';
import { STEPS } from './constants';
import createClient from './graphql';
import createStore from './store';
Vue.use(VueApollo);
const defaultClient = createClient(STEPS);
const apolloProvider = new VueApollo({
defaultClient,
});
export default () => {
const el = document.getElementById('js-new-subscription');
const store = createStore(el.dataset);
......@@ -9,6 +19,7 @@ export default () => {
return new Vue({
el,
store,
apolloProvider,
components: {
App,
},
......
import Api from 'ee/api';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { sprintf, s__ } from '~/locale';
import { STEPS, PAYMENT_FORM_ID } from '../constants';
import { PAYMENT_FORM_ID } from '../constants';
import defaultClient from '../graphql';
import * as types from './mutation_types';
export const activateStep = ({ commit }, currentStep) => {
if (STEPS.includes(currentStep)) {
commit(types.UPDATE_CURRENT_STEP, currentStep);
}
};
export const activateNextStep = ({ commit, getters }) => {
const { currentStepIndex } = getters;
if (currentStepIndex < STEPS.length - 1) {
const nextStep = STEPS[currentStepIndex + 1];
commit(types.UPDATE_CURRENT_STEP, nextStep);
}
};
export const updateSelectedPlan = ({ commit }, selectedPlan) => {
commit(types.UPDATE_SELECTED_PLAN, selectedPlan);
};
......@@ -180,10 +166,12 @@ export const fetchPaymentMethodDetails = ({ state, dispatch, commit }) =>
.catch(() => dispatch('fetchPaymentMethodDetailsError'))
.finally(() => commit(types.UPDATE_IS_LOADING_PAYMENT_METHOD, false));
export const fetchPaymentMethodDetailsSuccess = ({ commit, dispatch }, creditCardDetails) => {
export const fetchPaymentMethodDetailsSuccess = ({ commit }, creditCardDetails) => {
commit(types.UPDATE_CREDIT_CARD_DETAILS, creditCardDetails);
dispatch('activateNextStep');
defaultClient.mutate({
mutation: activateNextStepMutation,
});
};
export const fetchPaymentMethodDetailsError = () => {
......
import { s__ } from '~/locale';
import { STEPS, NEW_GROUP } from '../constants';
export const currentStep = (state) => state.currentStep;
export const stepIndex = () => (step) => STEPS.findIndex((el) => el === step);
export const currentStepIndex = (state, getters) => getters.stepIndex(state.currentStep);
import { NEW_GROUP } from '../constants';
export const selectedPlanText = (state, getters) => getters.selectedPlanDetails.text;
......
export const UPDATE_CURRENT_STEP = 'UPDATE_CURRENT_STEP';
export const UPDATE_SELECTED_PLAN = 'UPDATE_SELECTED_PLAN';
export const UPDATE_SELECTED_GROUP = 'UPDATE_SELECTED_GROUP';
......
import * as types from './mutation_types';
export default {
[types.UPDATE_CURRENT_STEP](state, currentStep) {
state.currentStep = currentStep;
},
[types.UPDATE_SELECTED_PLAN](state, selectedPlan) {
state.selectedPlan = selectedPlan;
},
......
import { parseBoolean } from '~/lib/utils/common_utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { STEPS, TAX_RATE } from '../constants';
import { TAX_RATE } from '../constants';
const parsePlanData = (planData) =>
JSON.parse(planData).map((plan) => ({
......@@ -52,7 +52,6 @@ export default ({
const groups = parseGroupData(groupData);
return {
currentStep: STEPS[0],
isSetupForCompany: parseBoolean(setupForCompany) || !isNewUser,
availablePlans,
selectedPlan: determineSelectedPlan(planId, availablePlans),
......
<script>
import { GlFormGroup, GlButton } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
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 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 { convertToSnakeCase, dasherize } from '~/lib/utils/text_utility';
import StepHeader from './step_header.vue';
import StepSummary from './step_summary.vue';
......@@ -13,7 +16,7 @@ export default {
StepSummary,
},
props: {
step: {
stepId: {
type: String,
required: true,
},
......@@ -31,30 +34,61 @@ export default {
default: '',
},
},
data() {
return {
activeStep: {},
stepList: [],
loading: false,
};
},
apollo: {
activeStep: {
query: activeStepQuery,
},
stepList: {
query: stepListQuery,
},
},
computed: {
isActive() {
return this.currentStep === this.step;
return this.activeStep.id === this.stepId;
},
isFinished() {
return this.isValid && !this.isActive;
},
isEditable() {
return this.isFinished && this.stepIndex(this.step) < this.currentStepIndex;
const index = this.stepList.findIndex(({ id }) => id === this.stepId);
const activeIndex = this.stepList.findIndex(({ id }) => id === this.activeStep.id);
return this.isFinished && index < activeIndex;
},
snakeCasedStep() {
return dasherize(convertToSnakeCase(this.step));
return dasherize(convertToSnakeCase(this.stepId));
},
...mapGetters(['currentStep', 'stepIndex', 'currentStepIndex']),
},
methods: {
...mapActions(['activateStep', 'activateNextStep']),
nextStep() {
if (this.isValid) {
this.activateNextStep();
async nextStep() {
if (!this.isValid) {
return;
}
this.loading = true;
await this.$apollo
.mutate({
mutation: activateNextStepMutation,
})
.finally(() => {
this.loading = false;
});
},
edit() {
this.activateStep(this.step);
async edit() {
this.loading = true;
await this.$apollo
.mutate({
mutation: updateStepMutation,
variables: { id: this.stepId },
})
.finally(() => {
this.loading = false;
});
},
},
};
......
......@@ -4,7 +4,7 @@ import { createWrapper } from '@vue/test-utils';
import initBuyMinutesApp from 'ee/subscriptions/buy_minutes';
import * as utils from 'ee/subscriptions/buy_minutes/utils';
import StepOrderApp from 'ee/vue_shared/components/step_order_app.vue';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { mockCiMinutesPlans, mockParsedCiMinutesPlans } from './mock_data';
jest.mock('ee/subscriptions/buy_minutes/utils');
......
import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import { mount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import Component from 'ee/subscriptions/new/components/checkout/billing_address.vue';
import Step from 'ee/subscriptions/new/components/checkout/step.vue';
import BillingAddress from 'ee/subscriptions/new/components/checkout/billing_address.vue';
import { STEPS } from 'ee/subscriptions/new/constants';
import { getStoreConfig } from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
Vue.use(Vuex);
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueApollo);
describe('Billing Address', () => {
let store;
let wrapper;
let mockApolloProvider;
const actionMocks = {
fetchCountries: jest.fn(),
fetchStates: jest.fn(),
};
const createComponent = () => {
function activateNextStep() {
return mockApolloProvider.clients.defaultClient.mutate({
mutation: activateNextStepMutation,
});
}
function createStore() {
const { actions, ...storeConfig } = getStoreConfig();
store = new Vuex.Store({
return new Vuex.Store({
...storeConfig,
actions: { ...actions, ...actionMocks },
});
}
wrapper = mount(Component, {
store,
function createComponent(options = {}) {
return mount(BillingAddress, {
localVue,
...options,
});
};
}
beforeEach(() => {
createComponent();
store = createStore();
mockApolloProvider = createMockApolloProvider(STEPS);
wrapper = createComponent({ store, apolloProvider: mockApolloProvider });
});
afterEach(() => {
......@@ -110,14 +128,15 @@ describe('Billing Address', () => {
});
describe('showing the summary', () => {
beforeEach(() => {
beforeEach(async () => {
store.commit(types.UPDATE_COUNTRY, 'country');
store.commit(types.UPDATE_STREET_ADDRESS_LINE_ONE, 'address line 1');
store.commit(types.UPDATE_STREET_ADDRESS_LINE_TWO, 'address line 2');
store.commit(types.UPDATE_COUNTRY_STATE, 'state');
store.commit(types.UPDATE_CITY, 'city');
store.commit(types.UPDATE_ZIP_CODE, 'zip');
store.commit(types.UPDATE_CURRENT_STEP, 'nextStep');
await activateNextStep();
await activateNextStep();
});
it('should show the entered address line 1', () => {
......
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import Api from 'ee/api';
import Component 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 createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types';
import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
describe('Confirm Order', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueApollo);
let wrapper;
let mockApolloProvider;
jest.mock('ee/api.js');
const store = createStore();
const createComponent = (opts = {}) => {
wrapper = shallowMount(Component, {
function activateStep(stepId) {
return mockApolloProvider.clients.defaultClient.mutate({
mutation: updateStepMutation,
variables: { id: stepId },
});
}
function createComponent(options = {}) {
return shallowMount(ConfirmOrder, {
localVue,
store,
...opts,
...options,
});
};
}
const findConfirmButton = () => wrapper.find(GlButton);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
beforeEach(() => {
createComponent();
mockApolloProvider = createMockApolloProvider(STEPS);
wrapper = createComponent({ apolloProvider: mockApolloProvider });
});
afterEach(() => {
......@@ -36,8 +49,8 @@ describe('Confirm Order', () => {
});
describe('Active', () => {
beforeEach(() => {
store.commit(types.UPDATE_CURRENT_STEP, 'confirmOrder');
beforeEach(async () => {
await activateStep(STEPS[3].id);
});
it('button should be visible', () => {
......@@ -74,8 +87,8 @@ describe('Confirm Order', () => {
});
describe('Inactive', () => {
beforeEach(() => {
store.commit(types.UPDATE_CURRENT_STEP, 'otherStep');
beforeEach(async () => {
await activateStep(STEPS[1].id);
});
it('button should not be visible', () => {
......
import { mount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import Component from 'ee/subscriptions/new/components/checkout/payment_method.vue';
import Step from 'ee/subscriptions/new/components/checkout/step.vue';
import PaymentMethod from 'ee/subscriptions/new/components/checkout/payment_method.vue';
import { STEPS } from 'ee/subscriptions/new/constants';
import createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
describe('Payment Method', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueApollo);
let store;
let wrapper;
const createComponent = (opts = {}) => {
wrapper = mount(Component, {
function createComponent(options = {}) {
return mount(PaymentMethod, {
localVue,
store,
...opts,
...options,
});
};
}
beforeEach(() => {
store = createStore();
......@@ -31,7 +35,8 @@ describe('Payment Method', () => {
credit_card_expiration_year: 2009,
});
createComponent();
const mockApollo = createMockApolloProvider(STEPS);
wrapper = createComponent({ apolloProvider: mockApollo });
});
afterEach(() => {
......
import { mount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import Step from 'ee/subscriptions/new/components/checkout/step.vue';
import Component from 'ee/subscriptions/new/components/checkout/subscription_details.vue';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import { NEW_GROUP, STEPS } from 'ee/subscriptions/new/constants';
import createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
const availablePlans = [
{ id: 'firstPlanId', code: 'bronze', price_per_year: 48, name: 'bronze' },
{ id: 'secondPlanId', code: 'silver', price_per_year: 228, name: 'silver' },
];
const groupData = [
{ id: 132, name: 'My first group', users: 3 },
{ id: 483, name: 'My second group', users: 12 },
];
const defaultInitialStoreData = {
availablePlans: JSON.stringify(availablePlans),
groupData: JSON.stringify(groupData),
planId: 'secondPlanId',
namespaceId: null,
setupForCompany: 'true',
fullName: 'Full Name',
};
describe('Subscription Details', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueApollo);
let store;
let wrapper;
const availablePlans = [
{ id: 'firstPlanId', code: 'bronze', price_per_year: 48, name: 'bronze' },
{ id: 'secondPlanId', code: 'silver', price_per_year: 228, name: 'silver' },
];
const groupData = [
{ id: 132, name: 'My first group', users: 3 },
{ id: 483, name: 'My second group', users: 12 },
];
let initialNamespaceId = null;
const initialData = (namespaceId) => {
return {
availablePlans: JSON.stringify(availablePlans),
groupData: JSON.stringify(groupData),
planId: 'secondPlanId',
namespaceId,
setupForCompany: 'true',
fullName: 'Full Name',
};
};
const createComponent = () => {
wrapper = mount(Component, {
function createComponent(options = {}) {
const { apolloProvider, store } = options;
return mount(Component, {
localVue,
store,
apolloProvider,
stubs: {
Step,
},
});
};
}
const organizationNameInput = () => wrapper.find({ ref: 'organization-name' });
const groupSelect = () => wrapper.find({ ref: 'group-select' });
const numberOfUsersInput = () => wrapper.find({ ref: 'number-of-users' });
const companyLink = () => wrapper.find({ ref: 'company-link' });
beforeEach(() => {
store = createStore(initialData(initialNamespaceId));
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('A new user setting up for personal use', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS);
const store = createStore(defaultInitialStoreData);
store.state.isNewUser = true;
store.state.isSetupForCompany = false;
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('should not display an input field for the company or group name', () => {
......@@ -85,8 +87,11 @@ describe('Subscription Details', () => {
describe('A new user setting up for a company or group', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS);
const store = createStore(defaultInitialStoreData);
store.state.isNewUser = true;
store.state.groupData = [];
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('should display an input field for the company or group name', () => {
......@@ -112,8 +117,11 @@ describe('Subscription Details', () => {
describe('An existing user without any groups', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS);
const store = createStore(defaultInitialStoreData);
store.state.isNewUser = false;
store.state.groupData = [];
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('should display an input field for the company or group name', () => {
......@@ -138,8 +146,13 @@ describe('Subscription Details', () => {
});
describe('An existing user with groups', () => {
let store;
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS);
store = createStore(defaultInitialStoreData);
store.state.isNewUser = false;
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('should not display an input field for the company or group name', () => {
......@@ -196,9 +209,13 @@ describe('Subscription Details', () => {
});
describe('An existing user coming from group billing page', () => {
let store;
beforeEach(() => {
initialNamespaceId = '132';
const mockApollo = createMockApolloProvider(STEPS);
store = createStore({ ...defaultInitialStoreData, namespaceId: '132' });
store.state.isNewUser = false;
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('should not display an input field for the company or group name', () => {
......@@ -246,6 +263,13 @@ describe('Subscription Details', () => {
describe('validations', () => {
const isStepValid = () => wrapper.find(Step).props('isValid');
let store;
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS);
store = createStore(defaultInitialStoreData);
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
describe('when setting up for a company', () => {
beforeEach(() => {
......@@ -331,12 +355,16 @@ describe('Subscription Details', () => {
});
describe('Showing summary', () => {
let store;
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS, 1);
store = createStore(defaultInitialStoreData);
store.commit(types.UPDATE_IS_SETUP_FOR_COMPANY, true);
store.commit(types.UPDATE_SELECTED_PLAN, 'firstPlanId');
store.commit(types.UPDATE_ORGANIZATION_NAME, 'My Organization');
store.commit(types.UPDATE_NUMBER_OF_USERS, 25);
store.commit(types.UPDATE_CURRENT_STEP, 'nextStep');
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('should show the selected plan', () => {
......
import MockAdapter from 'axios-mock-adapter';
import Api from 'ee/api';
import * as constants from 'ee/subscriptions/new/constants';
import defaultClient from 'ee/subscriptions/new/graphql';
import * as actions from 'ee/subscriptions/new/store/actions';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import testAction from 'helpers/vuex_action_helper';
import createFlash from '~/flash';
......@@ -16,53 +18,18 @@ const {
} = Api;
jest.mock('~/flash');
jest.mock('ee/subscriptions/new/constants', () => ({
STEPS: ['firstStep', 'secondStep'],
}));
describe('Subscriptions Actions', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
defaultClient.mutate = jest.fn();
});
afterEach(() => {
mock.restore();
});
describe('activateStep', () => {
it('set the currentStep to the provided value', (done) => {
testAction(
actions.activateStep,
'secondStep',
{},
[{ type: 'UPDATE_CURRENT_STEP', payload: 'secondStep' }],
[],
done,
);
});
it('does not change the currentStep if provided value is not available', (done) => {
testAction(actions.activateStep, 'thirdStep', {}, [], [], done);
});
});
describe('activateNextStep', () => {
it('set the currentStep to the next step in the available steps', (done) => {
testAction(
actions.activateNextStep,
{},
{ currentStepIndex: 0 },
[{ type: 'UPDATE_CURRENT_STEP', payload: 'secondStep' }],
[],
done,
);
});
it('does not change the currentStep if the current step is the last step', (done) => {
testAction(actions.activateNextStep, {}, { currentStepIndex: 1 }, [], [], done);
});
defaultClient.mutate.mockClear();
});
describe('updateSelectedPlan', () => {
......@@ -553,7 +520,7 @@ describe('Subscriptions Actions', () => {
});
describe('fetchPaymentMethodDetailsSuccess', () => {
it('updates creditCardDetails to the provided data and calls activateNextStep', (done) => {
it('updates creditCardDetails to the provided data and calls defaultClient with activateNextStepMutation', (done) => {
testAction(
actions.fetchPaymentMethodDetailsSuccess,
{
......@@ -574,8 +541,13 @@ describe('Subscriptions Actions', () => {
},
},
],
[{ type: 'activateNextStep' }],
done,
[],
() => {
expect(defaultClient.mutate).toHaveBeenCalledWith({
mutation: activateNextStepMutation,
});
done();
},
);
});
});
......
import * as constants from 'ee/subscriptions/new/constants';
import * as getters from 'ee/subscriptions/new/store/getters';
constants.STEPS = ['firstStep', 'secondStep'];
const state = {
currentStep: 'secondStep',
isSetupForCompany: true,
isNewUser: true,
availablePlans: [
......@@ -26,35 +23,6 @@ const state = {
};
describe('Subscriptions Getters', () => {
describe('currentStep', () => {
it('returns the states currentStep', () => {
expect(getters.currentStep(state)).toBe('secondStep');
});
});
describe('stepIndex', () => {
it('returns a function', () => {
expect(getters.stepIndex()).toBeInstanceOf(Function);
});
it('returns a function that returns the index of the given step', () => {
expect(getters.stepIndex()('secondStep')).toBe(1);
});
});
describe('currentStepIndex', () => {
it('returns a function', () => {
expect(getters.currentStepIndex(state, getters)).toBeInstanceOf(Function);
});
it('calls the stepIndex function with the current step name', () => {
const stepIndexSpy = jest.spyOn(getters, 'stepIndex');
getters.currentStepIndex(state, getters);
expect(stepIndexSpy).toHaveBeenCalledWith('secondStep');
});
});
describe('selectedPlanText', () => {
it('returns the text for selectedPlan', () => {
expect(
......
......@@ -2,7 +2,6 @@ import * as types from 'ee/subscriptions/new/store/mutation_types';
import mutations from 'ee/subscriptions/new/store/mutations';
const state = () => ({
currentStep: 'firstStep',
selectedPlan: 'firstPlan',
isSetupForCompany: true,
numberOfUsers: 1,
......@@ -22,7 +21,6 @@ beforeEach(() => {
describe('ee/subscriptions/new/store/mutation', () => {
describe.each`
mutation | value | stateProp
${types.UPDATE_CURRENT_STEP} | ${'secondStep'} | ${'currentStep'}
${types.UPDATE_SELECTED_PLAN} | ${'secondPlan'} | ${'selectedPlan'}
${types.UPDATE_SELECTED_GROUP} | ${'selectedGroup'} | ${'selectedGroup'}
${types.UPDATE_IS_SETUP_FOR_COMPANY} | ${false} | ${'isSetupForCompany'}
......
import * as constants from 'ee/subscriptions/new/constants';
import createState from 'ee/subscriptions/new/store/state';
constants.STEPS = ['firstStep', 'secondStep'];
constants.TAX_RATE = 0;
describe('projectsSelector default state', () => {
......@@ -31,10 +30,6 @@ describe('projectsSelector default state', () => {
const state = createState(initialData);
it('sets the currentStep to the first item of the STEPS constant', () => {
expect(state.currentStep).toEqual('firstStep');
});
describe('availablePlans', () => {
it('sets the availablePlans to the provided parsed availablePlans', () => {
expect(state.availablePlans).toEqual([
......
import { GlButton } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import Component from 'ee/subscriptions/new/components/checkout/step.vue';
import StepSummary from 'ee/subscriptions/new/components/checkout/step_summary.vue';
import * as constants from 'ee/subscriptions/new/constants';
import createStore from 'ee/subscriptions/new/store';
import VueApollo from 'vue-apollo';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import StepSummary from 'ee/vue_shared/purchase_flow/components/step_summary.vue';
import updateStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/update_active_step.mutation.graphql';
import { STEPS } from '../mock_data';
import { createMockApolloProvider } from '../spec_helper';
describe('Step', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
const localVue = createLocalVue();
localVue.use(VueApollo);
let store;
describe('Step', () => {
let wrapper;
const initialProps = {
step: 'secondStep',
stepId: STEPS[1].id,
isValid: true,
title: 'title',
nextStepButtonText: 'next',
};
const createComponent = (propsData) => {
wrapper = shallowMount(Component, {
propsData: { ...initialProps, ...propsData },
function activateFirstStep(apolloProvider) {
return apolloProvider.clients.defaultClient.mutate({
mutation: updateStepMutation,
variables: { id: STEPS[0].id },
});
}
function createComponent(options = {}) {
const { apolloProvider, propsData } = options;
return shallowMount(Step, {
localVue,
store,
propsData: { ...initialProps, ...propsData },
apolloProvider,
});
};
const activatePreviousStep = () => {
store.dispatch('activateStep', 'firstStep');
};
constants.STEPS = ['firstStep', 'secondStep'];
beforeEach(() => {
store = createStore();
store.dispatch('activateStep', 'secondStep');
});
}
afterEach(() => {
wrapper.destroy();
......@@ -45,42 +41,47 @@ describe('Step', () => {
describe('Step Body', () => {
it('should display the step body when this step is the current step', () => {
createComponent();
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find('.card > div').attributes('style')).toBeUndefined();
});
it('should not display the step body when this step is not the current step', () => {
activatePreviousStep();
createComponent();
it('should not display the step body when this step is not the current step', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
await activateFirstStep(mockApollo);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find('.card > div').attributes('style')).toBe('display: none;');
});
});
describe('Step Summary', () => {
it('should be shown when this step is valid and not active', () => {
activatePreviousStep();
createComponent();
it('should be shown when this step is valid and not active', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
await activateFirstStep(mockApollo);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(true);
});
it('should not be shown when this step is not valid and not active', () => {
activatePreviousStep();
createComponent({ isValid: false });
it('should not be shown when this step is not valid and not active', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
await activateFirstStep(mockApollo);
wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
it('should not be shown when this step is valid and active', () => {
createComponent();
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
it('should not be shown when this step is not valid and active', () => {
createComponent({ isValid: false });
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
......@@ -88,22 +89,25 @@ describe('Step', () => {
describe('isEditable', () => {
it('should set the isEditable property to true when this step is finished and comes before the current step', () => {
createComponent({ step: 'firstStep' });
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { stepId: STEPS[0].id }, apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).props('isEditable')).toBe(true);
});
});
describe('Showing the summary', () => {
it('shows the summary when this step is finished', () => {
activatePreviousStep();
createComponent();
it('shows the summary when this step is finished', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
await activateFirstStep(mockApollo);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(true);
});
it('does not show the summary when this step is not finished', () => {
createComponent();
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
......@@ -111,25 +115,32 @@ describe('Step', () => {
describe('Next button', () => {
it('shows the next button when the text was passed', () => {
createComponent();
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.text()).toBe('next');
});
it('does not show the next button when no text was passed', () => {
createComponent({ nextStepButtonText: '' });
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({
propsData: { nextStepButtonText: '' },
apolloProvider: mockApollo,
});
expect(wrapper.text()).toBe('');
});
it('is disabled when this step is not valid', () => {
createComponent({ isValid: false });
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { isValid: false }, apolloProvider: mockApollo });
expect(wrapper.find(GlButton).attributes('disabled')).toBe('true');
});
it('is enabled when this step is valid', () => {
createComponent();
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ apolloProvider: mockApollo });
expect(wrapper.find(GlButton).attributes('disabled')).toBeUndefined();
});
......
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 resolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import createMockApollo from 'helpers/mock_apollo_helper';
export function createMockApolloProvider(stepList, initialStepIndex = 0) {
const mockApollo = createMockApollo([], resolvers);
mockApollo.clients.defaultClient.cache.writeQuery({
query: stepListQuery,
data: { stepList },
});
mockApollo.clients.defaultClient.cache.writeQuery({
query: activeStepQuery,
data: { activeStep: stepList[initialStepIndex] },
});
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