Commit 954431f3 authored by Alex Buijs's avatar Alex Buijs

Add billing address component for paid signup flow

This is part of the paid signup flow
parent ff77bdf6
......@@ -2,9 +2,10 @@
import { s__ } from '~/locale';
import ProgressBar from './checkout/progress_bar.vue';
import SubscriptionDetails from './checkout/subscription_details.vue';
import BillingAddress from './checkout/billing_address.vue';
export default {
components: { ProgressBar, SubscriptionDetails },
components: { ProgressBar, SubscriptionDetails, BillingAddress },
i18n: {
checkout: s__('Checkout|Checkout'),
},
......@@ -17,6 +18,7 @@ export default {
<div class="flash-container"></div>
<h2 class="mt-4 mb-3 mb-lg-5">{{ $options.i18n.checkout }}</h2>
<subscription-details />
<billing-address />
</div>
</div>
</template>
<script>
import _ from 'underscore';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { mapState, mapActions } from 'vuex';
import { GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { s__ } from '~/locale';
import Step from './components/step.vue';
export default {
components: {
Step,
GlFormGroup,
GlFormInput,
GlFormSelect,
},
directives: {
autofocusonshow,
},
computed: {
...mapState([
'country',
'streetAddressLine1',
'streetAddressLine2',
'city',
'countryState',
'zipCode',
'countryOptions',
'stateOptions',
]),
countryModel: {
get() {
return this.country;
},
set(country) {
this.updateCountry(country);
},
},
streetAddressLine1Model: {
get() {
return this.streetAddressLine1;
},
set(streetAddressLine1) {
this.updateStreetAddressLine1(streetAddressLine1);
},
},
streetAddressLine2Model: {
get() {
return this.streetAddressLine2;
},
set(streetAddressLine2) {
this.updateStreetAddressLine2(streetAddressLine2);
},
},
cityModel: {
get() {
return this.city;
},
set(city) {
this.updateCity(city);
},
},
countryStateModel: {
get() {
return this.countryState;
},
set(countryState) {
this.updateCountryState(countryState);
},
},
zipCodeModel: {
get() {
return this.zipCode;
},
set(zipCode) {
this.updateZipCode(zipCode);
},
},
isValid() {
return (
!_.isEmpty(this.country) &&
!_.isEmpty(this.streetAddressLine1) &&
!_.isEmpty(this.city) &&
!_.isEmpty(this.zipCode)
);
},
countryOptionsWithDefault() {
return [
{
text: this.$options.i18n.countrySelectPrompt,
value: null,
},
...this.countryOptions,
];
},
stateOptionsWithDefault() {
return [
{
text: this.$options.i18n.stateSelectPrompt,
value: null,
},
...this.stateOptions,
];
},
},
mounted() {
this.fetchCountries();
},
methods: {
...mapActions([
'fetchCountries',
'fetchStates',
'updateCountry',
'updateStreetAddressLine1',
'updateStreetAddressLine2',
'updateCity',
'updateCountryState',
'updateZipCode',
]),
},
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'),
},
};
</script>
<template>
<step
step="billingAddress"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
>
<template #body>
<gl-form-group :label="$options.i18n.countryLabel" label-size="sm" class="mb-3">
<gl-form-select
v-model="countryModel"
v-autofocusonshow
:options="countryOptionsWithDefault"
class="js-country"
@change="fetchStates"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.streetAddressLabel" label-size="sm" class="mb-3">
<gl-form-input v-model="streetAddressLine1Model" type="text" />
<gl-form-input v-model="streetAddressLine2Model" type="text" class="mt-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" />
</gl-form-group>
<div class="combined d-flex">
<gl-form-group :label="$options.i18n.stateLabel" label-size="sm" class="mr-3 w-50">
<gl-form-select v-model="countryStateModel" :options="stateOptionsWithDefault" />
</gl-form-group>
<gl-form-group :label="$options.i18n.zipCodeLabel" label-size="sm" class="w-50">
<gl-form-input v-model="zipCodeModel" type="text" />
</gl-form-group>
</div>
</template>
<template #summary>
<div class="js-summary-line-1">{{ streetAddressLine1 }}</div>
<div class="js-summary-line-2">{{ streetAddressLine2 }}</div>
<div class="js-summary-line-3">{{ city }}, {{ countryState }} {{ zipCode }}</div>
</template>
</step>
</template>
export const STEPS = ['subscriptionDetails'];
export const STEPS = ['subscriptionDetails', 'billingAddress'];
export const COUNTRIES_URL = '/-/countries';
export const STATES_URL = '/-/country_states';
export const TAX_RATE = 0;
import * as types from './mutation_types';
import { STEPS } from '../constants';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import createFlash from '~/flash';
import { STEPS, COUNTRIES_URL, STATES_URL } from '../constants';
export const activateStep = ({ commit }, currentStep) => {
if (STEPS.includes(currentStep)) {
......@@ -32,3 +35,72 @@ export const updateNumberOfUsers = ({ commit }, numberOfUsers) => {
export const updateOrganizationName = ({ commit }, organizationName) => {
commit(types.UPDATE_ORGANIZATION_NAME, organizationName);
};
export const fetchCountries = ({ dispatch }) => {
axios
.get(COUNTRIES_URL)
.then(({ data }) => dispatch('fetchCountriesSuccess', data))
.catch(() => dispatch('fetchCountriesError'));
};
export const fetchCountriesSuccess = ({ commit }, data = []) => {
const countries = data.map(country => ({ text: country[0], value: country[1] }));
commit(types.UPDATE_COUNTRY_OPTIONS, countries);
};
export const fetchCountriesError = () => {
createFlash(s__('Checkout|Failed to load countries. Please try again.'));
};
export const fetchStates = ({ state, dispatch }) => {
dispatch('resetStates');
if (!state.country) {
return;
}
axios
.get(STATES_URL, { params: { country: state.country } })
.then(({ data }) => dispatch('fetchStatesSuccess', data))
.catch(() => dispatch('fetchStatesError'));
};
export const fetchStatesSuccess = ({ commit }, data = {}) => {
const states = Object.keys(data).map(state => ({ text: state, value: data[state] }));
commit(types.UPDATE_STATE_OPTIONS, states);
};
export const fetchStatesError = () => {
createFlash(s__('Checkout|Failed to load states. Please try again.'));
};
export const resetStates = ({ commit }) => {
commit(types.UPDATE_COUNTRY_STATE, null);
commit(types.UPDATE_STATE_OPTIONS, []);
};
export const updateCountry = ({ commit }, country) => {
commit(types.UPDATE_COUNTRY, country);
};
export const updateStreetAddressLine1 = ({ commit }, streetAddressLine1) => {
commit(types.UPDATE_STREET_ADDRESS_LINE_ONE, streetAddressLine1);
};
export const updateStreetAddressLine2 = ({ commit }, streetAddressLine2) => {
commit(types.UPDATE_STREET_ADDRESS_LINE_TWO, streetAddressLine2);
};
export const updateCity = ({ commit }, city) => {
commit(types.UPDATE_CITY, city);
};
export const updateCountryState = ({ commit }, countryState) => {
commit(types.UPDATE_COUNTRY_STATE, countryState);
};
export const updateZipCode = ({ commit }, zipCode) => {
commit(types.UPDATE_ZIP_CODE, zipCode);
};
......@@ -7,3 +7,19 @@ export const UPDATE_IS_SETUP_FOR_COMPANY = 'UPDATE_IS_SETUP_FOR_COMPANY';
export const UPDATE_NUMBER_OF_USERS = 'UPDATE_NUMBER_OF_USERS';
export const UPDATE_ORGANIZATION_NAME = 'UPDATE_ORGANIZATION_NAME';
export const UPDATE_COUNTRY_OPTIONS = 'UPDATE_COUNTRY_OPTIONS';
export const UPDATE_STATE_OPTIONS = 'UPDATE_STATE_OPTIONS';
export const UPDATE_COUNTRY = 'UPDATE_COUNTRY';
export const UPDATE_STREET_ADDRESS_LINE_ONE = 'UPDATE_STREET_ADDRESS_LINE_ONE';
export const UPDATE_STREET_ADDRESS_LINE_TWO = 'UPDATE_STREET_ADDRESS_LINE_TWO';
export const UPDATE_CITY = 'UPDATE_CITY';
export const UPDATE_COUNTRY_STATE = 'UPDATE_COUNTRY_STATE';
export const UPDATE_ZIP_CODE = 'UPDATE_ZIP_CODE';
......@@ -20,4 +20,36 @@ export default {
[types.UPDATE_ORGANIZATION_NAME](state, organizationName) {
state.organizationName = organizationName;
},
[types.UPDATE_COUNTRY_OPTIONS](state, countryOptions) {
state.countryOptions = countryOptions;
},
[types.UPDATE_STATE_OPTIONS](state, stateOptions) {
state.stateOptions = stateOptions;
},
[types.UPDATE_COUNTRY](state, country) {
state.country = country;
},
[types.UPDATE_STREET_ADDRESS_LINE_ONE](state, streetAddressLine1) {
state.streetAddressLine1 = streetAddressLine1;
},
[types.UPDATE_STREET_ADDRESS_LINE_TWO](state, streetAddressLine2) {
state.streetAddressLine2 = streetAddressLine2;
},
[types.UPDATE_CITY](state, city) {
state.city = city;
},
[types.UPDATE_COUNTRY_STATE](state, countryState) {
state.countryState = countryState;
},
[types.UPDATE_ZIP_CODE](state, zipCode) {
state.zipCode = zipCode;
},
};
......@@ -27,6 +27,14 @@ export default ({ planData = '[]', planId, setupForCompany, fullName }) => {
fullName,
organizationName: null,
numberOfUsers: parseBoolean(setupForCompany) ? 0 : 1,
country: null,
streetAddressLine1: null,
streetAddressLine2: null,
city: null,
countryState: null,
zipCode: null,
countryOptions: [],
stateOptions: [],
taxRate: TAX_RATE,
startDate: new Date(Date.now()),
};
......
......@@ -83,15 +83,19 @@ $subscriptions-full-width-lg: 541px;
flex-grow: 1;
max-width: 420px;
legend {
border-bottom: 0;
font-weight: $gl-font-weight-bold;
}
.gl-form-input,
.gl-form-select {
height: 32px;
line-height: 1rem;
}
}
.number {
width: 50%;
@media(min-width: map-get($grid-breakpoints, lg)) {
max-width: 202px;
}
......@@ -99,7 +103,6 @@ $subscriptions-full-width-lg: 541px;
.label {
padding: 0;
width: 50%;
.gl-link {
font-size: inherit;
......
import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types';
import Step from 'ee/subscriptions/new/components/checkout/components/step.vue';
import Component from 'ee/subscriptions/new/components/checkout/billing_address.vue';
describe('Billing Address', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let store;
let wrapper;
const methodMocks = {
fetchCountries: jest.fn(),
fetchStates: jest.fn(),
};
const createComponent = () => {
wrapper = mount(Component, {
localVue,
store,
methods: methodMocks,
});
};
beforeEach(() => {
store = createStore();
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('mounted', () => {
it('should load the countries', () => {
expect(methodMocks.fetchCountries).toHaveBeenCalled();
});
});
describe('country options', () => {
const countrySelect = () => wrapper.find('.js-country');
beforeEach(() => {
store.commit(types.UPDATE_COUNTRY_OPTIONS, [{ text: 'Netherlands', value: 'NL' }]);
});
it('should display the select prompt', () => {
expect(countrySelect().html()).toContain('<option value="">Please select a country</option>');
});
it('should display the countries returned from the server', () => {
expect(countrySelect().html()).toContain('<option value="NL">Netherlands</option>');
});
it('should fetch states when selecting a country', () => {
countrySelect().trigger('change');
return localVue.nextTick().then(() => {
expect(methodMocks.fetchStates).toHaveBeenCalled();
});
});
});
describe('validations', () => {
const isStepValid = () => wrapper.find(Step).props('isValid');
beforeEach(() => {
store.commit(types.UPDATE_COUNTRY, 'country');
store.commit(types.UPDATE_STREET_ADDRESS_LINE_ONE, 'address line 1');
store.commit(types.UPDATE_CITY, 'city');
store.commit(types.UPDATE_ZIP_CODE, 'zip');
});
it('should be valid when country, streetAddressLine1, city and zipCode have been entered', () => {
expect(isStepValid()).toBe(true);
});
it('should be invalid when country is undefined', () => {
store.commit(types.UPDATE_COUNTRY, null);
return localVue.nextTick().then(() => {
expect(isStepValid()).toBe(false);
});
});
it('should be invalid when streetAddressLine1 is undefined', () => {
store.commit(types.UPDATE_STREET_ADDRESS_LINE_ONE, null);
return localVue.nextTick().then(() => {
expect(isStepValid()).toBe(false);
});
});
it('should be invalid when city is undefined', () => {
store.commit(types.UPDATE_CITY, null);
return localVue.nextTick().then(() => {
expect(isStepValid()).toBe(false);
});
});
it('should be invalid when zipCode is undefined', () => {
store.commit(types.UPDATE_ZIP_CODE, null);
return localVue.nextTick().then(() => {
expect(isStepValid()).toBe(false);
});
});
});
describe('showing the summary', () => {
beforeEach(() => {
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');
});
it('should show the entered address line 1', () => {
expect(wrapper.find('.js-summary-line-1').text()).toEqual('address line 1');
});
it('should show the entered address line 2', () => {
expect(wrapper.find('.js-summary-line-2').text()).toEqual('address line 2');
});
it('should show the entered address city, state and zip code', () => {
expect(wrapper.find('.js-summary-line-3').text()).toEqual('city, state zip');
});
});
});
......@@ -3,17 +3,16 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import createStore from 'ee/subscriptions/new/store';
import * as constants from 'ee/subscriptions/new/constants';
import component from 'ee/subscriptions/new/components/checkout/components/step.vue';
import Component from 'ee/subscriptions/new/components/checkout/components/step.vue';
import StepSummary from 'ee/subscriptions/new/components/checkout/components/step_summary.vue';
describe('Step', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let store;
let wrapper;
const store = createStore();
const initialProps = {
step: 'secondStep',
isValid: true,
......@@ -21,11 +20,11 @@ describe('Step', () => {
nextStepButtonText: 'next',
};
const factory = propsData => {
wrapper = shallowMount(component, {
store,
const createComponent = propsData => {
wrapper = shallowMount(Component, {
propsData: { ...initialProps, ...propsData },
localVue,
store,
});
};
......@@ -36,6 +35,7 @@ describe('Step', () => {
constants.STEPS = ['firstStep', 'secondStep'];
beforeEach(() => {
store = createStore();
store.dispatch('activateStep', 'secondStep');
});
......@@ -45,14 +45,14 @@ describe('Step', () => {
describe('Step Body', () => {
it('should display the step body when this step is the current step', () => {
factory();
createComponent();
expect(wrapper.find('.card > div').attributes('style')).toBeUndefined();
});
it('should not display the step body when this step is not the current step', () => {
activatePreviousStep();
factory();
createComponent();
expect(wrapper.find('.card > div').attributes('style')).toBe('display: none;');
});
......@@ -61,26 +61,26 @@ describe('Step', () => {
describe('Step Summary', () => {
it('should be shown when this step is valid and not active', () => {
activatePreviousStep();
factory();
createComponent();
expect(wrapper.find(StepSummary).exists()).toBe(true);
});
it('should not be shown when this step is not valid and not active', () => {
activatePreviousStep();
factory({ isValid: false });
createComponent({ isValid: false });
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
it('should not be shown when this step is valid and active', () => {
factory();
createComponent();
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
it('should not be shown when this step is not valid and active', () => {
factory({ isValid: false });
createComponent({ isValid: false });
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
......@@ -88,7 +88,7 @@ describe('Step', () => {
describe('isEditable', () => {
it('should set the isEditable property to true when this step is finished and comes before the current step', () => {
factory({ step: 'firstStep' });
createComponent({ step: 'firstStep' });
expect(wrapper.find(StepSummary).props('isEditable')).toBe(true);
});
......@@ -97,13 +97,13 @@ describe('Step', () => {
describe('Showing the summary', () => {
it('shows the summary when this step is finished', () => {
activatePreviousStep();
factory();
createComponent();
expect(wrapper.find(StepSummary).exists()).toBe(true);
});
it('does not show the summary when this step is not finished', () => {
factory();
createComponent();
expect(wrapper.find(StepSummary).exists()).toBe(false);
});
......@@ -111,25 +111,25 @@ describe('Step', () => {
describe('Next button', () => {
it('shows the next button when the text was passed', () => {
factory();
createComponent();
expect(wrapper.text()).toBe('next');
});
it('does not show the next button when no text was passed', () => {
factory({ nextStepButtonText: '' });
createComponent({ nextStepButtonText: '' });
expect(wrapper.text()).toBe('');
});
it('is disabled when this step is not valid', () => {
factory({ isValid: false });
createComponent({ isValid: false });
expect(wrapper.find(GlButton).attributes('disabled')).toBe('true');
});
it('is enabled when this step is valid', () => {
factory();
createComponent();
expect(wrapper.find(GlButton).attributes('disabled')).toBeUndefined();
});
......
import { shallowMount } from '@vue/test-utils';
import component from 'ee/subscriptions/new/components/checkout/progress_bar.vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Component from 'ee/subscriptions/new/components/checkout/progress_bar.vue';
describe('Progress Bar', () => {
const localVue = createLocalVue();
let wrapper;
const factory = propsData => {
wrapper = shallowMount(component, {
const createComponent = propsData => {
wrapper = shallowMount(Component, {
propsData,
localVue,
});
};
......@@ -14,7 +17,7 @@ describe('Progress Bar', () => {
const secondStep = () => wrapper.find('.bar div:nth-child(2)');
beforeEach(() => {
factory({ step: 2 });
createComponent({ step: 2 });
});
afterEach(() => {
......
......@@ -9,6 +9,7 @@ describe('Subscription Details', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let store;
let wrapper;
const planData = [
......@@ -23,17 +24,17 @@ describe('Subscription Details', () => {
fullName: 'Full Name',
};
const store = createStore(initialData);
const isStepValid = () => wrapper.find(Step).props('isValid');
const createComponent = (opts = {}) => {
const createComponent = () => {
wrapper = mount(Component, {
localVue,
store,
...opts,
});
};
beforeEach(() => {
store = createStore(initialData);
createComponent();
});
......@@ -41,8 +42,6 @@ describe('Subscription Details', () => {
wrapper.destroy();
});
const isStepValid = () => wrapper.find(Step).props('isValid');
describe('Setting up for personal use', () => {
beforeEach(() => {
store.commit(types.UPDATE_IS_SETUP_FOR_COMPANY, false);
......
import testAction from 'helpers/vuex_action_helper';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import * as actions from 'ee/subscriptions/new/store/actions';
import * as constants from 'ee/subscriptions/new/constants';
jest.mock('~/flash');
constants.STEPS = ['firstStep', 'secondStep'];
let mock;
describe('Subscriptions Actions', () => {
describe('activateStep', () => {
it('set the currentStep to the provided value', done => {
......@@ -101,4 +108,237 @@ describe('Subscriptions Actions', () => {
);
});
});
describe('fetchCountries', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('calls fetchCountriesSuccess with the returned data on success', done => {
mock.onGet(constants.COUNTRIES_URL).replyOnce(200, ['Netherlands', 'NL']);
testAction(
actions.fetchCountries,
null,
{},
[],
[{ type: 'fetchCountriesSuccess', payload: ['Netherlands', 'NL'] }],
done,
);
});
it('calls fetchCountriesError on error', done => {
mock.onGet(constants.COUNTRIES_URL).replyOnce(500);
testAction(actions.fetchCountries, null, {}, [], [{ type: 'fetchCountriesError' }], done);
});
});
describe('fetchCountriesSuccess', () => {
it('transforms and adds fetched countryOptions', done => {
testAction(
actions.fetchCountriesSuccess,
[['Netherlands', 'NL']],
{},
[{ type: 'UPDATE_COUNTRY_OPTIONS', payload: [{ text: 'Netherlands', value: 'NL' }] }],
[],
done,
);
});
it('adds an empty array when no data provided', done => {
testAction(
actions.fetchCountriesSuccess,
undefined,
{},
[{ type: 'UPDATE_COUNTRY_OPTIONS', payload: [] }],
[],
done,
);
});
});
describe('fetchCountriesError', () => {
it('creates a flash', done => {
testAction(actions.fetchCountriesError, null, {}, [], [], () => {
expect(createFlash).toHaveBeenCalledWith('Failed to load countries. Please try again.');
done();
});
});
});
describe('fetchStates', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('calls resetStates and fetchStatesSuccess with the returned data on success', done => {
mock
.onGet(constants.STATES_URL, { params: { country: 'NL' } })
.replyOnce(200, { utrecht: 'UT' });
testAction(
actions.fetchStates,
null,
{ country: 'NL' },
[],
[{ type: 'resetStates' }, { type: 'fetchStatesSuccess', payload: { utrecht: 'UT' } }],
done,
);
});
it('only calls resetStates when no country selected', done => {
mock.onGet(constants.STATES_URL).replyOnce(500);
testAction(actions.fetchStates, null, { country: null }, [], [{ type: 'resetStates' }], done);
});
it('calls resetStates and fetchStatesError on error', done => {
mock.onGet(constants.STATES_URL).replyOnce(500);
testAction(
actions.fetchStates,
null,
{ country: 'NL' },
[],
[{ type: 'resetStates' }, { type: 'fetchStatesError' }],
done,
);
});
});
describe('fetchStatesSuccess', () => {
it('transforms and adds received stateOptions', done => {
testAction(
actions.fetchStatesSuccess,
{ Utrecht: 'UT' },
{},
[{ type: 'UPDATE_STATE_OPTIONS', payload: [{ text: 'Utrecht', value: 'UT' }] }],
[],
done,
);
});
it('adds an empty array when no data provided', done => {
testAction(
actions.fetchStatesSuccess,
undefined,
{},
[{ type: 'UPDATE_STATE_OPTIONS', payload: [] }],
[],
done,
);
});
});
describe('fetchStatesError', () => {
it('creates a flash', done => {
testAction(actions.fetchStatesError, null, {}, [], [], () => {
expect(createFlash).toHaveBeenCalledWith('Failed to load states. Please try again.');
done();
});
});
});
describe('resetStates', () => {
it('resets the selected state and sets the stateOptions to the initial value', done => {
testAction(
actions.resetStates,
null,
{},
[
{ type: 'UPDATE_COUNTRY_STATE', payload: null },
{ type: 'UPDATE_STATE_OPTIONS', payload: [] },
],
[],
done,
);
});
});
describe('updateCountry', () => {
it('updates country to the provided value', done => {
testAction(
actions.updateCountry,
'country',
{},
[{ type: 'UPDATE_COUNTRY', payload: 'country' }],
[],
done,
);
});
});
describe('updateStreetAddressLine1', () => {
it('updates streetAddressLine1 to the provided value', done => {
testAction(
actions.updateStreetAddressLine1,
'streetAddressLine1',
{},
[{ type: 'UPDATE_STREET_ADDRESS_LINE_ONE', payload: 'streetAddressLine1' }],
[],
done,
);
});
});
describe('updateStreetAddressLine2', () => {
it('updates streetAddressLine2 to the provided value', done => {
testAction(
actions.updateStreetAddressLine2,
'streetAddressLine2',
{},
[{ type: 'UPDATE_STREET_ADDRESS_LINE_TWO', payload: 'streetAddressLine2' }],
[],
done,
);
});
});
describe('updateCity', () => {
it('updates city to the provided value', done => {
testAction(
actions.updateCity,
'city',
{},
[{ type: 'UPDATE_CITY', payload: 'city' }],
[],
done,
);
});
});
describe('updateCountryState', () => {
it('updates countryState to the provided value', done => {
testAction(
actions.updateCountryState,
'countryState',
{},
[{ type: 'UPDATE_COUNTRY_STATE', payload: 'countryState' }],
[],
done,
);
});
});
describe('updateZipCode', () => {
it('updates zipCode to the provided value', done => {
testAction(
actions.updateZipCode,
'zipCode',
{},
[{ type: 'UPDATE_ZIP_CODE', payload: 'zipCode' }],
[],
done,
);
});
});
});
......@@ -7,6 +7,8 @@ const state = () => ({
isSetupForCompany: true,
numberOfUsers: 1,
organizationName: 'name',
countryOptions: [],
stateOptions: [],
});
let stateCopy;
......@@ -15,42 +17,29 @@ beforeEach(() => {
stateCopy = state();
});
describe('UPDATE_CURRENT_STEP', () => {
it('should set the currentStep to the given step', () => {
mutations[types.UPDATE_CURRENT_STEP](stateCopy, 'secondStep');
expect(stateCopy.currentStep).toEqual('secondStep');
});
});
describe('UPDATE_SELECTED_PLAN', () => {
it('should set the selectedPlan to the given plan', () => {
mutations[types.UPDATE_SELECTED_PLAN](stateCopy, 'secondPlan');
expect(stateCopy.selectedPlan).toEqual('secondPlan');
});
});
describe('UPDATE_IS_SETUP_FOR_COMPANY', () => {
it('should set the isSetupForCompany to the given boolean', () => {
mutations[types.UPDATE_IS_SETUP_FOR_COMPANY](stateCopy, false);
expect(stateCopy.isSetupForCompany).toEqual(false);
});
});
describe('UPDATE_NUMBER_OF_USERS', () => {
it('should set the numberOfUsers to the given number', () => {
mutations[types.UPDATE_NUMBER_OF_USERS](stateCopy, 2);
expect(stateCopy.numberOfUsers).toEqual(2);
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_IS_SETUP_FOR_COMPANY} | ${false} | ${'isSetupForCompany'}
${types.UPDATE_NUMBER_OF_USERS} | ${2} | ${'numberOfUsers'}
${types.UPDATE_ORGANIZATION_NAME} | ${'new name'} | ${'organizationName'}
${types.UPDATE_COUNTRY_OPTIONS} | ${[{ text: 'country', value: 'id' }]} | ${'countryOptions'}
${types.UPDATE_STATE_OPTIONS} | ${[{ text: 'state', value: 'id' }]} | ${'stateOptions'}
${types.UPDATE_COUNTRY} | ${'NL'} | ${'country'}
${types.UPDATE_STREET_ADDRESS_LINE_ONE} | ${'streetAddressLine1'} | ${'streetAddressLine1'}
${types.UPDATE_STREET_ADDRESS_LINE_TWO} | ${'streetAddressLine2'} | ${'streetAddressLine2'}
${types.UPDATE_CITY} | ${'city'} | ${'city'}
${types.UPDATE_COUNTRY_STATE} | ${'countryState'} | ${'countryState'}
${types.UPDATE_ZIP_CODE} | ${'zipCode'} | ${'zipCode'}
`('$mutation', ({ mutation, value, stateProp }) => {
it(`should set the ${stateProp} to the given value`, () => {
expect(stateCopy[stateProp]).not.toEqual(value);
mutations[mutation](stateCopy, value);
expect(stateCopy[stateProp]).toEqual(value);
});
});
describe('UPDATE_ORGANIZATION_NAME', () => {
it('should set the organizationName to the given name', () => {
mutations[types.UPDATE_ORGANIZATION_NAME](stateCopy, 'new name');
expect(stateCopy.organizationName).toEqual('new name');
});
});
......@@ -98,15 +98,43 @@ describe('projectsSelector default state', () => {
});
});
describe('taxRate', () => {
it('sets the country to null', () => {
expect(state.country).toBeNull();
});
it('sets the streetAddressLine1 to null', () => {
expect(state.streetAddressLine1).toBeNull();
});
it('sets the streetAddressLine2 to null', () => {
expect(state.streetAddressLine2).toBeNull();
});
it('sets the city to null', () => {
expect(state.city).toBeNull();
});
it('sets the countryState to null', () => {
expect(state.countryState).toBeNull();
});
it('sets the zipCode to null', () => {
expect(state.zipCode).toBeNull();
});
it('sets the countryOptions to an empty array', () => {
expect(state.countryOptions).toEqual([]);
});
it('sets the stateOptions to an empty array', () => {
expect(state.stateOptions).toEqual([]);
});
it('sets the taxRate to the TAX_RATE constant', () => {
expect(state.taxRate).toEqual(0);
});
});
describe('startDate', () => {
it('sets the startDate to the current date', () => {
expect(state.startDate).toEqual(currentDate);
});
});
});
......@@ -3359,15 +3359,33 @@ msgstr ""
msgid "Checkout|3. Your GitLab group"
msgstr ""
msgid "Checkout|Billing address"
msgstr ""
msgid "Checkout|Checkout"
msgstr ""
msgid "Checkout|City"
msgstr ""
msgid "Checkout|Continue to billing"
msgstr ""
msgid "Checkout|Continue to payment"
msgstr ""
msgid "Checkout|Country"
msgstr ""
msgid "Checkout|Edit"
msgstr ""
msgid "Checkout|Failed to load countries. Please try again."
msgstr ""
msgid "Checkout|Failed to load states. Please try again."
msgstr ""
msgid "Checkout|GitLab plan"
msgstr ""
......@@ -3383,6 +3401,18 @@ msgstr ""
msgid "Checkout|Number of users"
msgstr ""
msgid "Checkout|Please select a country"
msgstr ""
msgid "Checkout|Please select a state"
msgstr ""
msgid "Checkout|State"
msgstr ""
msgid "Checkout|Street address"
msgstr ""
msgid "Checkout|Subscription details"
msgstr ""
......@@ -3401,6 +3431,9 @@ msgstr ""
msgid "Checkout|Your organization"
msgstr ""
msgid "Checkout|Zip code"
msgstr ""
msgid "Checkout|company or team"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment