Commit 87c15fb7 authored by Michael Lunøe's avatar Michael Lunøe Committed by Olena Horal-Koretska

Clean up initial data parsing in /buy_minutes route

parent c765eb69
...@@ -21,7 +21,7 @@ export default { ...@@ -21,7 +21,7 @@ export default {
emptySvg, emptySvg,
data() { data() {
return { return {
plans: [], plans: null,
hasError: false, hasError: false,
}; };
}, },
...@@ -33,6 +33,11 @@ export default { ...@@ -33,6 +33,11 @@ export default {
tags: [planTags.CI_1000_MINUTES_PLAN], tags: [planTags.CI_1000_MINUTES_PLAN],
}, },
update(data) { update(data) {
if (!data?.plans?.length) {
this.hasError = true;
return null;
}
return data.plans; return data.plans;
}, },
error(error) { error(error) {
...@@ -50,7 +55,7 @@ export default { ...@@ -50,7 +55,7 @@ export default {
:description="$options.i18n.ERROR_FETCHING_DATA_DESCRIPTION" :description="$options.i18n.ERROR_FETCHING_DATA_DESCRIPTION"
:svg-path="`data:image/svg+xml;utf8,${encodeURIComponent($options.emptySvg)}`" :svg-path="`data:image/svg+xml;utf8,${encodeURIComponent($options.emptySvg)}`"
/> />
<step-order-app v-else> <step-order-app v-else-if="!$apollo.loading">
<template #checkout> <template #checkout>
<checkout :plans="plans" /> <checkout :plans="plans" />
</template> </template>
......
...@@ -13,16 +13,16 @@ export default { ...@@ -13,16 +13,16 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
isNewUser: null,
};
},
apollo: { apollo: {
state: { isNewUser: {
query: STATE_QUERY, query: STATE_QUERY,
}, },
}, },
computed: {
isNewUser() {
return this.state.isNewUser;
},
},
currentStep: STEPS.checkout, currentStep: STEPS.checkout,
steps: SUBSCRIPTON_FLOW_STEPS, steps: SUBSCRIPTON_FLOW_STEPS,
i18n: { i18n: {
...@@ -31,7 +31,10 @@ export default { ...@@ -31,7 +31,10 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="checkout gl-flex gl-flex-column gl-justify-content-between w-100"> <div
v-if="!$apollo.loading"
class="checkout gl-flex gl-flex-column gl-justify-content-between w-100"
>
<div class="full-width"> <div class="full-width">
<progress-bar v-if="isNewUser" :steps="$options.steps" :current-step="$options.currentStep" /> <progress-bar v-if="isNewUser" :steps="$options.steps" :current-step="$options.currentStep" />
<div class="flash-container"></div> <div class="flash-container"></div>
......
<script> <script>
import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui'; import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { STEPS } from 'ee/subscriptions/constants';
import UPDATE_STATE from 'ee/subscriptions/graphql/mutations/update_state.mutation.graphql'; import UPDATE_STATE from 'ee/subscriptions/graphql/mutations/update_state.mutation.graphql';
import STATE_QUERY from 'ee/subscriptions/graphql/queries/state.query.graphql'; import STATE_QUERY from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { NEW_GROUP, STEPS } from 'ee/subscriptions/new/constants'; import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__, __ } from '~/locale'; import { sprintf, s__, __ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
...@@ -26,21 +27,49 @@ export default { ...@@ -26,21 +27,49 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
subscription: {},
namespaces: [],
customer: {},
isSetupForCompany: null,
isNewUser: null,
};
},
apollo: { apollo: {
state: { state: {
query: STATE_QUERY, query: STATE_QUERY,
update(data) {
const {
subscription = {},
namespaces = [],
customer = {},
isSetupForCompany = null,
isNewUser = null,
} = data;
return {
subscription,
namespaces,
customer,
isSetupForCompany,
isNewUser,
};
},
result({ data }) {
const { subscription, namespaces, customer, isSetupForCompany, isNewUser } = data || {};
this.subscription = subscription;
this.namespaces = namespaces;
this.customer = customer;
this.isSetupForCompany = isSetupForCompany;
this.isNewUser = isNewUser;
},
}, },
}, },
computed: { computed: {
subscription() {
return this.state.subscription;
},
namespaces() {
return this.state.namespaces;
},
selectedPlanModel: { selectedPlanModel: {
get() { get() {
return this.subscription.planId; return this.subscription.planId || this.plans[0].code;
}, },
set(planId) { set(planId) {
this.updateSubscription({ subscription: { planId } }); this.updateSubscription({ subscription: { planId } });
...@@ -67,7 +96,7 @@ export default { ...@@ -67,7 +96,7 @@ export default {
}, },
companyModel: { companyModel: {
get() { get() {
return this.state.customer.company; return this.customer.company;
}, },
set(company) { set(company) {
this.updateSubscription({ customer: { company } }); this.updateSubscription({ customer: { company } });
...@@ -99,10 +128,10 @@ export default { ...@@ -99,10 +128,10 @@ export default {
); );
}, },
isValid() { isValid() {
if (this.state.isSetupForCompany) { if (this.isSetupForCompany) {
return ( return (
!isEmpty(this.subscription.planId) && !isEmpty(this.subscription.planId) &&
(!isEmpty(this.state.customer.company) || this.isNewGroupSelected) && (!isEmpty(this.customer.company) || this.isNewGroupSelected) &&
this.isNumberOfUsersValid this.isNumberOfUsersValid
); );
} }
...@@ -110,13 +139,13 @@ export default { ...@@ -110,13 +139,13 @@ export default {
return !isEmpty(this.subscription.planId) && this.subscription.quantity === 1; return !isEmpty(this.subscription.planId) && this.subscription.quantity === 1;
}, },
isShowingGroupSelector() { isShowingGroupSelector() {
return !this.state.isNewUser && this.namespaces.length; return !this.isNewUser && this.namespaces.length;
}, },
isNewGroupSelected() { isNewGroupSelected() {
return this.subscription.namespaceId === NEW_GROUP; return this.subscription.namespaceId === NEW_GROUP;
}, },
isShowingNameOfCompanyInput() { isShowingNameOfCompanyInput() {
return this.state.isSetupForCompany && (!this.namespaces.length || this.isNewGroupSelected); return this.isSetupForCompany && (!this.namespaces.length || this.isNewGroupSelected);
}, },
groupOptionsWithDefault() { groupOptionsWithDefault() {
return [ return [
...@@ -147,7 +176,7 @@ export default { ...@@ -147,7 +176,7 @@ export default {
}); });
}, },
toggleIsSetupForCompany() { toggleIsSetupForCompany() {
this.updateSubscription({ isSetupForCompany: !this.state.isSetupForCompany }); this.updateSubscription({ isSetupForCompany: !this.isSetupForCompany });
}, },
}, },
i18n: { i18n: {
...@@ -172,6 +201,7 @@ export default { ...@@ -172,6 +201,7 @@ export default {
</script> </script>
<template> <template>
<step <step
v-if="!$apollo.loading"
:step-id="$options.stepId" :step-id="$options.stepId"
:title="$options.i18n.stepTitle" :title="$options.i18n.stepTitle"
:is-valid="isValid" :is-valid="isValid"
...@@ -219,12 +249,12 @@ export default { ...@@ -219,12 +249,12 @@ export default {
v-model.number="numberOfUsersModel" v-model.number="numberOfUsersModel"
type="number" type="number"
:min="selectedGroupUsers" :min="selectedGroupUsers"
:disabled="!state.isSetupForCompany" :disabled="!isSetupForCompany"
data-qa-selector="number_of_users" data-qa-selector="number_of_users"
/> />
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
v-if="!state.isSetupForCompany" v-if="!isSetupForCompany"
ref="company-link" ref="company-link"
class="label ml-3 align-self-end" class="label ml-3 align-self-end"
> >
...@@ -240,7 +270,7 @@ export default { ...@@ -240,7 +270,7 @@ export default {
<strong ref="summary-line-1"> <strong ref="summary-line-1">
{{ selectedPlanTextLine }} {{ selectedPlanTextLine }}
</strong> </strong>
<div v-if="state.isSetupForCompany" ref="summary-line-2"> <div v-if="isSetupForCompany" ref="summary-line-2">
{{ $options.i18n.group }}: {{ customer.company || selectedGroupName }} {{ $options.i18n.group }}: {{ customer.company || selectedGroupName }}
</div> </div>
<div ref="summary-line-3">{{ $options.i18n.users }}: {{ subscription.quantity }}</div> <div ref="summary-line-3">{{ $options.i18n.users }}: {{ subscription.quantity }}</div>
......
import Vue from 'vue'; import Vue from 'vue';
import App from 'ee/subscriptions/buy_minutes/components/app.vue'; import ensureData from '~/ensure_data';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql'; import App from './components/app.vue';
import { STEPS } from 'ee/subscriptions/new/constants';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import { writeInitialDataToApolloCache } from './utils';
const arrayToGraphqlArray = (arr, typename) =>
Array.from(arr, (item) =>
Object.assign(convertObjectPropsToCamelCase(item, { deep: true }), { __typename: typename }),
);
const writeInitialDataToApolloProvider = (dataset) => {
const { groupData, newUser, setupForCompany, fullName, planId } = dataset;
// eslint-disable-next-line @gitlab/require-i18n-strings
const namespaces = arrayToGraphqlArray(JSON.parse(groupData), 'Namespace');
const isNewUser = parseBoolean(newUser);
const isSetupForCompany = parseBoolean(setupForCompany) || !isNewUser;
apolloProvider.clients.defaultClient.cache.writeQuery({
query: stateQuery,
data: {
state: {
isNewUser,
isSetupForCompany,
namespaces,
fullName,
subscription: {
planId,
paymentMethodId: null,
quantity: 1,
namespaceId: null,
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Subscription',
},
customer: {
country: null,
address1: null,
address2: null,
city: null,
state: null,
zipCode: null,
company: null,
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Customer',
},
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'State',
},
activeStep: STEPS[0],
stepList: STEPS,
},
});
};
export default (el) => { export default (el) => {
if (!el) { if (!el) {
return null; return null;
} }
writeInitialDataToApolloProvider(el.dataset); const extendedApp = ensureData(App, {
parseData: writeInitialDataToApolloCache.bind(null, apolloProvider),
data: el.dataset,
shouldLog: true,
});
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
render(createElement) { render(createElement) {
return createElement(App); return createElement(extendedApp);
}, },
}); });
}; };
import { STEPS } from 'ee/subscriptions/constants';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
function arrayToGraphqlArray(arr, typename) {
return Array.from(arr, (item) => {
return Object.assign(convertObjectPropsToCamelCase(item, { deep: true }), {
__typename: typename,
});
});
}
export function writeInitialDataToApolloCache(apolloProvider, dataset) {
const { groupData, newUser, setupForCompany, fullName, planId = null } = dataset;
// eslint-disable-next-line @gitlab/require-i18n-strings
const namespaces = arrayToGraphqlArray(JSON.parse(groupData), 'Namespace');
const isNewUser = parseBoolean(newUser);
const isSetupForCompany = parseBoolean(setupForCompany) || !isNewUser;
apolloProvider.clients.defaultClient.cache.writeQuery({
query: stateQuery,
data: {
isNewUser,
isSetupForCompany,
namespaces,
fullName,
subscription: {
planId,
paymentMethodId: null,
quantity: 1,
namespaceId: null,
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Subscription',
},
customer: {
country: null,
address1: null,
address2: null,
city: null,
state: null,
zipCode: null,
company: null,
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Customer',
},
activeStep: STEPS[0],
stepList: STEPS,
},
});
}
...@@ -2,3 +2,13 @@ import { s__ } from '~/locale'; ...@@ -2,3 +2,13 @@ import { s__ } from '~/locale';
export const ERROR_FETCHING_COUNTRIES = s__('Checkout|Failed to load countries. Please try again.'); export const ERROR_FETCHING_COUNTRIES = s__('Checkout|Failed to load countries. Please try again.');
export const ERROR_FETCHING_STATES = s__('Checkout|Failed to load states. Please try again.'); export const ERROR_FETCHING_STATES = s__('Checkout|Failed to load states. Please try again.');
// The order of the steps in this array determines the flow of the application
/* 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 */
...@@ -3,13 +3,6 @@ query getPlans($tags: [PlanTag!]) { ...@@ -3,13 +3,6 @@ query getPlans($tags: [PlanTag!]) {
id id
name name
code code
active
deprecated
free
pricePerMonth
pricePerYear pricePerYear
features
aboutPageHref
hideDeprecatedCard
} }
} }
query state { query state {
state @client { namespaces @client {
namespaces { id
id name
name users
users }
} isNewUser @client
isNewUser fullName @client
fullName isSetupForCompany @client
isSetupForCompany customer @client {
customer { country
country address1
address1 address2
address2 city
city state
state zipCode
zipCode company
company }
} subscription @client {
subscription { planId
planId paymentMethodId
paymentMethodId quantity
quantity namespaceId
namespaceId
}
} }
activeStep @client { activeStep @client {
id id
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
import { GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { STEPS } from '../../constants';
export default { export default {
components: { components: {
......
<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 { STEPS } from 'ee/subscriptions/constants';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants'; 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 createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { STEPS } from '../../constants';
export default { export default {
components: { components: {
......
<script> <script>
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import { STEPS } from '../../constants';
import Zuora from './zuora.vue'; import Zuora from './zuora.vue';
export default { export default {
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui'; import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { NEW_GROUP, STEPS } from 'ee/subscriptions/new/constants'; import { STEPS } from 'ee/subscriptions/constants';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
......
// The order of the steps in this array determines the flow of the application
/* 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'; export const ZUORA_SCRIPT_URL = 'https://static.zuora.com/Resources/libs/hosted/1.3.1/zuora-min.js';
export const PAYMENT_FORM_ID = 'paid_signup_flow'; export const PAYMENT_FORM_ID = 'paid_signup_flow';
......
...@@ -3,7 +3,7 @@ import stepListQuery from 'ee/vue_shared/purchase_flow/graphql/queries/step_list ...@@ -3,7 +3,7 @@ import stepListQuery from 'ee/vue_shared/purchase_flow/graphql/queries/step_list
import resolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers'; import resolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import typeDefs from 'ee/vue_shared/purchase_flow/graphql/typedefs.graphql'; import typeDefs from 'ee/vue_shared/purchase_flow/graphql/typedefs.graphql';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { STEPS } from './constants'; import { STEPS } from '../constants';
function createClient(stepList) { function createClient(stepList) {
const client = createDefaultClient(resolvers, { const client = createDefaultClient(resolvers, {
......
...@@ -37,6 +37,41 @@ describe('App', () => { ...@@ -37,6 +37,41 @@ describe('App', () => {
}); });
describe('when data is not received', () => { describe('when data is not received', () => {
it('should display the GlEmptyState for empty data', async () => {
const mockApollo = createMockApolloProvider({
plansQueryMock: jest.fn().mockResolvedValue({ data: null }),
});
wrapper = createComponent({ apolloProvider: mockApollo });
await waitForPromises();
expect(wrapper.findComponent(StepOrderApp).exists()).toBe(false);
expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
});
it('should display the GlEmptyState for empty plans', async () => {
const mockApollo = createMockApolloProvider({
plansQueryMock: jest.fn().mockResolvedValue({ data: { plans: null } }),
});
wrapper = createComponent({ apolloProvider: mockApollo });
await waitForPromises();
expect(wrapper.findComponent(StepOrderApp).exists()).toBe(false);
expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
});
it('should display the GlEmptyState for plans data of wrong type', async () => {
const mockApollo = createMockApolloProvider({
plansQueryMock: jest.fn().mockResolvedValue({ data: { plans: {} } }),
});
wrapper = createComponent({ apolloProvider: mockApollo });
await waitForPromises();
expect(wrapper.findComponent(StepOrderApp).exists()).toBe(false);
expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
});
});
describe('when an error is received', () => {
it('should display the GlEmptyState', async () => { it('should display the GlEmptyState', async () => {
const mockApollo = createMockApolloProvider({ const mockApollo = createMockApolloProvider({
plansQueryMock: jest.fn().mockRejectedValue(new Error('An error happened!')), plansQueryMock: jest.fn().mockRejectedValue(new Error('An error happened!')),
......
...@@ -10,7 +10,7 @@ import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; ...@@ -10,7 +10,7 @@ import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers'; import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import { import {
stateData as initialStateData, stateData as initialStateData,
namespaces as defaultNamespaces, mockParsedNamespaces,
mockCiMinutesPlans, mockCiMinutesPlans,
} from 'ee_jest/subscriptions/buy_minutes/mock_data'; } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -61,7 +61,7 @@ describe('Subscription Details', () => { ...@@ -61,7 +61,7 @@ describe('Subscription Details', () => {
describe('A new user setting up for personal use', () => { describe('A new user setting up for personal use', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ state: { isNewUser: true, isSetupForCompany: false } }); wrapper = createComponent({ isNewUser: true, isSetupForCompany: false });
}); });
it('should not display an input field for the company or group name', () => { it('should not display an input field for the company or group name', () => {
...@@ -87,9 +87,7 @@ describe('Subscription Details', () => { ...@@ -87,9 +87,7 @@ describe('Subscription Details', () => {
describe('A new user setting up for a company or group', () => { describe('A new user setting up for a company or group', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({ isNewUser: true, isSetupForCompany: true, namespaces: [] });
state: { isNewUser: true, isSetupForCompany: true, namespaces: [] },
});
}); });
it('should display an input field for the company or group name', () => { it('should display an input field for the company or group name', () => {
...@@ -115,9 +113,7 @@ describe('Subscription Details', () => { ...@@ -115,9 +113,7 @@ describe('Subscription Details', () => {
describe('An existing user without any groups', () => { describe('An existing user without any groups', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({ isNewUser: false, namespaces: [] });
state: { isNewUser: false, namespaces: [] },
});
}); });
it('should display an input field for the company or group name', () => { it('should display an input field for the company or group name', () => {
...@@ -143,7 +139,7 @@ describe('Subscription Details', () => { ...@@ -143,7 +139,7 @@ describe('Subscription Details', () => {
describe('An existing user with groups', () => { describe('An existing user with groups', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ state: { isNewUser: false, namespaces: defaultNamespaces } }); wrapper = createComponent({ isNewUser: false, namespaces: mockParsedNamespaces });
}); });
it('should not display an input field for the company or group name', () => { it('should not display an input field for the company or group name', () => {
...@@ -170,7 +166,8 @@ describe('Subscription Details', () => { ...@@ -170,7 +166,8 @@ describe('Subscription Details', () => {
describe('selecting an existing group', () => { describe('selecting an existing group', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
state: { subscription: { namespaceId: 483 }, namespaces: defaultNamespaces }, subscription: { namespaceId: 483 },
namespaces: mockParsedNamespaces,
}); });
}); });
...@@ -186,7 +183,8 @@ describe('Subscription Details', () => { ...@@ -186,7 +183,8 @@ describe('Subscription Details', () => {
describe('selecting "Create a new group', () => { describe('selecting "Create a new group', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
state: { subscription: { namespaceId: NEW_GROUP }, namespaces: defaultNamespaces }, subscription: { namespaceId: NEW_GROUP },
namespaces: mockParsedNamespaces,
}); });
}); });
...@@ -206,12 +204,10 @@ describe('Subscription Details', () => { ...@@ -206,12 +204,10 @@ describe('Subscription Details', () => {
describe('An existing user coming from group billing page', () => { describe('An existing user coming from group billing page', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
state: { isNewUser: false,
isNewUser: false, isSetupForCompany: true,
isSetupForCompany: true, subscription: { namespaceId: 132 },
subscription: { namespaceId: 132 }, namespaces: mockParsedNamespaces,
namespaces: defaultNamespaces,
},
}); });
}); });
...@@ -242,7 +238,8 @@ describe('Subscription Details', () => { ...@@ -242,7 +238,8 @@ describe('Subscription Details', () => {
describe('selecting an existing group', () => { describe('selecting an existing group', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
state: { subscription: { namespaceId: 483 }, namespaces: defaultNamespaces }, subscription: { namespaceId: 483 },
namespaces: mockParsedNamespaces,
}); });
}); });
...@@ -266,10 +263,8 @@ describe('Subscription Details', () => { ...@@ -266,10 +263,8 @@ describe('Subscription Details', () => {
describe('when setting up for a company', () => { describe('when setting up for a company', () => {
it('should be valid', () => { it('should be valid', () => {
wrapper = createComponent({ wrapper = createComponent({
state: { subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 14 },
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 14 }, customer: { company: 'Organization name' },
customer: { company: 'Organization name' },
},
}); });
expect(isStepValid()).toBe(true); expect(isStepValid()).toBe(true);
...@@ -277,11 +272,9 @@ describe('Subscription Details', () => { ...@@ -277,11 +272,9 @@ describe('Subscription Details', () => {
it('should be invalid when no plan is selected', async () => { it('should be invalid when no plan is selected', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: true,
isSetupForCompany: true, subscription: { planId: null, namespaceId: 483, quantity: 14 },
subscription: { planId: null, namespaceId: 483, quantity: 14 }, customer: { company: 'Organization name' },
customer: { company: 'Organization name' },
},
}); });
await nextTick(); await nextTick();
...@@ -291,11 +284,9 @@ describe('Subscription Details', () => { ...@@ -291,11 +284,9 @@ describe('Subscription Details', () => {
it('should be invalid when no organization name is given, and no group is selected', async () => { it('should be invalid when no organization name is given, and no group is selected', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: true,
isSetupForCompany: true, subscription: { namespaceId: null },
subscription: { namespaceId: null }, customer: { company: null },
customer: { company: null },
},
}); });
await nextTick(); await nextTick();
...@@ -305,10 +296,8 @@ describe('Subscription Details', () => { ...@@ -305,10 +296,8 @@ describe('Subscription Details', () => {
it('should be invalid when number of users is 0', async () => { it('should be invalid when number of users is 0', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: true,
isSetupForCompany: true, subscription: { quantity: 0 },
subscription: { quantity: 0 },
},
}); });
await nextTick(); await nextTick();
...@@ -318,10 +307,8 @@ describe('Subscription Details', () => { ...@@ -318,10 +307,8 @@ describe('Subscription Details', () => {
it('should be invalid when number of users is smaller than the selected group users', async () => { it('should be invalid when number of users is smaller than the selected group users', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: true,
isSetupForCompany: true, subscription: { namespaceId: 483, quantity: 10 },
subscription: { namespaceId: 483, quantity: 10 },
},
}); });
await nextTick(); await nextTick();
...@@ -333,11 +320,9 @@ describe('Subscription Details', () => { ...@@ -333,11 +320,9 @@ describe('Subscription Details', () => {
describe('when not setting up for a company', () => { describe('when not setting up for a company', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: false,
isSetupForCompany: false, subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 1 },
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 1 }, customer: { company: 'Organization name' },
customer: { company: 'Organization name' },
},
}); });
}); });
...@@ -347,11 +332,9 @@ describe('Subscription Details', () => { ...@@ -347,11 +332,9 @@ describe('Subscription Details', () => {
it('should be invalid when no plan is selected', async () => { it('should be invalid when no plan is selected', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: false,
isSetupForCompany: false, subscription: { planId: null, namespaceId: 483, quantity: 1 },
subscription: { planId: null, namespaceId: 483, quantity: 1 }, customer: { company: 'Organization name' },
customer: { company: 'Organization name' },
},
}); });
await nextTick(); await nextTick();
...@@ -361,11 +344,9 @@ describe('Subscription Details', () => { ...@@ -361,11 +344,9 @@ describe('Subscription Details', () => {
it('should be invalid when no number of users is 0', async () => { it('should be invalid when no number of users is 0', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: false,
isSetupForCompany: false, subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 0 },
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 0 }, customer: { company: 'Organization name' },
customer: { company: 'Organization name' },
},
}); });
await nextTick(); await nextTick();
...@@ -375,11 +356,9 @@ describe('Subscription Details', () => { ...@@ -375,11 +356,9 @@ describe('Subscription Details', () => {
it('should be invalid when no number of users is greater than 1', async () => { it('should be invalid when no number of users is greater than 1', async () => {
wrapper = createComponent({ wrapper = createComponent({
state: { isSetupForCompany: false,
isSetupForCompany: false, subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 2 },
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 2 }, customer: { company: 'Organization name' },
customer: { company: 'Organization name' },
},
}); });
await nextTick(); await nextTick();
......
...@@ -11,12 +11,13 @@ import { ...@@ -11,12 +11,13 @@ import {
mockCiMinutesPlans, mockCiMinutesPlans,
} from 'ee_jest/subscriptions/buy_minutes/mock_data'; } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
describe('Checkout', () => { describe('Checkout', () => {
const resolvers = { ...purchaseFlowResolvers, ...subscriptionsResolvers }; const resolvers = merge({}, purchaseFlowResolvers, subscriptionsResolvers);
let wrapper; let wrapper;
const createMockApolloProvider = (stateData = {}) => { const createMockApolloProvider = (stateData = {}) => {
...@@ -54,8 +55,8 @@ describe('Checkout', () => { ...@@ -54,8 +55,8 @@ describe('Checkout', () => {
[true, true], [true, true],
[false, false], [false, false],
])('when isNewUser=%s', (isNewUser, visible) => { ])('when isNewUser=%s', (isNewUser, visible) => {
beforeEach(() => { beforeEach(async () => {
createComponent({ state: { isNewUser } }); createComponent({ isNewUser });
}); });
it(`progress bar visibility is ${visible}`, () => { it(`progress bar visibility is ${visible}`, () => {
...@@ -64,8 +65,9 @@ describe('Checkout', () => { ...@@ -64,8 +65,9 @@ describe('Checkout', () => {
}); });
describe('passing the correct options to the progress bar component', () => { describe('passing the correct options to the progress bar component', () => {
beforeEach(() => { beforeEach(async () => {
createComponent({ state: { isNewUser: true } }); createComponent({ isNewUser: true });
await waitForPromises();
}); });
it('passes the steps', () => { it('passes the steps', () => {
......
import { createWrapper } from '@vue/test-utils';
import initBuyMinutesApp from 'ee/subscriptions/buy_minutes';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { mockCiMinutesPlans } from './mock_data';
import { createMockApolloProvider } from './spec_helper';
jest.doMock('ee/subscriptions/buy_minutes/graphql', createMockApolloProvider());
describe('initBuyMinutesApp', () => {
let vm;
let wrapper;
function createComponent() {
const el = document.createElement('div');
Object.assign(el.dataset, {
planId: mockCiMinutesPlans[0].code,
groupData: '[]',
fullName: 'GitLab',
});
vm = initBuyMinutesApp(el).$mount();
wrapper = createWrapper(vm);
}
afterEach(() => {
if (vm) {
vm.$destroy();
}
wrapper.destroy();
vm = null;
});
it('displays the StepOrderApp', () => {
createComponent();
expect(wrapper.find(StepOrderApp).exists()).toBe(true);
});
});
import { STEPS } from 'ee/subscriptions/new/constants'; import { STEPS } from 'ee/subscriptions/constants';
export const mockCiMinutesPlans = [ export const mockCiMinutesPlans = [
{ { id: 'firstPlanId', code: 'bronze', pricePerYear: 48, name: 'bronze', __typename: 'Plan' },
id: 'ci_minutes', { id: 'secondPlanId', code: 'silver', pricePerYear: 228, name: 'silver', __typename: 'Plan' },
deprecated: false,
name: '1000 CI minutes pack',
code: 'ci_minutes',
active: true,
free: null,
pricePerMonth: 0.8333333333333334,
pricePerYear: 10.0,
features: null,
aboutPageHref: null,
hideDeprecatedCard: false,
},
]; ];
export const mockNamespaces =
'[{"id":132,"name":"Gitlab Org","users":3},{"id":483,"name":"Gnuwget","users":12}]';
export const namespaces = [ export const mockParsedNamespaces = [
{ id: 132, name: 'My first group', users: 3, __typename: 'Namespace' }, { __typename: 'Namespace', id: 132, name: 'Gitlab Org', users: 3 },
{ id: 483, name: 'My second group', users: 12, __typename: 'Namespace' }, { __typename: 'Namespace', id: 483, name: 'Gnuwget', users: 12 },
]; ];
export const plans = [ export const mockNewUser = 'false';
{ id: 'firstPlanId', code: 'bronze', pricePerYear: 48, name: 'bronze', __typename: 'Plan' }, export const mockFullName = 'John Admin';
{ id: 'secondPlanId', code: 'silver', pricePerYear: 228, name: 'silver', __typename: 'Plan' }, export const mockSetupForCompany = 'true';
];
export const stateData = { export const stateData = {
state: { namespaces: [],
plans, subscription: {
namespaces: [], planId: 'secondPlanId',
subscription: { quantity: 1,
planId: 'secondPlanId', namespaceId: null,
quantity: 1, paymentMethodId: null,
namespaceId: null, __typename: 'Subscription',
paymentMethodId: null, },
__typename: 'Subscription', customer: {
}, country: null,
customer: { address1: null,
country: null, address2: null,
address1: null, city: null,
address2: null, state: null,
city: null, zipCode: null,
state: null, company: null,
zipCode: null, __typename: 'Customer',
company: null,
__typename: 'Customer',
},
fullName: 'Full Name',
isNewUser: false,
isSetupForCompany: true,
}, },
fullName: 'Full Name',
isNewUser: false,
isSetupForCompany: true,
stepList: STEPS, stepList: STEPS,
activeStep: STEPS[0], activeStep: STEPS[0],
}; };
import apolloProvider from 'ee/subscriptions/buy_minutes/graphql';
import { writeInitialDataToApolloCache } from 'ee/subscriptions/buy_minutes/utils';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import {
mockNamespaces,
mockParsedNamespaces,
mockNewUser,
mockFullName,
mockSetupForCompany,
} from './mock_data';
const DEFAULT_DATA = {
groupData: mockNamespaces,
newUser: mockNewUser,
fullName: mockFullName,
setupForCompany: mockSetupForCompany,
};
describe('utils', () => {
beforeEach(() => {
apolloProvider.clients.defaultClient.clearStore();
});
describe('#writeInitialDataToApolloCache', () => {
describe('namespaces', () => {
describe.each`
namespaces | parsedNamespaces | throws
${'[]'} | ${[]} | ${false}
${'null'} | ${{}} | ${true}
${''} | ${{}} | ${true}
${mockNamespaces} | ${mockParsedNamespaces} | ${false}
`('parameter decoding', ({ namespaces, parsedNamespaces, throws }) => {
it(`decodes ${namespaces} to ${parsedNamespaces}`, async () => {
if (throws) {
expect(() => {
writeInitialDataToApolloCache(apolloProvider, { groupData: namespaces });
}).toThrow();
} else {
writeInitialDataToApolloCache(apolloProvider, {
...DEFAULT_DATA,
groupData: namespaces,
});
const sourceData = await apolloProvider.clients.defaultClient.query({
query: stateQuery,
});
expect(sourceData.data.namespaces).toStrictEqual(parsedNamespaces);
}
});
});
});
describe('newUser', () => {
describe.each`
newUser | parsedNewUser | throws
${'true'} | ${true} | ${false}
${mockNewUser} | ${false} | ${false}
${''} | ${false} | ${true}
`('parameter decoding', ({ newUser, parsedNewUser, throws }) => {
it(`decodes ${newUser} to ${parsedNewUser}`, async () => {
if (throws) {
expect(() => {
writeInitialDataToApolloCache(apolloProvider, { newUser });
}).toThrow();
} else {
writeInitialDataToApolloCache(apolloProvider, { ...DEFAULT_DATA, newUser });
const sourceData = await apolloProvider.clients.defaultClient.query({
query: stateQuery,
});
expect(sourceData.data.isNewUser).toEqual(parsedNewUser);
}
});
});
});
describe('fullName', () => {
describe.each`
fullName | parsedFullName
${mockFullName} | ${mockFullName}
${''} | ${''}
${null} | ${null}
`('parameter decoding', ({ fullName, parsedFullName }) => {
it(`decodes ${fullName} to ${parsedFullName}`, async () => {
writeInitialDataToApolloCache(apolloProvider, { ...DEFAULT_DATA, fullName });
const sourceData = await apolloProvider.clients.defaultClient.query({
query: stateQuery,
});
expect(sourceData.data.fullName).toEqual(parsedFullName);
});
});
});
describe('setupForCompany', () => {
describe.each`
setupForCompany | parsedSetupForCompany | throws
${mockSetupForCompany} | ${true} | ${false}
${'false'} | ${false} | ${false}
${''} | ${false} | ${true}
`('parameter decoding', ({ setupForCompany, parsedSetupForCompany, throws }) => {
it(`decodes ${setupForCompany} to ${parsedSetupForCompany}`, async () => {
if (throws) {
expect(() => {
writeInitialDataToApolloCache(apolloProvider, { setupForCompany });
}).toThrow();
} else {
writeInitialDataToApolloCache(apolloProvider, {
...DEFAULT_DATA,
newUser: 'true',
setupForCompany,
});
const sourceData = await apolloProvider.clients.defaultClient.query({
query: stateQuery,
});
expect(sourceData.data.isSetupForCompany).toEqual(parsedSetupForCompany);
}
});
});
});
});
});
...@@ -2,8 +2,8 @@ import { mount, createLocalVue } from '@vue/test-utils'; ...@@ -2,8 +2,8 @@ import { mount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import BillingAddress from 'ee/subscriptions/new/components/checkout/billing_address.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 { getStoreConfig } from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types'; import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
......
...@@ -3,8 +3,8 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; ...@@ -3,8 +3,8 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Vuex from 'vuex'; import Vuex from 'vuex';
import Api from 'ee/api'; import Api from 'ee/api';
import { STEPS } from 'ee/subscriptions/constants';
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 createStore from 'ee/subscriptions/new/store'; 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 { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper'; import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
......
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import PaymentMethod from 'ee/subscriptions/new/components/checkout/payment_method.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 createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types'; import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
......
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import Component from 'ee/subscriptions/new/components/checkout/subscription_details.vue'; import Component from 'ee/subscriptions/new/components/checkout/subscription_details.vue';
import { NEW_GROUP, STEPS } from 'ee/subscriptions/new/constants'; import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import createStore from 'ee/subscriptions/new/store'; import createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types'; import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue'; import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
......
...@@ -9,7 +9,7 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => { ...@@ -9,7 +9,7 @@ describe('ee/vue_shared/purchase_flow/graphql/resolvers', () => {
let mockApolloClient; let mockApolloClient;
describe('Query', () => { describe('Query', () => {
beforeEach(async () => { beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS, 0); const mockApollo = createMockApolloProvider(STEPS, 0);
mockApolloClient = mockApollo.clients.defaultClient; mockApolloClient = mockApollo.clients.defaultClient;
}); });
......
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