Commit 4ae97c82 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'growth-87-subscription-details-component' into 'master'

Subscription details component for paid signup flow

See merge request gitlab-org/gitlab!21563
parents ef35ea61 1d1836af
<script> <script>
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import ProgressBar from './checkout/progress_bar.vue'; import ProgressBar from './checkout/progress_bar.vue';
import SubscriptionDetails from './checkout/subscription_details.vue';
export default { export default {
components: { ProgressBar }, components: { ProgressBar, SubscriptionDetails },
i18n: { i18n: {
checkout: s__('Checkout|Checkout'), checkout: s__('Checkout|Checkout'),
}, },
...@@ -15,6 +16,7 @@ export default { ...@@ -15,6 +16,7 @@ export default {
<progress-bar :step="2" /> <progress-bar :step="2" />
<div class="flash-container"></div> <div class="flash-container"></div>
<h2 class="mt-4 mb-3 mb-lg-5">{{ $options.i18n.checkout }}</h2> <h2 class="mt-4 mb-3 mb-lg-5">{{ $options.i18n.checkout }}</h2>
<subscription-details />
</div> </div>
</div> </div>
</template> </template>
...@@ -37,10 +37,10 @@ export default { ...@@ -37,10 +37,10 @@ export default {
isFinished() { isFinished() {
return this.isValid && !this.isActive; return this.isValid && !this.isActive;
}, },
editable() { isEditable() {
return this.isFinished && this.stepIndex(this.step) < this.activeStepIndex; return this.isFinished && this.stepIndex(this.step) < this.currentStepIndex;
}, },
...mapGetters(['currentStep', 'stepIndex', 'activeStepIndex']), ...mapGetters(['currentStep', 'stepIndex', 'currentStepIndex']),
}, },
methods: { methods: {
...mapActions(['activateStep', 'activateNextStep']), ...mapActions(['activateStep', 'activateNextStep']),
...@@ -67,7 +67,7 @@ export default { ...@@ -67,7 +67,7 @@ export default {
</gl-button> </gl-button>
</gl-form-group> </gl-form-group>
</div> </div>
<step-summary v-if="isFinished" :editable="editable" :edit="edit"> <step-summary v-if="isFinished" :is-editable="isEditable" :edit="edit">
<slot name="summary"></slot> <slot name="summary"></slot>
</step-summary> </step-summary>
</div> </div>
......
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
GlButton, GlButton,
}, },
props: { props: {
editable: { isEditable: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
<div> <div>
<slot></slot> <slot></slot>
</div> </div>
<div v-if="editable" class="d-flex flex-column justify-content-center"> <div v-if="isEditable" class="d-flex flex-column justify-content-center">
<gl-button @click="edit">{{ $options.i18n.edit }}</gl-button> <gl-button @click="edit">{{ $options.i18n.edit }}</gl-button>
</div> </div>
</div> </div>
......
<script>
import _ from 'underscore';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import Step from './components/step.vue';
export default {
components: {
GlFormGroup,
GlFormSelect,
GlFormInput,
GlSprintf,
GlLink,
Step,
},
directives: {
autofocusonshow,
},
computed: {
...mapState([
'availablePlans',
'selectedPlan',
'isSetupForCompany',
'organizationName',
'numberOfUsers',
]),
...mapGetters(['selectedPlanText']),
selectedPlanModel: {
get() {
return this.selectedPlan;
},
set(selectedPlan) {
this.updateSelectedPlan(selectedPlan);
},
},
numberOfUsersModel: {
get() {
return this.numberOfUsers;
},
set(number) {
this.updateNumberOfUsers(number);
},
},
organizationNameModel: {
get() {
return this.organizationName;
},
set(organizationName) {
this.updateOrganizationName(organizationName);
},
},
selectedPlanTextLine() {
return sprintf(this.$options.i18n.selectedPlan, { selectedPlanText: this.selectedPlanText });
},
isValid() {
if (this.isSetupForCompany) {
return (
!_.isEmpty(this.selectedPlan) &&
!_.isEmpty(this.organizationName) &&
this.numberOfUsers > 0
);
}
return !_.isEmpty(this.selectedPlan) && this.numberOfUsers === 1;
},
},
methods: {
...mapActions([
'updateSelectedPlan',
'toggleIsSetupForCompany',
'updateNumberOfUsers',
'updateOrganizationName',
]),
},
i18n: {
stepTitle: s__('Checkout|Subscription details'),
nextStepButtonText: s__('Checkout|Continue to billing'),
selectedPlanLabel: s__('Checkout|GitLab plan'),
organizationNameLabel: s__('Checkout|Name of company or organization using GitLab'),
numberOfUsersLabel: s__('Checkout|Number of users'),
needMoreUsersLink: s__('Checkout|Need more users? Purchase GitLab for your %{company}.'),
companyOrTeam: s__('Checkout|company or team'),
selectedPlan: s__('Checkout|%{selectedPlanText} plan'),
group: s__('Checkout|Group'),
users: s__('Checkout|Users'),
},
};
</script>
<template>
<step
step="subscriptionDetails"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
>
<template #body>
<gl-form-group
:label="$options.i18n.selectedPlanLabel"
label-size="sm"
label-for="selectedPlan"
class="append-bottom-default"
>
<gl-form-select
id="selectedPlan"
v-model="selectedPlanModel"
v-autofocusonshow
:options="availablePlans"
/>
</gl-form-group>
<gl-form-group
v-if="isSetupForCompany"
:label="$options.i18n.organizationNameLabel"
label-size="sm"
label-for="organizationName"
class="append-bottom-default"
>
<gl-form-input id="organizationName" v-model="organizationNameModel" type="text" />
</gl-form-group>
<div class="combined d-flex">
<gl-form-group
:label="$options.i18n.numberOfUsersLabel"
label-size="sm"
label-for="numberOfUsers"
class="number"
>
<gl-form-input
id="numberOfUsers"
v-model.number="numberOfUsersModel"
type="number"
min="0"
:disabled="!isSetupForCompany"
/>
</gl-form-group>
<gl-form-group
v-if="!isSetupForCompany"
class="label prepend-left-default align-self-end company-link"
>
<gl-sprintf :message="$options.i18n.needMoreUsersLink">
<template #company>
<gl-link @click="toggleIsSetupForCompany">{{ $options.i18n.companyOrTeam }}</gl-link>
</template>
</gl-sprintf>
</gl-form-group>
</div>
</template>
<template #summary>
<strong class="js-summary-line-1">
{{ selectedPlanTextLine }}
</strong>
<div v-if="isSetupForCompany" class="js-summary-line-2">
{{ $options.i18n.group }}: {{ organizationName }}
</div>
<div class="js-summary-line-3">{{ $options.i18n.users }}: {{ numberOfUsers }}</div>
</template>
</step>
</template>
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
export const STEPS = ['subscriptionDetails'];
export const STEPS = [];
import Vue from 'vue'; import Vue from 'vue';
import store from './store'; import createStore from './store';
import Checkout from './components/checkout.vue'; import Checkout from './components/checkout.vue';
export default () => { export default () => {
...@@ -7,12 +7,9 @@ export default () => { ...@@ -7,12 +7,9 @@ export default () => {
return new Vue({ return new Vue({
el: checkoutEl, el: checkoutEl,
store, store: createStore(checkoutEl.dataset),
components: {
Checkout,
},
render(createElement) { render(createElement) {
return createElement('checkout', {}); return createElement(Checkout);
}, },
}); });
}; };
import * as types from './mutation_types'; import * as types from './mutation_types';
import { STEPS } from '../constants'; import { STEPS } from '../constants';
export const activateStep = ({ commit }, step) => { export const activateStep = ({ commit }, currentStep) => {
if (STEPS.includes(step)) { if (STEPS.includes(currentStep)) {
commit(types.ACTIVATE_STEP, step); commit(types.UPDATE_CURRENT_STEP, currentStep);
} }
}; };
export const activateNextStep = ({ commit, getters }) => { export const activateNextStep = ({ commit, getters }) => {
const { activeStepIndex } = getters; const { currentStepIndex } = getters;
if (activeStepIndex < STEPS.length - 1) { if (currentStepIndex < STEPS.length - 1) {
const nextStep = STEPS[activeStepIndex + 1]; const nextStep = STEPS[currentStepIndex + 1];
commit(types.ACTIVATE_STEP, nextStep); commit(types.UPDATE_CURRENT_STEP, nextStep);
} }
}; };
export const updateSelectedPlan = ({ commit }, selectedPlan) => {
commit(types.UPDATE_SELECTED_PLAN, selectedPlan);
};
export const toggleIsSetupForCompany = ({ state, commit }) => {
commit(types.UPDATE_IS_SETUP_FOR_COMPANY, !state.isSetupForCompany);
};
export const updateNumberOfUsers = ({ commit }, numberOfUsers) => {
commit(types.UPDATE_NUMBER_OF_USERS, numberOfUsers || 0);
};
export const updateOrganizationName = ({ commit }, organizationName) => {
commit(types.UPDATE_ORGANIZATION_NAME, organizationName);
};
...@@ -4,4 +4,9 @@ export const currentStep = state => state.currentStep; ...@@ -4,4 +4,9 @@ export const currentStep = state => state.currentStep;
export const stepIndex = () => step => STEPS.findIndex(el => el === step); export const stepIndex = () => step => STEPS.findIndex(el => el === step);
export const activeStepIndex = (state, getters) => getters.stepIndex(state.currentStep); export const currentStepIndex = (state, getters) => getters.stepIndex(state.currentStep);
export const selectedPlanText = (state, getters) => getters.selectedPlanDetails.text;
export const selectedPlanDetails = state =>
state.availablePlans.find(plan => plan.value === state.selectedPlan);
...@@ -4,16 +4,14 @@ import Vuex from 'vuex'; ...@@ -4,16 +4,14 @@ import Vuex from 'vuex';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters'; import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import state from './state'; import createState from './state';
Vue.use(Vuex); Vue.use(Vuex);
const createStore = () => export default (initialState = {}) =>
new Vuex.Store({ new Vuex.Store({
state: createState(initialState),
actions, actions,
getters, getters,
mutations, mutations,
state,
}); });
export default createStore();
/* eslint-disable import/prefer-default-export */ export const UPDATE_CURRENT_STEP = 'UPDATE_CURRENT_STEP';
export const ACTIVATE_STEP = 'ACTIVATE_STEP'; export const UPDATE_SELECTED_PLAN = 'UPDATE_SELECTED_PLAN';
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';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.ACTIVATE_STEP](state, step) { [types.UPDATE_CURRENT_STEP](state, currentStep) {
state.currentStep = step; state.currentStep = currentStep;
},
[types.UPDATE_SELECTED_PLAN](state, selectedPlan) {
state.selectedPlan = selectedPlan;
},
[types.UPDATE_IS_SETUP_FOR_COMPANY](state, isSetupForCompany) {
state.isSetupForCompany = isSetupForCompany;
},
[types.UPDATE_NUMBER_OF_USERS](state, numberOfUsers) {
state.numberOfUsers = numberOfUsers;
},
[types.UPDATE_ORGANIZATION_NAME](state, organizationName) {
state.organizationName = organizationName;
}, },
}; };
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { parseBoolean } from '~/lib/utils/common_utils';
import { STEPS } from '../constants'; import { STEPS } from '../constants';
export default () => ({ const parsePlanData = planData =>
JSON.parse(planData).map(plan => ({
value: plan.id,
text: capitalizeFirstCharacter(plan.code),
pricePerUserPerYear: plan.price_per_year,
}));
const determineSelectedPlan = (planId, plans) => {
if (planId && plans.find(plan => plan.value === planId)) {
return planId;
}
return plans[0] && plans[0].value;
};
export default ({ planData = '[]', planId, setupForCompany, fullName }) => {
const plans = parsePlanData(planData);
return {
currentStep: STEPS[0], currentStep: STEPS[0],
}); availablePlans: plans,
selectedPlan: determineSelectedPlan(planId, plans),
isSetupForCompany: parseBoolean(setupForCompany),
fullName,
organizationName: null,
numberOfUsers: parseBoolean(setupForCompany) ? 0 : 1,
};
};
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
@media(min-width: map-get($grid-breakpoints, lg)) { @media(min-width: map-get($grid-breakpoints, lg)) {
justify-content: inherit !important; justify-content: inherit !important;
margin-top: 36px; margin-top: $gl-padding-32;
max-width: none; max-width: none;
} }
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import store from 'ee/subscriptions/new/store'; import createStore from 'ee/subscriptions/new/store';
import * as constants from 'ee/subscriptions/new/constants'; 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'; import StepSummary from 'ee/subscriptions/new/components/checkout/components/step_summary.vue';
...@@ -12,7 +12,9 @@ describe('Step', () => { ...@@ -12,7 +12,9 @@ describe('Step', () => {
let wrapper; let wrapper;
const initialData = { const store = createStore();
const initialProps = {
step: 'secondStep', step: 'secondStep',
isValid: true, isValid: true,
title: 'title', title: 'title',
...@@ -22,7 +24,7 @@ describe('Step', () => { ...@@ -22,7 +24,7 @@ describe('Step', () => {
const factory = propsData => { const factory = propsData => {
wrapper = shallowMount(localVue.extend(component), { wrapper = shallowMount(localVue.extend(component), {
store, store,
propsData: { ...initialData, ...propsData }, propsData: { ...initialProps, ...propsData },
localVue, localVue,
sync: false, sync: false,
}); });
...@@ -42,67 +44,54 @@ describe('Step', () => { ...@@ -42,67 +44,54 @@ describe('Step', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('isActive', () => { describe('Step Body', () => {
it('should return true when this step is the current step', () => { it('should display the step body when this step is the current step', () => {
factory(); factory();
expect(wrapper.vm.isActive).toEqual(true); expect(wrapper.find('.card > div').attributes('style')).toBeUndefined();
}); });
it('should return false when this step is not the current step', () => { it('should not display the step body when this step is not the current step', () => {
activatePreviousStep(); activatePreviousStep();
factory(); factory();
expect(wrapper.vm.isActive).toEqual(false); expect(wrapper.find('.card > div').attributes('style')).toBe('display: none;');
}); });
}); });
describe('isFinished', () => { describe('Step Summary', () => {
it('should return true when this step is valid and not active', () => { it('should be shown when this step is valid and not active', () => {
activatePreviousStep(); activatePreviousStep();
factory(); factory();
expect(wrapper.vm.isFinished).toEqual(true); expect(wrapper.find(StepSummary).exists()).toBe(true);
}); });
it('should return false when this step is not valid and not active', () => { it('should not be shown when this step is not valid and not active', () => {
activatePreviousStep(); activatePreviousStep();
factory({ isValid: false }); factory({ isValid: false });
expect(wrapper.vm.isFinished).toEqual(false); expect(wrapper.find(StepSummary).exists()).toBe(false);
}); });
it('should return false when this step is valid and active', () => { it('should not be shown when this step is valid and active', () => {
factory(); factory();
expect(wrapper.vm.isFinished).toEqual(false); expect(wrapper.find(StepSummary).exists()).toBe(false);
}); });
it('should return false when this step is not valid and active', () => { it('should not be shown when this step is not valid and active', () => {
factory({ isValid: false }); factory({ isValid: false });
expect(wrapper.vm.isFinished).toEqual(false); expect(wrapper.find(StepSummary).exists()).toBe(false);
});
});
describe('editable', () => {
it('should return true when this step is finished and comes before the current step', () => {
factory({ step: 'firstStep' });
expect(wrapper.vm.editable).toEqual(true);
}); });
it('should return false when this step is not finished and comes before the current step', () => {
factory({ step: 'firstStep', isValid: false });
expect(wrapper.vm.editable).toEqual(false);
}); });
it('should return false when this step is finished and does not come before the current step', () => { describe('isEditable', () => {
activatePreviousStep(); it('should set the isEditable property to true when this step is finished and comes before the current step', () => {
factory({ step: 'firstStep' }); factory({ step: 'firstStep' });
expect(wrapper.vm.editable).toEqual(false); expect(wrapper.find(StepSummary).props('isEditable')).toBe(true);
}); });
}); });
...@@ -125,13 +114,13 @@ describe('Step', () => { ...@@ -125,13 +114,13 @@ describe('Step', () => {
it('shows the next button when the text was passed', () => { it('shows the next button when the text was passed', () => {
factory(); factory();
expect(wrapper.text()).toEqual('next'); expect(wrapper.text()).toBe('next');
}); });
it('does not show the next button when no text was passed', () => { it('does not show the next button when no text was passed', () => {
factory({ nextStepButtonText: '' }); factory({ nextStepButtonText: '' });
expect(wrapper.text()).toEqual(''); expect(wrapper.text()).toBe('');
}); });
it('is disabled when this step is not valid', () => { it('is disabled when this step is not valid', () => {
......
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/subscription_details.vue';
describe('Subscription Details', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let wrapper;
const planData = [
{ id: 'firstPlanId', code: 'bronze', price_per_year: 48 },
{ id: 'secondPlanId', code: 'silver', price_per_year: 228 },
];
const initialData = {
planData: JSON.stringify(planData),
planId: 'secondPlanId',
setupForCompany: 'true',
fullName: 'Full Name',
};
const store = createStore(initialData);
const createComponent = (opts = {}) => {
wrapper = mount(Component, {
localVue,
sync: false,
store,
...opts,
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
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);
store.commit(types.UPDATE_NUMBER_OF_USERS, 1);
});
it('should be valid', () => {
expect(isStepValid()).toBe(true);
});
it('should not display an input field for the company or group name', () => {
expect(wrapper.find('#organizationName').exists()).toBe(false);
});
it('should disable the number of users input field', () => {
expect(wrapper.find('#numberOfUsers').attributes('disabled')).toBeDefined();
});
it('should show a link to change to setting up for a company', () => {
expect(wrapper.find('.company-link').exists()).toBe(true);
});
});
describe('Setting up for a company or group', () => {
beforeEach(() => {
store.commit(types.UPDATE_IS_SETUP_FOR_COMPANY, true);
store.commit(types.UPDATE_NUMBER_OF_USERS, 0);
});
it('should be invalid', () => {
expect(isStepValid()).toBe(false);
});
it('should display an input field for the company or group name', () => {
expect(wrapper.find('#organizationName').exists()).toBe(true);
});
it('should enable the number of users input field', () => {
expect(wrapper.find('#numberOfUsers').attributes('disabled')).toBeUndefined();
});
it('should not show the link to change to setting up for a company', () => {
expect(wrapper.find('.company-link').exists()).toBe(false);
});
describe('filling in the company name and the number of users', () => {
it('should make the component valid', () => {
store.commit(types.UPDATE_ORGANIZATION_NAME, 'My Organization');
store.commit(types.UPDATE_NUMBER_OF_USERS, 2);
return localVue.nextTick().then(() => {
expect(isStepValid()).toBe(true);
});
});
});
});
describe('Showing summary', () => {
beforeEach(() => {
store.commit(types.UPDATE_IS_SETUP_FOR_COMPANY, true);
store.commit(types.UPDATE_SELECTED_PLAN, 'firstPlanId');
store.commit(types.UPDATE_ORGANIZATION_NAME, 'My Organization');
store.commit(types.UPDATE_NUMBER_OF_USERS, 25);
store.commit(types.UPDATE_CURRENT_STEP, 'nextStep');
});
it('should show the selected plan', () => {
expect(wrapper.find('.js-summary-line-1').text()).toEqual('Bronze plan');
});
it('should show the entered group name', () => {
expect(wrapper.find('.js-summary-line-2').text()).toEqual('Group: My Organization');
});
it('should show the entered number of users', () => {
expect(wrapper.find('.js-summary-line-3').text()).toEqual('Users: 25');
});
});
});
...@@ -11,7 +11,7 @@ describe('Subscriptions Actions', () => { ...@@ -11,7 +11,7 @@ describe('Subscriptions Actions', () => {
actions.activateStep, actions.activateStep,
'secondStep', 'secondStep',
{}, {},
[{ type: 'ACTIVATE_STEP', payload: 'secondStep' }], [{ type: 'UPDATE_CURRENT_STEP', payload: 'secondStep' }],
[], [],
done, done,
); );
...@@ -27,15 +27,78 @@ describe('Subscriptions Actions', () => { ...@@ -27,15 +27,78 @@ describe('Subscriptions Actions', () => {
testAction( testAction(
actions.activateNextStep, actions.activateNextStep,
{}, {},
{ activeStepIndex: 0 }, { currentStepIndex: 0 },
[{ type: 'ACTIVATE_STEP', payload: 'secondStep' }], [{ type: 'UPDATE_CURRENT_STEP', payload: 'secondStep' }],
[], [],
done, done,
); );
}); });
it('does not change the currentStep if the current step is the last step', done => { it('does not change the currentStep if the current step is the last step', done => {
testAction(actions.activateNextStep, {}, { activeStepIndex: 1 }, [], [], done); testAction(actions.activateNextStep, {}, { currentStepIndex: 1 }, [], [], done);
});
});
describe('updateSelectedPlan', () => {
it('updates the selected plan', done => {
testAction(
actions.updateSelectedPlan,
'planId',
{},
[{ type: 'UPDATE_SELECTED_PLAN', payload: 'planId' }],
[],
done,
);
});
});
describe('toggleIsSetupForCompany', () => {
it('toggles the isSetupForCompany value', done => {
testAction(
actions.toggleIsSetupForCompany,
{},
{ isSetupForCompany: true },
[{ type: 'UPDATE_IS_SETUP_FOR_COMPANY', payload: false }],
[],
done,
);
});
});
describe('updateNumberOfUsers', () => {
it('updates numberOfUsers to 0 when no value is provided', done => {
testAction(
actions.updateNumberOfUsers,
null,
{},
[{ type: 'UPDATE_NUMBER_OF_USERS', payload: 0 }],
[],
done,
);
});
it('updates numberOfUsers when a value is provided', done => {
testAction(
actions.updateNumberOfUsers,
2,
{},
[{ type: 'UPDATE_NUMBER_OF_USERS', payload: 2 }],
[],
done,
);
});
});
describe('updateOrganizationName', () => {
it('updates organizationName to the provided value', done => {
testAction(
actions.updateOrganizationName,
'name',
{},
[{ type: 'UPDATE_ORGANIZATION_NAME', payload: 'name' }],
[],
done,
);
}); });
}); });
}); });
...@@ -5,6 +5,14 @@ constants.STEPS = ['firstStep', 'secondStep']; ...@@ -5,6 +5,14 @@ constants.STEPS = ['firstStep', 'secondStep'];
const state = { const state = {
currentStep: 'secondStep', currentStep: 'secondStep',
isSetupForCompany: true,
availablePlans: [
{
value: 'firstPlan',
text: 'first plan',
},
],
selectedPlan: 'firstPlan',
}; };
describe('Subscriptions Getters', () => { describe('Subscriptions Getters', () => {
...@@ -24,16 +32,33 @@ describe('Subscriptions Getters', () => { ...@@ -24,16 +32,33 @@ describe('Subscriptions Getters', () => {
}); });
}); });
describe('activeStepIndex', () => { describe('currentStepIndex', () => {
it('returns a function', () => { it('returns a function', () => {
expect(getters.activeStepIndex(state, getters)).toBeInstanceOf(Function); expect(getters.currentStepIndex(state, getters)).toBeInstanceOf(Function);
}); });
it('calls the stepIndex function with the current step name', () => { it('calls the stepIndex function with the current step name', () => {
const stepIndexSpy = jest.spyOn(getters, 'stepIndex'); const stepIndexSpy = jest.spyOn(getters, 'stepIndex');
getters.activeStepIndex(state, getters); getters.currentStepIndex(state, getters);
expect(stepIndexSpy).toHaveBeenCalledWith('secondStep'); expect(stepIndexSpy).toHaveBeenCalledWith('secondStep');
}); });
}); });
describe('selectedPlanText', () => {
it('returns the text for selectedPlan', () => {
expect(
getters.selectedPlanText(state, { selectedPlanDetails: { text: 'selected plan' } }),
).toEqual('selected plan');
});
});
describe('selectedPlanDetails', () => {
it('returns the details for the selected plan', () => {
expect(getters.selectedPlanDetails(state)).toEqual({
value: 'firstPlan',
text: 'first plan',
});
});
});
}); });
...@@ -3,6 +3,10 @@ import * as types from 'ee/subscriptions/new/store/mutation_types'; ...@@ -3,6 +3,10 @@ import * as types from 'ee/subscriptions/new/store/mutation_types';
const state = () => ({ const state = () => ({
currentStep: 'firstStep', currentStep: 'firstStep',
selectedPlan: 'firstPlan',
isSetupForCompany: true,
numberOfUsers: 1,
organizationName: 'name',
}); });
let stateCopy; let stateCopy;
...@@ -11,10 +15,42 @@ beforeEach(() => { ...@@ -11,10 +15,42 @@ beforeEach(() => {
stateCopy = state(); stateCopy = state();
}); });
describe('ACTIVATE_STEP', () => { describe('UPDATE_CURRENT_STEP', () => {
it('should set the currentStep to the given step', () => { it('should set the currentStep to the given step', () => {
mutations[types.ACTIVATE_STEP](stateCopy, 'secondStep'); mutations[types.UPDATE_CURRENT_STEP](stateCopy, 'secondStep');
expect(stateCopy.currentStep).toEqual('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('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');
});
});
import createState from 'ee/subscriptions/new/store/state';
import * as constants from 'ee/subscriptions/new/constants';
constants.STEPS = ['firstStep', 'secondStep'];
describe('projectsSelector default state', () => {
const planData = [
{ id: 'firstPlanId', code: 'bronze', price_per_year: 48 },
{ id: 'secondPlanId', code: 'silver', price_per_year: 228 },
];
const initialData = {
planData: JSON.stringify(planData),
planId: 'secondPlanId',
setupForCompany: 'true',
fullName: 'Full Name',
};
const state = createState(initialData);
it('sets the currentStep to the first item of the STEPS constant', () => {
expect(state.currentStep).toEqual('firstStep');
});
describe('availablePlans', () => {
it('sets the availablePlans to the provided parsed planData', () => {
expect(state.availablePlans).toEqual([
{ value: 'firstPlanId', text: 'Bronze', pricePerUserPerYear: 48 },
{ value: 'secondPlanId', text: 'Silver', pricePerUserPerYear: 228 },
]);
});
it('sets the availablePlans to an empty array when no planData provided', () => {
const modifiedState = createState({ ...initialData, ...{ planData: undefined } });
expect(modifiedState.availablePlans).toEqual([]);
});
});
describe('selectedPlan', () => {
it('sets the selectedPlan to the provided planId if it is present in the provided planData', () => {
expect(state.selectedPlan).toEqual('secondPlanId');
});
it('sets the selectedPlan to the first value of availablePlans if planId is not provided', () => {
const modifiedState = createState({ ...initialData, ...{ planId: undefined } });
expect(modifiedState.selectedPlan).toEqual('firstPlanId');
});
it('sets the selectedPlan to the first value of availablePlans if planId is not present in the availablePlans', () => {
const modifiedState = createState({ ...initialData, ...{ planId: 'invalidPlanId' } });
expect(modifiedState.selectedPlan).toEqual('firstPlanId');
});
it('sets the selectedPlan to an empty string if availablePlans are not present', () => {
const modifiedState = createState({ ...initialData, ...{ planData: '[]' } });
expect(modifiedState.selectedPlan).toBeUndefined();
});
});
describe('isSetupForCompany', () => {
it('sets the isSetupForCompany to true if provided setupForCompany is "true"', () => {
expect(state.isSetupForCompany).toEqual(true);
});
it('sets the isSetupForCompany to false if provided setupForCompany is "false"', () => {
const modifiedState = createState({ ...initialData, ...{ setupForCompany: 'false' } });
expect(modifiedState.isSetupForCompany).toEqual(false);
});
});
it('sets the fullName to the provided fullName', () => {
expect(state.fullName).toEqual('Full Name');
});
it('sets the organizationName to null', () => {
expect(state.organizationName).toBeNull();
});
describe('numberOfUsers', () => {
it('sets the numberOfUsers to 0 when setupForCompany is true', () => {
expect(state.numberOfUsers).toEqual(0);
});
it('sets the numberOfUsers to 1 when setupForCompany is false', () => {
const modifiedState = createState({ ...initialData, ...{ setupForCompany: 'false' } });
expect(modifiedState.numberOfUsers).toEqual(1);
});
});
});
...@@ -3266,6 +3266,9 @@ msgstr "" ...@@ -3266,6 +3266,9 @@ msgstr ""
msgid "Checkout" msgid "Checkout"
msgstr "" msgstr ""
msgid "Checkout|%{selectedPlanText} plan"
msgstr ""
msgid "Checkout|1. Your profile" msgid "Checkout|1. Your profile"
msgstr "" msgstr ""
...@@ -3278,9 +3281,36 @@ msgstr "" ...@@ -3278,9 +3281,36 @@ msgstr ""
msgid "Checkout|Checkout" msgid "Checkout|Checkout"
msgstr "" msgstr ""
msgid "Checkout|Continue to billing"
msgstr ""
msgid "Checkout|Edit" msgid "Checkout|Edit"
msgstr "" msgstr ""
msgid "Checkout|GitLab plan"
msgstr ""
msgid "Checkout|Group"
msgstr ""
msgid "Checkout|Name of company or organization using GitLab"
msgstr ""
msgid "Checkout|Need more users? Purchase GitLab for your %{company}."
msgstr ""
msgid "Checkout|Number of users"
msgstr ""
msgid "Checkout|Subscription details"
msgstr ""
msgid "Checkout|Users"
msgstr ""
msgid "Checkout|company or team"
msgstr ""
msgid "Cherry-pick this commit" msgid "Cherry-pick this commit"
msgstr "" 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