Commit 731b929e authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'vs/migrate-billing-address-to-gql' into 'master'

Migrate BillingAddress to GraphQL

See merge request gitlab-org/gitlab!63492
parents 3cb81609 5da8300a
...@@ -3,21 +3,17 @@ import ProgressBar from 'ee/registrations/components/progress_bar.vue'; ...@@ -3,21 +3,17 @@ import ProgressBar from 'ee/registrations/components/progress_bar.vue';
import { STEPS, SUBSCRIPTON_FLOW_STEPS } from 'ee/registrations/constants'; import { STEPS, SUBSCRIPTON_FLOW_STEPS } from 'ee/registrations/constants';
import STATE_QUERY from 'ee/subscriptions/graphql/queries/state.query.graphql'; import STATE_QUERY from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import BillingAddress from './checkout/billing_address.vue';
import SubscriptionDetails from './checkout/subscription_details.vue'; import SubscriptionDetails from './checkout/subscription_details.vue';
export default { export default {
components: { ProgressBar, SubscriptionDetails }, components: { ProgressBar, SubscriptionDetails, BillingAddress },
props: { props: {
plans: { plans: {
type: Array, type: Array,
required: true, required: true,
}, },
}, },
data() {
return {
isNewUser: null,
};
},
apollo: { apollo: {
isNewUser: { isNewUser: {
query: STATE_QUERY, query: STATE_QUERY,
...@@ -40,6 +36,7 @@ export default { ...@@ -40,6 +36,7 @@ export default {
<div class="flash-container"></div> <div class="flash-container"></div>
<h2 class="gl-mt-6 gl-mb-7 gl-mb-lg-5">{{ $options.i18n.checkout }}</h2> <h2 class="gl-mt-6 gl-mb-7 gl-mb-lg-5">{{ $options.i18n.checkout }}</h2>
<subscription-details :plans="plans" /> <subscription-details :plans="plans" />
<billing-address />
</div> </div>
</div> </div>
</template> </template>
<script>
import { GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { STEPS } from 'ee/subscriptions/constants';
import updateStateMutation from 'ee/subscriptions/graphql/mutations/update_state.mutation.graphql';
import countriesQuery from 'ee/subscriptions/graphql/queries/countries.query.graphql';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import statesQuery from 'ee/subscriptions/graphql/queries/states.query.graphql';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
export default {
components: {
Step,
GlFormGroup,
GlFormInput,
GlFormSelect,
},
directives: {
autofocusonshow,
},
data() {
return {
countries: [],
};
},
apollo: {
customer: {
query: stateQuery,
},
countries: {
query: countriesQuery,
},
states: {
query: statesQuery,
skip() {
return !this.customer.country;
},
variables() {
return {
countryId: this.customer.country,
};
},
},
},
computed: {
countryModel: {
get() {
return this.customer.country;
},
set(country) {
this.updateState({ customer: { country, state: null } });
},
},
streetAddressLine1Model: {
get() {
return this.customer.address1;
},
set(address1) {
this.updateState({ customer: { address1 } });
},
},
streetAddressLine2Model: {
get() {
return this.customer.address2;
},
set(address2) {
this.updateState({ customer: { address2 } });
},
},
cityModel: {
get() {
return this.customer.city;
},
set(city) {
this.updateState({ customer: { city } });
},
},
countryStateModel: {
get() {
return this.customer.state;
},
set(state) {
this.updateState({ customer: { state } });
},
},
zipCodeModel: {
get() {
return this.customer.zipCode;
},
set(zipCode) {
this.updateState({ customer: { zipCode } });
},
},
isValid() {
return (
!isEmpty(this.customer.country) &&
!isEmpty(this.customer.address1) &&
!isEmpty(this.customer.city) &&
!isEmpty(this.customer.zipCode)
);
},
countryOptionsWithDefault() {
return [
{
name: this.$options.i18n.countrySelectPrompt,
id: null,
},
...this.countries,
];
},
stateOptionsWithDefault() {
return [
{
name: this.$options.i18n.stateSelectPrompt,
id: null,
},
...this.states,
];
},
selectedStateName() {
if (!this.customer.state || !this.states) {
return '';
}
return this.states.find((state) => state.id === this.customer.state).name;
},
},
methods: {
updateState(payload) {
this.$apollo
.mutate({
mutation: updateStateMutation,
variables: {
input: payload,
},
})
.catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
});
},
},
i18n: {
stepTitle: s__('Checkout|Billing address'),
nextStepButtonText: s__('Checkout|Continue to payment'),
countryLabel: s__('Checkout|Country'),
countrySelectPrompt: s__('Checkout|Please select a country'),
streetAddressLabel: s__('Checkout|Street address'),
cityLabel: s__('Checkout|City'),
stateLabel: s__('Checkout|State'),
stateSelectPrompt: s__('Checkout|Please select a state'),
zipCodeLabel: s__('Checkout|Zip code'),
},
stepId: STEPS[1].id,
};
</script>
<template>
<step
v-if="!$apollo.loading.customer"
:step-id="$options.stepId"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
>
<template #body>
<gl-form-group
v-if="!$apollo.loading.countries"
:label="$options.i18n.countryLabel"
label-size="sm"
class="mb-3"
>
<gl-form-select
v-model="countryModel"
v-autofocusonshow
:options="countryOptionsWithDefault"
class="js-country"
value-field="id"
text-field="name"
data-qa-selector="country"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.streetAddressLabel" label-size="sm" class="mb-3">
<gl-form-input
v-model="streetAddressLine1Model"
type="text"
data-qa-selector="street_address_1"
/>
<gl-form-input
v-model="streetAddressLine2Model"
type="text"
data-qa-selector="street_address_2"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.cityLabel" label-size="sm" class="mb-3">
<gl-form-input v-model="cityModel" type="text" data-qa-selector="city" />
</gl-form-group>
<div class="combined d-flex">
<gl-form-group
v-if="!$apollo.loading.states && states"
:label="$options.i18n.stateLabel"
label-size="sm"
class="mr-3 w-50"
>
<gl-form-select
v-model="countryStateModel"
:options="stateOptionsWithDefault"
value-field="id"
text-field="name"
data-qa-selector="state"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.zipCodeLabel" label-size="sm" class="w-50">
<gl-form-input v-model="zipCodeModel" type="text" data-qa-selector="zip_code" />
</gl-form-group>
</div>
</template>
<template #summary>
<div class="js-summary-line-1">{{ customer.address1 }}</div>
<div class="js-summary-line-2">{{ customer.address2 }}</div>
<div class="js-summary-line-3">
{{ customer.city }}, {{ customer.country }} {{ selectedStateName }} {{ customer.zipCode }}
</div>
</template>
</step>
</template>
...@@ -6,6 +6,9 @@ import UPDATE_STATE from 'ee/subscriptions/graphql/mutations/update_state.mutati ...@@ -6,6 +6,9 @@ import UPDATE_STATE from 'ee/subscriptions/graphql/mutations/update_state.mutati
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 } 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 { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import createFlash from '~/flash';
import { getParameterValues } from '~/lib/utils/url_utility';
import { sprintf, s__, __ } from '~/locale'; import { sprintf, s__, __ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
...@@ -34,45 +37,34 @@ export default { ...@@ -34,45 +37,34 @@ export default {
customer: {}, customer: {},
isSetupForCompany: null, isSetupForCompany: null,
isNewUser: null, isNewUser: null,
selectedPlanId: null,
}; };
}, },
apollo: { apollo: {
state: { state: {
query: STATE_QUERY, query: STATE_QUERY,
update(data) { manual: true,
const { result({ data, loading }) {
subscription = {}, if (loading) {
namespaces = [], return;
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.subscription = data.subscription;
this.namespaces = namespaces; this.namespaces = data.namespaces;
this.customer = customer; this.customer = data.customer;
this.isSetupForCompany = isSetupForCompany; this.isSetupForCompany = data.isSetupForCompany;
this.isNewUser = isNewUser; this.isNewUser = data.isNewUser;
this.selectedPlanId = data.selectedPlanId;
}, },
}, },
}, },
computed: { computed: {
selectedPlanModel: { selectedPlanModel: {
get() { get() {
return this.subscription.planId || this.plans[0].code; return this.selectedPlanId || this.plans[0].id;
}, },
set(planId) { set(planId) {
this.updateSubscription({ subscription: { planId } }); this.updateState({ subscription: { planId } });
}, },
}, },
selectedGroupModel: { selectedGroupModel: {
...@@ -83,7 +75,7 @@ export default { ...@@ -83,7 +75,7 @@ export default {
const quantity = const quantity =
this.namespaces.find((namespace) => namespace.id === namespaceId)?.users || 1; this.namespaces.find((namespace) => namespace.id === namespaceId)?.users || 1;
this.updateSubscription({ subscription: { namespaceId, quantity } }); this.updateState({ subscription: { namespaceId, quantity } });
}, },
}, },
numberOfUsersModel: { numberOfUsersModel: {
...@@ -91,7 +83,7 @@ export default { ...@@ -91,7 +83,7 @@ export default {
return this.selectedGroupUsers || 1; return this.selectedGroupUsers || 1;
}, },
set(number) { set(number) {
this.updateSubscription({ subscription: { quantity: number } }); this.updateState({ subscription: { quantity: number } });
}, },
}, },
companyModel: { companyModel: {
...@@ -99,11 +91,11 @@ export default { ...@@ -99,11 +91,11 @@ export default {
return this.customer.company; return this.customer.company;
}, },
set(company) { set(company) {
this.updateSubscription({ customer: { company } }); this.updateState({ customer: { company } });
}, },
}, },
selectedPlan() { selectedPlan() {
const selectedPlan = this.plans.find((plan) => plan.code === this.subscription.planId); const selectedPlan = this.plans.find((plan) => plan.id === this.selectedPlanId);
if (!selectedPlan) { if (!selectedPlan) {
return this.plans[0]; return this.plans[0];
} }
...@@ -111,13 +103,13 @@ export default { ...@@ -111,13 +103,13 @@ export default {
return selectedPlan; return selectedPlan;
}, },
selectedPlanTextLine() { selectedPlanTextLine() {
return sprintf(this.$options.i18n.selectedPlan, { selectedPlanText: this.selectedPlan.code }); return sprintf(this.$options.i18n.selectedPlan, { selectedPlanText: this.selectedPlan.id });
},
selectedGroup() {
return this.namespaces.find((namespace) => namespace.id === this.subscription.namespaceId);
}, },
selectedGroupUsers() { selectedGroupUsers() {
return ( return this.selectedGroup?.users || 1;
this.namespaces.find((namespace) => namespace.id === this.subscription.namespaceId)
?.users || 1
);
}, },
isGroupSelected() { isGroupSelected() {
return this.subscription.namespaceId !== null; return this.subscription.namespaceId !== null;
...@@ -130,13 +122,13 @@ export default { ...@@ -130,13 +122,13 @@ export default {
isValid() { isValid() {
if (this.isSetupForCompany) { if (this.isSetupForCompany) {
return ( return (
!isEmpty(this.subscription.planId) && this.isNumberOfUsersValid &&
(!isEmpty(this.customer.company) || this.isNewGroupSelected) && !isEmpty(this.selectedPlanId) &&
this.isNumberOfUsersValid (!isEmpty(this.customer.company) || this.isGroupSelected)
); );
} }
return !isEmpty(this.subscription.planId) && this.subscription.quantity === 1; return this.subscription.quantity === 1 && !isEmpty(this.selectedPlanId);
}, },
isShowingGroupSelector() { isShowingGroupSelector() {
return !this.isNewUser && this.namespaces.length; return !this.isNewUser && this.namespaces.length;
...@@ -166,18 +158,41 @@ export default { ...@@ -166,18 +158,41 @@ export default {
: this.$options.i18n.selectedGroupDescription; : this.$options.i18n.selectedGroupDescription;
}, },
}, },
mounted() {
this.preselectPlan();
},
methods: { methods: {
updateSubscription(payload = {}) { updateState(payload = {}) {
this.$apollo.mutate({ this.$apollo
.mutate({
mutation: UPDATE_STATE, mutation: UPDATE_STATE,
variables: { variables: {
input: payload, input: payload,
}, },
})
.catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
}); });
}, },
toggleIsSetupForCompany() { toggleIsSetupForCompany() {
this.updateSubscription({ isSetupForCompany: !this.isSetupForCompany }); this.updateSubscription({ isSetupForCompany: !this.isSetupForCompany });
}, },
preselectPlan() {
if (this.selectedPlanId) {
return;
}
let preselectedPlan = this.plans[0];
const planIdFromSearchParams = getParameterValues('planId');
if (planIdFromSearchParams.length > 0) {
preselectedPlan =
this.plans.find((plan) => plan.id === planIdFromSearchParams[0].id) || preselectedPlan;
}
this.updateState({ selectedPlanId: preselectedPlan.id });
},
}, },
i18n: { i18n: {
stepTitle: s__('Checkout|Subscription details'), stepTitle: s__('Checkout|Subscription details'),
...@@ -213,7 +228,7 @@ export default { ...@@ -213,7 +228,7 @@ export default {
v-model="selectedPlanModel" v-model="selectedPlanModel"
v-autofocusonshow v-autofocusonshow
:options="plans" :options="plans"
value-field="code" value-field="id"
text-field="name" text-field="name"
data-qa-selector="plan_name" data-qa-selector="plan_name"
/> />
...@@ -271,7 +286,7 @@ export default { ...@@ -271,7 +286,7 @@ export default {
{{ selectedPlanTextLine }} {{ selectedPlanTextLine }}
</strong> </strong>
<div v-if="isSetupForCompany" ref="summary-line-2"> <div v-if="isSetupForCompany" ref="summary-line-2">
{{ $options.i18n.group }}: {{ customer.company || selectedGroupName }} {{ $options.i18n.group }}: {{ customer.company || selectedGroup.name }}
</div> </div>
<div ref="summary-line-3">{{ $options.i18n.users }}: {{ subscription.quantity }}</div> <div ref="summary-line-3">{{ $options.i18n.users }}: {{ subscription.quantity }}</div>
</template> </template>
......
...@@ -21,15 +21,14 @@ export default { ...@@ -21,15 +21,14 @@ export default {
apollo: { apollo: {
state: { state: {
query: STATE_QUERY, query: STATE_QUERY,
update(data) { manual: true,
return data;
},
result({ data }) { result({ data }) {
this.subscription = data.subscription; this.subscription = data.subscription;
this.namespaces = data.namespaces; this.namespaces = data.namespaces;
this.isSetupForCompany = data.isSetupForCompany; this.isSetupForCompany = data.isSetupForCompany;
this.fullName = data.fullName; this.fullName = data.fullName;
this.customer = data.customer; this.customer = data.customer;
this.selectedPlanId = data.selectedPlanId;
}, },
}, },
}, },
...@@ -41,11 +40,12 @@ export default { ...@@ -41,11 +40,12 @@ export default {
collapsed: true, collapsed: true,
fullName: null, fullName: null,
customer: {}, customer: {},
selectedPlanId: null,
}; };
}, },
computed: { computed: {
selectedPlan() { selectedPlan() {
return this.plans.find((plan) => plan.code === this.subscription.planId); return this.plans.find((plan) => plan.code === this.selectedPlanId);
}, },
selectedPlanPrice() { selectedPlanPrice() {
return this.selectedPlan.pricePerYear; return this.selectedPlan.pricePerYear;
...@@ -106,7 +106,7 @@ export default { ...@@ -106,7 +106,7 @@ export default {
</script> </script>
<template> <template>
<div <div
v-if="!$apollo.loading && (!isGroupSelected || isSelectedGroupPresent)" v-if="!$apollo.loading && (!isGroupSelected || isSelectedGroupPresent) && selectedPlan"
class="order-summary gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-mt-2 mt-lg-5" class="order-summary gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-mt-2 mt-lg-5"
> >
<div class="d-lg-none"> <div class="d-lg-none">
......
...@@ -4,3 +4,4 @@ export const planTags = { ...@@ -4,3 +4,4 @@ export const planTags = {
}; };
/* eslint-enable @gitlab/require-i18n-strings */ /* eslint-enable @gitlab/require-i18n-strings */
export const CUSTOMER_CLIENT = 'customerClient'; export const CUSTOMER_CLIENT = 'customerClient';
export const GITLAB_CLIENT = 'gitlabClient';
import { merge } from 'lodash';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import typeDefs from 'ee/vue_shared/purchase_flow/graphql/typedefs.graphql';
import createClient from '~/lib/graphql'; import createClient from '~/lib/graphql';
import { CUSTOMER_CLIENT } from './constants'; import { GITLAB_CLIENT, CUSTOMER_CLIENT } from './constants';
import { resolvers } from './graphql/resolvers'; import { resolvers } from './graphql/resolvers';
Vue.use(VueApollo); Vue.use(VueApollo);
const defaultClient = createClient(resolvers, { assumeImmutableResults: true }); const gitlabClient = createClient(merge({}, resolvers, purchaseFlowResolvers), {
typeDefs,
assumeImmutableResults: true,
});
const customerClient = createClient( const customerClient = createClient(
{}, {},
{ path: '/-/customers_dot/proxy/graphql', useGet: true, assumeImmutableResults: true }, {
path: '/-/customers_dot/proxy/graphql',
useGet: true,
assumeImmutableResults: true,
},
); );
export default new VueApollo({ export default new VueApollo({
defaultClient, defaultClient: gitlabClient,
clients: { clients: {
[GITLAB_CLIENT]: gitlabClient,
[CUSTOMER_CLIENT]: customerClient, [CUSTOMER_CLIENT]: customerClient,
}, },
}); });
...@@ -3,7 +3,7 @@ import { merge } from 'lodash'; ...@@ -3,7 +3,7 @@ import { merge } from 'lodash';
import Api from 'ee/api'; import Api from 'ee/api';
import * as SubscriptionsApi from 'ee/api/subscriptions_api'; import * as SubscriptionsApi from 'ee/api/subscriptions_api';
import { ERROR_FETCHING_COUNTRIES, ERROR_FETCHING_STATES } from 'ee/subscriptions/constants'; import { ERROR_FETCHING_COUNTRIES, ERROR_FETCHING_STATES } from 'ee/subscriptions/constants';
import STATE_QUERY from 'ee/subscriptions/graphql/queries/state.query.graphql'; import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import createFlash from '~/flash'; import createFlash from '~/flash';
// NOTE: These resolvers are temporary and will be removed in the future. // NOTE: These resolvers are temporary and will be removed in the future.
...@@ -15,16 +15,20 @@ export const resolvers = { ...@@ -15,16 +15,20 @@ export const resolvers = {
.then(({ data }) => .then(({ data }) =>
data.map(([name, alpha2]) => data.map(([name, alpha2]) =>
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
({ name, alpha2, __typename: 'Country' }), ({ name, id: alpha2, __typename: 'Country' }),
), ),
) )
.catch(() => createFlash({ message: ERROR_FETCHING_COUNTRIES })); .catch(() => createFlash({ message: ERROR_FETCHING_COUNTRIES }));
}, },
states: (countryId) => { states: (_, { countryId }) => {
return Api.fetchStates(countryId) return Api.fetchStates(countryId)
.then(({ data }) => { .then(({ data }) => {
return Object.entries(data).map(([key, value]) => ({
id: value,
name: key,
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
return data.map((state) => Object.assign(state, { __typename: 'State' })); __typename: 'State',
}));
}) })
.catch(() => createFlash({ message: ERROR_FETCHING_STATES })); .catch(() => createFlash({ message: ERROR_FETCHING_STATES }));
}, },
...@@ -34,13 +38,13 @@ export const resolvers = { ...@@ -34,13 +38,13 @@ export const resolvers = {
return SubscriptionsApi.createSubscription(groupId, customer, subscription); return SubscriptionsApi.createSubscription(groupId, customer, subscription);
}, },
updateState: (_, { input }, { cache }) => { updateState: (_, { input }, { cache }) => {
const oldState = cache.readQuery({ query: STATE_QUERY }); const oldState = cache.readQuery({ query: stateQuery });
const state = produce(oldState, (draftState) => { const state = produce(oldState, (draftState) => {
merge(draftState, input); merge(draftState, input);
}); });
cache.writeQuery({ query: STATE_QUERY, data: state }); cache.writeQuery({ query: stateQuery, data: state });
}, },
}, },
}; };
...@@ -24,8 +24,8 @@ export function writeInitialDataToApolloCache(apolloProvider, dataset) { ...@@ -24,8 +24,8 @@ export function writeInitialDataToApolloCache(apolloProvider, dataset) {
isSetupForCompany, isSetupForCompany,
namespaces, namespaces,
fullName, fullName,
selectedPlanId: planId,
subscription: { subscription: {
planId,
paymentMethodId: null, paymentMethodId: null,
quantity: 1, quantity: 1,
namespaceId: null, namespaceId: null,
......
query state { query State {
namespaces @client { namespaces @client {
id id
name name
...@@ -7,6 +7,7 @@ query state { ...@@ -7,6 +7,7 @@ query state {
isNewUser @client isNewUser @client
fullName @client fullName @client
isSetupForCompany @client isSetupForCompany @client
selectedPlanId @client
customer @client { customer @client {
country country
address1 address1
...@@ -17,7 +18,6 @@ query state { ...@@ -17,7 +18,6 @@ query state {
company company
} }
subscription @client { subscription @client {
planId
paymentMethodId paymentMethodId
quantity quantity
namespaceId namespaceId
......
query State($countryId: ID!) {
states(countryId: $countryId) @client {
id
name
}
}
import { mount, createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash';
import VueApollo from 'vue-apollo';
import BillingAddress from 'ee/subscriptions/buy_minutes/components/checkout/billing_address.vue';
import { resolvers } from 'ee/subscriptions/buy_minutes/graphql/resolvers';
import { STEPS } from 'ee/subscriptions/constants';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { stateData as initialStateData } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
import waitForPromises from 'helpers/wait_for_promises';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('Billing Address', () => {
let wrapper;
const apolloResolvers = {
Query: {
countries: jest.fn().mockResolvedValue([
{ id: 'NL', name: 'Netherlands' },
{ id: 'US', name: 'United States of America' },
]),
states: jest.fn().mockResolvedValue([{ id: 'CA', name: 'California' }]),
},
};
const createComponent = (apolloLocalState = {}) => {
const apolloProvider = createMockApolloProvider(STEPS, STEPS[1], {
...resolvers,
...apolloResolvers,
});
apolloProvider.clients.defaultClient.cache.writeQuery({
query: stateQuery,
data: merge({}, initialStateData, apolloLocalState),
});
return mount(BillingAddress, {
localVue,
apolloProvider,
});
};
describe('country options', () => {
const countrySelect = () => wrapper.find('.js-country');
beforeEach(() => {
wrapper = createComponent();
return waitForPromises();
});
afterEach(() => {
wrapper.destroy();
});
it('displays the countries returned from the server', () => {
expect(countrySelect().html()).toContain('<option value="NL">Netherlands</option>');
});
});
describe('validations', () => {
const isStepValid = () => wrapper.find(Step).props('isValid');
const customerData = {
country: 'US',
address1: 'address line 1',
address2: 'address line 2',
city: 'city',
zipCode: 'zip',
state: null,
};
it('is valid when country, streetAddressLine1, city and zipCode have been entered', async () => {
wrapper = createComponent({ customer: customerData });
await waitForPromises();
expect(isStepValid()).toBe(true);
});
it('is invalid when country is undefined', async () => {
wrapper = createComponent({ customer: { country: null } });
await waitForPromises();
expect(isStepValid()).toBe(false);
});
it('is invalid when streetAddressLine1 is undefined', async () => {
wrapper = createComponent({ customer: { address1: null } });
await waitForPromises();
expect(isStepValid()).toBe(false);
});
it('is invalid when city is undefined', async () => {
wrapper = createComponent({ customer: { city: null } });
await waitForPromises();
expect(isStepValid()).toBe(false);
});
it('is invalid when zipCode is undefined', async () => {
wrapper = createComponent({ customer: { zipCode: null } });
await waitForPromises();
expect(isStepValid()).toBe(false);
});
});
describe('showing the summary', () => {
beforeEach(async () => {
wrapper = createComponent({
customer: {
country: 'US',
address1: 'address line 1',
address2: 'address line 2',
city: 'city',
zipCode: 'zip',
state: 'CA',
},
});
await waitForPromises();
});
it('should show the entered address line 1', () => {
expect(wrapper.find('.js-summary-line-1').text()).toBe('address line 1');
});
it('should show the entered address line 2', () => {
expect(wrapper.find('.js-summary-line-2').text()).toBe('address line 2');
});
it('should show the entered address city, state and zip code', () => {
expect(wrapper.find('.js-summary-line-3').text()).toBe('city, US California zip');
});
});
});
...@@ -17,7 +17,7 @@ localVue.use(VueApollo); ...@@ -17,7 +17,7 @@ localVue.use(VueApollo);
describe('Order Summary', () => { describe('Order Summary', () => {
const resolvers = { ...purchaseFlowResolvers, ...subscriptionsResolvers }; const resolvers = { ...purchaseFlowResolvers, ...subscriptionsResolvers };
const initialStateData = { const initialStateData = {
subscription: { planId: 'silver' }, selectedPlanId: 'silver',
}; };
let wrapper; let wrapper;
...@@ -105,7 +105,8 @@ describe('Order Summary', () => { ...@@ -105,7 +105,8 @@ describe('Order Summary', () => {
describe('the default plan', () => { describe('the default plan', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
subscription: { planId: 'bronze', quantity: 1 }, subscription: { quantity: 1 },
selectedPlanId: 'bronze',
}); });
}); });
...@@ -126,7 +127,8 @@ describe('Order Summary', () => { ...@@ -126,7 +127,8 @@ describe('Order Summary', () => {
describe('Changing the number of users', () => { describe('Changing the number of users', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
subscription: { planId: 'silver', quantity: 1 }, subscription: { quantity: 1 },
selectedPlanId: 'silver',
}); });
}); });
...@@ -151,7 +153,8 @@ describe('Order Summary', () => { ...@@ -151,7 +153,8 @@ describe('Order Summary', () => {
describe('3 selected users', () => { describe('3 selected users', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
subscription: { planId: 'silver', quantity: 3 }, subscription: { quantity: 3 },
selectedPlanId: 'silver',
}); });
}); });
...@@ -175,7 +178,8 @@ describe('Order Summary', () => { ...@@ -175,7 +178,8 @@ describe('Order Summary', () => {
describe('no selected users', () => { describe('no selected users', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
subscription: { planId: 'silver', quantity: 0 }, subscription: { quantity: 0 },
selectedPlanId: 'silver',
}); });
}); });
......
...@@ -263,25 +263,14 @@ describe('Subscription Details', () => { ...@@ -263,25 +263,14 @@ 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({
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 14 }, subscription: { namespaceId: 483, quantity: 14 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' }, customer: { company: 'Organization name' },
}); });
expect(isStepValid()).toBe(true); expect(isStepValid()).toBe(true);
}); });
it('should be invalid when no plan is selected', async () => {
wrapper = createComponent({
isSetupForCompany: true,
subscription: { planId: null, namespaceId: 483, quantity: 14 },
customer: { company: 'Organization name' },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
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({
isSetupForCompany: true, isSetupForCompany: true,
...@@ -321,7 +310,8 @@ describe('Subscription Details', () => { ...@@ -321,7 +310,8 @@ describe('Subscription Details', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
isSetupForCompany: false, isSetupForCompany: false,
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 1 }, subscription: { namespaceId: 483, quantity: 1 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' }, customer: { company: 'Organization name' },
}); });
}); });
...@@ -330,22 +320,11 @@ describe('Subscription Details', () => { ...@@ -330,22 +320,11 @@ describe('Subscription Details', () => {
expect(isStepValid()).toBe(true); expect(isStepValid()).toBe(true);
}); });
it('should be invalid when no plan is selected', async () => {
wrapper = createComponent({
isSetupForCompany: false,
subscription: { planId: null, namespaceId: 483, quantity: 1 },
customer: { company: 'Organization name' },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
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({
isSetupForCompany: false, isSetupForCompany: false,
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 0 }, subscription: { namespaceId: 483, quantity: 0 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' }, customer: { company: 'Organization name' },
}); });
...@@ -357,7 +336,8 @@ describe('Subscription Details', () => { ...@@ -357,7 +336,8 @@ 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({
isSetupForCompany: false, isSetupForCompany: false,
subscription: { planId: 'firstPlanId', namespaceId: 483, quantity: 2 }, subscription: { namespaceId: 483, quantity: 2 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' }, customer: { company: 'Organization name' },
}); });
......
...@@ -47,7 +47,7 @@ const countries = [ ...@@ -47,7 +47,7 @@ const countries = [
['Uruguay', 'UY'], ['Uruguay', 'UY'],
]; ];
const states = [{ id: 1, name: 'state' }]; const states = { California: 'CA' };
describe('~/subscriptions/buy_minutes/graphql/resolvers', () => { describe('~/subscriptions/buy_minutes/graphql/resolvers', () => {
describe('Query', () => { describe('Query', () => {
...@@ -62,8 +62,8 @@ describe('~/subscriptions/buy_minutes/graphql/resolvers', () => { ...@@ -62,8 +62,8 @@ describe('~/subscriptions/buy_minutes/graphql/resolvers', () => {
expect(createFlash).not.toHaveBeenCalled(); expect(createFlash).not.toHaveBeenCalled();
expect(result).toStrictEqual([ expect(result).toStrictEqual([
{ name: 'United States of America', alpha2: 'US', __typename: 'Country' }, { name: 'United States of America', id: 'US', __typename: 'Country' },
{ name: 'Uruguay', alpha2: 'UY', __typename: 'Country' }, { name: 'Uruguay', id: 'UY', __typename: 'Country' },
]); ]);
}); });
}); });
...@@ -88,10 +88,10 @@ describe('~/subscriptions/buy_minutes/graphql/resolvers', () => { ...@@ -88,10 +88,10 @@ describe('~/subscriptions/buy_minutes/graphql/resolvers', () => {
}); });
it('returns an array of states with typename', async () => { it('returns an array of states with typename', async () => {
const result = await resolvers.Query.states(1); const result = await resolvers.Query.states(null, { countryId: 1 });
expect(createFlash).not.toHaveBeenCalled(); expect(createFlash).not.toHaveBeenCalled();
expect(result).toStrictEqual([{ id: 1, name: 'state', __typename: 'State' }]); expect(result).toStrictEqual([{ id: 'CA', name: 'California', __typename: 'State' }]);
}); });
}); });
...@@ -101,7 +101,7 @@ describe('~/subscriptions/buy_minutes/graphql/resolvers', () => { ...@@ -101,7 +101,7 @@ describe('~/subscriptions/buy_minutes/graphql/resolvers', () => {
}); });
it('shows a flash message', async () => { it('shows a flash message', async () => {
await resolvers.Query.states(); await resolvers.Query.states(null, { countryId: 1 });
expect(createFlash).toHaveBeenCalledWith({ message: ERROR_FETCHING_STATES }); expect(createFlash).toHaveBeenCalledWith({ message: ERROR_FETCHING_STATES });
}); });
......
...@@ -19,12 +19,12 @@ export const mockSetupForCompany = 'true'; ...@@ -19,12 +19,12 @@ export const mockSetupForCompany = 'true';
export const stateData = { export const stateData = {
namespaces: [], namespaces: [],
subscription: { subscription: {
planId: 'secondPlanId',
quantity: 1, quantity: 1,
namespaceId: null, namespaceId: null,
paymentMethodId: null, paymentMethodId: null,
__typename: 'Subscription', __typename: 'Subscription',
}, },
selectedPlanId: null,
customer: { customer: {
country: null, country: null,
address1: null, address1: null,
......
import { merge } from 'lodash';
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 resolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
export function createMockApolloProvider(stepList, initialStepIndex = 0) { export function createMockApolloProvider(stepList, initialStepIndex = 0, additionalResolvers = {}) {
const mockApollo = createMockApollo([], resolvers); const mockApollo = createMockApollo([], merge({}, resolvers, additionalResolvers));
mockApollo.clients.defaultClient.cache.writeQuery({ mockApollo.clients.defaultClient.cache.writeQuery({
query: stepListQuery, query: stepListQuery,
data: { stepList }, data: { stepList },
......
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