Commit c677b7b6 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'vs/change-text-in-buy-minutes' into 'master'

Adapt SubscriptionDetails for purchasing the CI Minutes addon

See merge request gitlab-org/gitlab!65305
parents 9d154fd7 57d72e27
<script> <script>
import ProgressBar from 'ee/registrations/components/progress_bar.vue';
import { STEPS, SUBSCRIPTON_FLOW_STEPS } from 'ee/registrations/constants';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import AddonPurchaseDetails from './checkout/addon_purchase_details.vue';
import BillingAddress from './checkout/billing_address.vue'; import BillingAddress from './checkout/billing_address.vue';
import ConfirmOrder from './checkout/confirm_order.vue'; import ConfirmOrder from './checkout/confirm_order.vue';
import PaymentMethod from './checkout/payment_method.vue'; import PaymentMethod from './checkout/payment_method.vue';
import SubscriptionDetails from './checkout/subscription_details.vue';
export default { export default {
components: { ProgressBar, SubscriptionDetails, BillingAddress, PaymentMethod, ConfirmOrder }, components: { AddonPurchaseDetails, BillingAddress, PaymentMethod, ConfirmOrder },
props: { props: {
plans: { plans: {
type: Array, type: Array,
required: true, required: true,
}, },
}, },
apollo: {
isNewUser: {
query: stateQuery,
},
},
currentStep: STEPS.checkout,
steps: SUBSCRIPTON_FLOW_STEPS,
i18n: { i18n: {
checkout: s__('Checkout|Checkout'), checkout: s__('Checkout|Checkout'),
}, },
}; };
</script> </script>
<template> <template>
<div <div class="checkout gl-display-flex gl-flex-direction-column gl-align-items-center">
v-if="!$apollo.loading"
class="checkout gl-display-flex gl-flex-direction-column gl-justify-content-between w-100"
>
<div class="full-width">
<progress-bar v-if="isNewUser" :steps="$options.steps" :current-step="$options.currentStep" />
<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" /> <addon-purchase-details :plans="plans" />
<billing-address /> <billing-address />
<payment-method /> <payment-method />
</div>
<confirm-order /> <confirm-order />
</div> </div>
</template> </template>
<script>
import { GlAlert, GlFormInput, GlSprintf } from '@gitlab/ui';
import { CI_MINUTES_PER_PACK } from 'ee/subscriptions/buy_minutes/constants';
import { STEPS } from 'ee/subscriptions/constants';
import updateState from 'ee/subscriptions/graphql/mutations/update_state.mutation.graphql';
import stateQuery from 'ee/subscriptions/graphql/queries/state.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 { sprintf, s__, formatNumber } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
export default {
name: 'AddonPurchaseDetails',
components: {
GlAlert,
GlFormInput,
GlSprintf,
Step,
},
directives: {
autofocusonshow,
},
apollo: {
quantity: {
query: stateQuery,
update(data) {
return data.subscription.quantity;
},
},
},
computed: {
quantityModel: {
get() {
return this.quantity || 1;
},
set(quantity) {
this.updateQuantity(quantity);
},
},
isValid() {
return this.quantity > 0;
},
totalCiMinutes() {
return this.quantity * CI_MINUTES_PER_PACK;
},
summaryCiMinutesQuantityText() {
return sprintf(this.$options.i18n.summaryCiMinutesQuantity, {
quantity: this.quantity,
});
},
ciMinutesQuantityText() {
return sprintf(this.$options.i18n.ciMinutesQuantityText, {
totalCiMinutes: formatNumber(this.totalCiMinutes),
});
},
summaryCiMinutesTotal() {
return sprintf(this.$options.i18n.summaryCiMinutesTotal, {
quantity: formatNumber(this.totalCiMinutes),
});
},
},
methods: {
updateQuantity(quantity = 1) {
this.$apollo
.mutate({
mutation: updateState,
variables: {
input: { subscription: { quantity } },
},
})
.catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
});
},
},
i18n: {
stepTitle: s__('Checkout|Purchase details'),
nextStepButtonText: s__('Checkout|Continue to billing'),
ciMinutesPacksLabel: s__('Checkout|CI minute packs'),
ciMinutesAlertText: s__(
"Checkout|CI minute packs are only used after you've used your subscription's monthly quota. The additional minutes will roll over month to month and are valid for one year.",
),
ciMinutesPacksQuantityFormula: s__('Checkout|x 1,000 minutes per pack = %{strong}'),
ciMinutesQuantityText: s__('Checkout|%{totalCiMinutes} CI minutes'),
summaryCiMinutesQuantity: s__('Checkout|%{quantity} CI minute packs'),
summaryCiMinutesTotal: s__('Checkout|Total minutes: %{quantity}'),
},
stepId: STEPS[0].id,
};
</script>
<template>
<step
v-if="!$apollo.loading"
:step-id="$options.stepId"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
>
<template #body>
<gl-alert variant="info" class="gl-mb-3" :dismissible="false">
{{ $options.i18n.ciMinutesAlertText }}
</gl-alert>
<label for="quantity">{{ $options.i18n.ciMinutesPacksLabel }}</label>
<div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
<gl-form-input
ref="quantity"
v-model.number="quantityModel"
name="quantity"
type="number"
:min="1"
data-qa-selector="quantity"
class="gl-w-15"
/>
<div class="gl-ml-3" data-testid="ci-minutes-quantity-text">
<gl-sprintf :message="$options.i18n.ciMinutesPacksQuantityFormula">
<template #strong>
<strong>{{ ciMinutesQuantityText }}</strong>
</template>
</gl-sprintf>
</div>
</div>
</template>
<template #summary>
<strong ref="summary-line-1">
{{ summaryCiMinutesQuantityText }}
</strong>
<div ref="summary-line-3">{{ summaryCiMinutesTotal }}</div>
</template>
</step>
</template>
<script>
import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { STEPS } from 'ee/subscriptions/constants';
import UPDATE_STATE from 'ee/subscriptions/graphql/mutations/update_state.mutation.graphql';
import STATE_QUERY from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
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 autofocusonshow from '~/vue_shared/directives/autofocusonshow';
export default {
components: {
GlFormGroup,
GlFormSelect,
GlFormInput,
GlSprintf,
GlLink,
Step,
},
directives: {
autofocusonshow,
},
props: {
plans: {
type: Array,
required: true,
},
},
data() {
return {
subscription: {},
namespaces: [],
customer: {},
isSetupForCompany: null,
isNewUser: null,
selectedPlanId: null,
};
},
apollo: {
state: {
query: STATE_QUERY,
manual: true,
result({ data, loading }) {
if (loading) {
return;
}
this.subscription = data.subscription;
this.namespaces = data.namespaces;
this.customer = data.customer;
this.isSetupForCompany = data.isSetupForCompany;
this.isNewUser = data.isNewUser;
this.selectedPlanId = data.selectedPlanId;
},
},
},
computed: {
selectedPlanModel: {
get() {
return this.selectedPlanId || this.plans[0].id;
},
set(planId) {
this.updateState({ subscription: { planId } });
},
},
selectedGroupModel: {
get() {
return this.subscription.namespaceId;
},
set(namespaceId) {
const quantity =
this.namespaces.find((namespace) => namespace.id === namespaceId)?.users || 1;
this.updateState({ subscription: { namespaceId, quantity } });
},
},
numberOfUsersModel: {
get() {
return this.selectedGroupUsers || 1;
},
set(number) {
this.updateState({ subscription: { quantity: number } });
},
},
companyModel: {
get() {
return this.customer.company;
},
set(company) {
this.updateState({ customer: { company } });
},
},
selectedPlan() {
const selectedPlan = this.plans.find((plan) => plan.id === this.selectedPlanId);
if (!selectedPlan) {
return this.plans[0];
}
return selectedPlan;
},
selectedPlanTextLine() {
return sprintf(this.$options.i18n.selectedPlan, { selectedPlanText: this.selectedPlan.id });
},
selectedGroup() {
return this.namespaces.find((namespace) => namespace.id === this.subscription.namespaceId);
},
selectedGroupUsers() {
return this.selectedGroup?.users || 1;
},
isGroupSelected() {
return this.subscription.namespaceId !== null;
},
isNumberOfUsersValid() {
return (
this.subscription.quantity > 0 && this.subscription.quantity >= this.selectedGroupUsers
);
},
isValid() {
if (this.isSetupForCompany) {
return (
this.isNumberOfUsersValid &&
!isEmpty(this.selectedPlanId) &&
(!isEmpty(this.customer.company) || this.isGroupSelected)
);
}
return this.subscription.quantity === 1 && !isEmpty(this.selectedPlanId);
},
isShowingGroupSelector() {
return !this.isNewUser && this.namespaces.length;
},
isNewGroupSelected() {
return this.subscription.namespaceId === NEW_GROUP;
},
isShowingNameOfCompanyInput() {
return this.isSetupForCompany && (!this.namespaces.length || this.isNewGroupSelected);
},
groupOptionsWithDefault() {
return [
{
name: this.$options.i18n.groupSelectPrompt,
id: null,
},
...this.namespaces,
{
name: this.$options.i18n.groupSelectCreateNewOption,
id: NEW_GROUP,
},
];
},
groupSelectDescription() {
return this.isNewGroupSelected
? this.$options.i18n.createNewGroupDescription
: this.$options.i18n.selectedGroupDescription;
},
},
mounted() {
this.preselectPlan();
},
methods: {
updateState(payload = {}) {
this.$apollo
.mutate({
mutation: UPDATE_STATE,
variables: {
input: payload,
},
})
.catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
});
},
toggleIsSetupForCompany() {
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: {
stepTitle: s__('Checkout|Subscription details'),
nextStepButtonText: s__('Checkout|Continue to billing'),
selectedPlanLabel: s__('Checkout|GitLab plan'),
selectedGroupLabel: s__('Checkout|GitLab group'),
groupSelectPrompt: __('Select'),
groupSelectCreateNewOption: s__('Checkout|Create a new group'),
selectedGroupDescription: s__('Checkout|Your subscription will be applied to this group'),
createNewGroupDescription: s__("Checkout|You'll create your new group after checkout"),
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: __('Group'),
users: __('Users'),
},
stepId: STEPS[0].id,
};
</script>
<template>
<step
v-if="!$apollo.loading"
:step-id="$options.stepId"
: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" class="mb-3">
<gl-form-select
v-model="selectedPlanModel"
v-autofocusonshow
:options="plans"
value-field="id"
text-field="name"
data-qa-selector="plan_name"
/>
</gl-form-group>
<gl-form-group
v-if="isShowingGroupSelector"
:label="$options.i18n.selectedGroupLabel"
:description="groupSelectDescription"
label-size="sm"
class="mb-3"
>
<gl-form-select
ref="group-select"
v-model="selectedGroupModel"
:options="groupOptionsWithDefault"
value-field="id"
text-field="name"
data-qa-selector="group_name"
/>
</gl-form-group>
<gl-form-group
v-if="isShowingNameOfCompanyInput"
:label="$options.i18n.organizationNameLabel"
label-size="sm"
class="mb-3"
>
<gl-form-input ref="organization-name" v-model="companyModel" type="text" />
</gl-form-group>
<div class="combined d-flex">
<gl-form-group :label="$options.i18n.numberOfUsersLabel" label-size="sm" class="number">
<gl-form-input
ref="number-of-users"
v-model.number="numberOfUsersModel"
type="number"
:min="selectedGroupUsers"
:disabled="!isSetupForCompany"
data-qa-selector="number_of_users"
/>
</gl-form-group>
<gl-form-group
v-if="!isSetupForCompany"
ref="company-link"
class="label ml-3 align-self-end"
>
<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 ref="summary-line-1">
{{ selectedPlanTextLine }}
</strong>
<div v-if="isSetupForCompany" ref="summary-line-2">
{{ $options.i18n.group }}: {{ customer.company || selectedGroup.name }}
</div>
<div ref="summary-line-3">{{ $options.i18n.users }}: {{ subscription.quantity }}</div>
</template>
</step>
</template>
...@@ -5,3 +5,5 @@ export const planTags = { ...@@ -5,3 +5,5 @@ 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'; export const GITLAB_CLIENT = 'gitlabClient';
export const CI_MINUTES_PER_PACK = 1000;
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { GlAlert } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash'; import { merge } from 'lodash';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import ProgressBar from 'ee/registrations/components/progress_bar.vue'; import AddonPurchaseDetails from 'ee/subscriptions/buy_minutes/components/checkout/addon_purchase_details.vue';
import Checkout from 'ee/subscriptions/buy_minutes/components/checkout.vue';
import subscriptionsResolvers from 'ee/subscriptions/buy_minutes/graphql/resolvers'; import subscriptionsResolvers from 'ee/subscriptions/buy_minutes/graphql/resolvers';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql'; import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers'; import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import { import {
stateData as initialStateData, stateData as initialStateData,
mockCiMinutesPlans, mockCiMinutesPlans,
} from 'ee_jest/subscriptions/buy_minutes/mock_data'; } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
describe('Checkout', () => { describe('AddonPurchaseDetails', () => {
const resolvers = merge({}, purchaseFlowResolvers, subscriptionsResolvers); const resolvers = { ...purchaseFlowResolvers, ...subscriptionsResolvers };
let wrapper; let wrapper;
const createMockApolloProvider = (stateData = {}) => { const createMockApolloProvider = (stateData = {}) => {
...@@ -36,50 +37,59 @@ describe('Checkout', () => { ...@@ -36,50 +37,59 @@ describe('Checkout', () => {
const createComponent = (stateData = {}) => { const createComponent = (stateData = {}) => {
const apolloProvider = createMockApolloProvider(stateData); const apolloProvider = createMockApolloProvider(stateData);
wrapper = shallowMount(Checkout, { return mount(AddonPurchaseDetails, {
apolloProvider,
localVue, localVue,
apolloProvider,
propsData: { propsData: {
plans: mockCiMinutesPlans, plans: mockCiMinutesPlans,
}, },
stubs: {
Step,
},
}); });
}; };
const findProgressBar = () => wrapper.find(ProgressBar); const findQuantity = () => wrapper.findComponent({ ref: 'quantity' });
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findCiMinutesQuantityText = () => wrapper.find('[data-testid="ci-minutes-quantity-text"]');
const isStepValid = () => wrapper.findComponent(Step).props('isValid');
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe.each([ it('sets the min quantity to 1', () => {
[true, true], expect(findQuantity().attributes('min')).toBe('1');
[false, false],
])('when isNewUser=%s', (isNewUser, visible) => {
beforeEach(async () => {
createComponent({ isNewUser });
}); });
it(`progress bar visibility is ${visible}`, () => { it('displays the alert', () => {
expect(findProgressBar().exists()).toBe(visible); expect(findGlAlert().isVisible()).toBe(true);
}); expect(findGlAlert().text()).toMatchInterpolatedText(
AddonPurchaseDetails.i18n.ciMinutesAlertText,
);
}); });
describe('passing the correct options to the progress bar component', () => { it('displays the total CI minutes text', async () => {
beforeEach(async () => { expect(findCiMinutesQuantityText().text()).toMatchInterpolatedText(
createComponent({ isNewUser: true }); 'x 1,000 minutes per pack = 1,000 CI minutes',
await waitForPromises(); );
}); });
it('passes the steps', () => { it('is valid', () => {
expect(findProgressBar().props('steps')).toEqual([ expect(isStepValid()).toBe(true);
'Your profile',
'Checkout',
'Your GitLab group',
]);
}); });
it('passes the current step', () => { it('is invalid when quantity is less than 1', async () => {
expect(findProgressBar().props('currentStep')).toEqual('Checkout'); wrapper = createComponent({
subscription: { namespaceId: 483, quantity: 0 },
}); });
await nextTick();
expect(isStepValid()).toBe(false);
}); });
}); });
import { mount, createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import SubscriptionDetails from 'ee/subscriptions/buy_minutes/components/checkout/subscription_details.vue';
import subscriptionsResolvers from 'ee/subscriptions/buy_minutes/graphql/resolvers';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import {
stateData as initialStateData,
mockParsedNamespaces,
mockCiMinutesPlans,
} from 'ee_jest/subscriptions/buy_minutes/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('Subscription Details', () => {
const resolvers = { ...purchaseFlowResolvers, ...subscriptionsResolvers };
let wrapper;
const createMockApolloProvider = (stateData = {}) => {
const mockApollo = createMockApollo([], resolvers);
const data = merge({}, initialStateData, stateData);
mockApollo.clients.defaultClient.cache.writeQuery({
query: stateQuery,
data,
});
return mockApollo;
};
const createComponent = (stateData = {}) => {
const apolloProvider = createMockApolloProvider(stateData);
return mount(SubscriptionDetails, {
localVue,
apolloProvider,
propsData: {
plans: mockCiMinutesPlans,
},
stubs: {
Step,
},
});
};
const organizationNameInput = () => wrapper.find({ ref: 'organization-name' });
const groupSelect = () => wrapper.find({ ref: 'group-select' });
const numberOfUsersInput = () => wrapper.find({ ref: 'number-of-users' });
const companyLink = () => wrapper.find({ ref: 'company-link' });
afterEach(() => {
wrapper.destroy();
});
describe('A new user setting up for personal use', () => {
beforeEach(() => {
wrapper = createComponent({ isNewUser: true, isSetupForCompany: false });
});
it('should not display an input field for the company or group name', () => {
expect(organizationNameInput().exists()).toBe(false);
});
it('should not display the group select', () => {
expect(groupSelect().exists()).toBe(false);
});
it('should disable the number of users input field', () => {
expect(numberOfUsersInput().attributes('disabled')).toBeDefined();
});
it('should set the min number of users to 1', () => {
expect(numberOfUsersInput().attributes('min')).toBe('1');
});
it('should show a link to change to setting up for a company', () => {
expect(companyLink().exists()).toBe(true);
});
});
describe('A new user setting up for a company or group', () => {
beforeEach(() => {
wrapper = createComponent({ isNewUser: true, isSetupForCompany: true, namespaces: [] });
});
it('should display an input field for the company or group name', () => {
expect(organizationNameInput().exists()).toBe(true);
});
it('should not display the group select', () => {
expect(groupSelect().exists()).toBe(false);
});
it('should enable the number of users input field', () => {
expect(numberOfUsersInput().attributes('disabled')).toBeUndefined();
});
it('should set the min number of users to 1', () => {
expect(numberOfUsersInput().attributes('min')).toBe('1');
});
it('should not show a link to change to setting up for a company', () => {
expect(companyLink().exists()).toBe(false);
});
});
describe('An existing user without any groups', () => {
beforeEach(() => {
wrapper = createComponent({ isNewUser: false, namespaces: [] });
});
it('should display an input field for the company or group name', () => {
expect(organizationNameInput().exists()).toBe(true);
});
it('should not display the group select', () => {
expect(groupSelect().exists()).toBe(false);
});
it('should enable the number of users input field', () => {
expect(numberOfUsersInput().attributes('disabled')).toBeUndefined();
});
it('should set the min number of users to 1', () => {
expect(numberOfUsersInput().attributes('min')).toBe('1');
});
it('should not show a link to change to setting up for a company', () => {
expect(companyLink().exists()).toBe(false);
});
});
describe('An existing user with groups', () => {
beforeEach(() => {
wrapper = createComponent({ isNewUser: false, namespaces: mockParsedNamespaces });
});
it('should not display an input field for the company or group name', () => {
expect(organizationNameInput().exists()).toBe(false);
});
it('should display the group select', () => {
expect(groupSelect().exists()).toBe(true);
});
it('should enable the number of users input field', () => {
expect(numberOfUsersInput().attributes('disabled')).toBeUndefined();
});
it('should set the min number of users to 1', () => {
expect(numberOfUsersInput().attributes('min')).toBe('1');
});
it('should not show a link to change to setting up for a company', () => {
expect(companyLink().exists()).toBe(false);
});
});
describe('selecting an existing group', () => {
beforeEach(() => {
wrapper = createComponent({
subscription: { namespaceId: 483 },
namespaces: mockParsedNamespaces,
});
});
it('should display the correct description', () => {
expect(wrapper.text()).toContain('Your subscription will be applied to this group');
});
it('should set the min number of users to 12', () => {
expect(numberOfUsersInput().attributes('min')).toBe('12');
});
});
describe('selecting "Create a new group', () => {
beforeEach(() => {
wrapper = createComponent({
subscription: { namespaceId: NEW_GROUP },
namespaces: mockParsedNamespaces,
});
});
it('should display the correct description', () => {
expect(wrapper.text()).toContain("You'll create your new group after checkout");
});
it('should display an input field for the company or group name', () => {
expect(organizationNameInput().exists()).toBe(true);
});
it('should set the min number of users to 1', () => {
expect(numberOfUsersInput().attributes('min')).toBe('1');
});
});
describe('An existing user coming from group billing page', () => {
beforeEach(() => {
wrapper = createComponent({
isNewUser: false,
isSetupForCompany: true,
subscription: { namespaceId: 132 },
namespaces: mockParsedNamespaces,
});
});
it('should not display an input field for the company or group name', () => {
expect(organizationNameInput().exists()).toBe(false);
});
it('should display the group select', () => {
expect(groupSelect().exists()).toBe(true);
});
it('should enable the number of users input field', () => {
expect(numberOfUsersInput().attributes('disabled')).toBeUndefined();
});
it('should set the min number of users to 3', () => {
expect(numberOfUsersInput().attributes('min')).toBe('3');
});
it('should set the selected group to initial namespace id', () => {
expect(groupSelect().element.value).toBe('132');
});
it('should not show a link to change to setting up for a company', () => {
expect(companyLink().exists()).toBe(false);
});
describe('selecting an existing group', () => {
beforeEach(() => {
wrapper = createComponent({
subscription: { namespaceId: 483 },
namespaces: mockParsedNamespaces,
});
});
it('should display the correct description', () => {
expect(wrapper.text()).toContain('Your subscription will be applied to this group');
});
it('should set the min number of users to 12', () => {
expect(numberOfUsersInput().attributes('min')).toBe('12');
});
it('should set the selected group to the user selected namespace id', () => {
expect(groupSelect().element.value).toBe('483');
});
});
});
describe('validations', () => {
const isStepValid = () => wrapper.find(Step).props('isValid');
describe('when setting up for a company', () => {
it('should be valid', () => {
wrapper = createComponent({
subscription: { namespaceId: 483, quantity: 14 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' },
});
expect(isStepValid()).toBe(true);
});
it('should be invalid when no organization name is given, and no group is selected', async () => {
wrapper = createComponent({
isSetupForCompany: true,
subscription: { namespaceId: null },
customer: { company: null },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
it('should be invalid when number of users is 0', async () => {
wrapper = createComponent({
isSetupForCompany: true,
subscription: { quantity: 0 },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
it('should be invalid when number of users is smaller than the selected group users', async () => {
wrapper = createComponent({
isSetupForCompany: true,
subscription: { namespaceId: 483, quantity: 10 },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
});
describe('when not setting up for a company', () => {
beforeEach(() => {
wrapper = createComponent({
isSetupForCompany: false,
subscription: { namespaceId: 483, quantity: 1 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' },
});
});
it('should be valid', () => {
expect(isStepValid()).toBe(true);
});
it('should be invalid when no number of users is 0', async () => {
wrapper = createComponent({
isSetupForCompany: false,
subscription: { namespaceId: 483, quantity: 0 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
it('should be invalid when no number of users is greater than 1', async () => {
wrapper = createComponent({
isSetupForCompany: false,
subscription: { namespaceId: 483, quantity: 2 },
selectedPlanId: 'firstPlanId',
customer: { company: 'Organization name' },
});
await nextTick();
expect(isStepValid()).toBe(false);
});
});
});
});
...@@ -6325,18 +6325,30 @@ msgstr "" ...@@ -6325,18 +6325,30 @@ msgstr ""
msgid "Checkout|%{name}'s GitLab subscription" msgid "Checkout|%{name}'s GitLab subscription"
msgstr "" msgstr ""
msgid "Checkout|%{quantity} CI minute packs"
msgstr ""
msgid "Checkout|%{selectedPlanText} plan" msgid "Checkout|%{selectedPlanText} plan"
msgstr "" msgstr ""
msgid "Checkout|%{startDate} - %{endDate}" msgid "Checkout|%{startDate} - %{endDate}"
msgstr "" msgstr ""
msgid "Checkout|%{totalCiMinutes} CI minutes"
msgstr ""
msgid "Checkout|(x%{numberOfUsers})" msgid "Checkout|(x%{numberOfUsers})"
msgstr "" msgstr ""
msgid "Checkout|Billing address" msgid "Checkout|Billing address"
msgstr "" msgstr ""
msgid "Checkout|CI minute packs"
msgstr ""
msgid "Checkout|CI minute packs are only used after you've used your subscription's monthly quota. The additional minutes will roll over month to month and are valid for one year."
msgstr ""
msgid "Checkout|Checkout" msgid "Checkout|Checkout"
msgstr "" msgstr ""
...@@ -6418,6 +6430,9 @@ msgstr "" ...@@ -6418,6 +6430,9 @@ msgstr ""
msgid "Checkout|Please select a state" msgid "Checkout|Please select a state"
msgstr "" msgstr ""
msgid "Checkout|Purchase details"
msgstr ""
msgid "Checkout|Select" msgid "Checkout|Select"
msgstr "" msgstr ""
...@@ -6442,6 +6457,9 @@ msgstr "" ...@@ -6442,6 +6457,9 @@ msgstr ""
msgid "Checkout|Total" msgid "Checkout|Total"
msgstr "" msgstr ""
msgid "Checkout|Total minutes: %{quantity}"
msgstr ""
msgid "Checkout|Users" msgid "Checkout|Users"
msgstr "" msgstr ""
...@@ -6460,6 +6478,9 @@ msgstr "" ...@@ -6460,6 +6478,9 @@ msgstr ""
msgid "Checkout|company or team" msgid "Checkout|company or team"
msgstr "" msgstr ""
msgid "Checkout|x 1,000 minutes per pack = %{strong}"
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