Commit 59506ba3 authored by Phil Hughes's avatar Phil Hughes

Merge branch '342853-track-saas-signup-events' into 'master'

Instrument GitLab.com New Subscription Purchase flow (SaaS)

See merge request gitlab-org/gitlab!74828
parents 42bcb6a1 88222f8b
......@@ -16,12 +16,18 @@ export const ERROR_LOADING_PAYMENT_FORM = s__(
'Checkout|Failed to load the payment form. Please try again.',
);
// The order of the steps in this array determines the flow of the application
/* eslint-disable @gitlab/require-i18n-strings */
export const STEP_SUBSCRIPTION_DETAILS = 'subscriptionDetails';
export const STEP_BILLING_ADDRESS = 'billingAddress';
export const STEP_PAYMENT_METHOD = 'paymentMethod';
export const STEP_CONFIRM_ORDER = 'confirmOrder';
// The order of the steps in this array determines the flow of the application
export const STEPS = [
{ id: 'subscriptionDetails', __typename: 'Step' },
{ id: 'billingAddress', __typename: 'Step' },
{ id: 'paymentMethod', __typename: 'Step' },
{ id: 'confirmOrder', __typename: 'Step' },
{ id: STEP_SUBSCRIPTION_DETAILS, __typename: 'Step' },
{ id: STEP_BILLING_ADDRESS, __typename: 'Step' },
{ id: STEP_PAYMENT_METHOD, __typename: 'Step' },
{ id: STEP_CONFIRM_ORDER, __typename: 'Step' },
];
export const TRACK_SUCCESS_MESSAGE = 'Success';
/* eslint-enable @gitlab/require-i18n-strings */
......@@ -3,6 +3,7 @@ import { mapState } from 'vuex';
import ProgressBar from 'ee/registrations/components/progress_bar.vue';
import { STEPS, SUBSCRIPTON_FLOW_STEPS } from 'ee/registrations/constants';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import BillingAddress from 'jh_else_ee/subscriptions/new/components/checkout/billing_address.vue';
import ConfirmOrder from './checkout/confirm_order.vue';
import PaymentMethod from './checkout/payment_method.vue';
......@@ -10,11 +11,15 @@ import SubscriptionDetails from './checkout/subscription_details.vue';
export default {
components: { ProgressBar, SubscriptionDetails, BillingAddress, PaymentMethod, ConfirmOrder },
mixins: [Tracking.mixin()],
currentStep: STEPS.checkout,
steps: SUBSCRIPTON_FLOW_STEPS,
computed: {
...mapState(['isNewUser']),
},
mounted() {
this.track('render', { label: 'saas_checkout' });
},
i18n: {
checkout: s__('Checkout|Checkout'),
},
......
......@@ -2,10 +2,11 @@
import { GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import { STEP_BILLING_ADDRESS } from 'ee/subscriptions/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import Tracking from '~/tracking';
export default {
components: {
......@@ -17,6 +18,7 @@ export default {
directives: {
autofocusonshow,
},
mixins: [Tracking.mixin()],
computed: {
...mapState([
'country',
......@@ -117,6 +119,21 @@ export default {
'updateCountryState',
'updateZipCode',
]),
trackStepTransition() {
this.track('click_button', { label: 'select_country', property: this.country });
this.track('click_button', { label: 'state', property: this.countryState });
this.track('click_button', {
label: 'saas_checkout_postal_code',
property: this.zipCode,
});
this.track('click_button', { label: 'continue_payment' });
},
trackStepEdit() {
this.track('click_button', {
label: 'edit',
property: STEP_BILLING_ADDRESS,
});
},
},
i18n: {
stepTitle: s__('Checkout|Billing address'),
......@@ -129,7 +146,7 @@ export default {
stateSelectPrompt: s__('Checkout|Please select a state'),
zipCodeLabel: s__('Checkout|Zip code'),
},
stepId: STEPS[1].id,
stepId: STEP_BILLING_ADDRESS,
};
</script>
<template>
......@@ -138,6 +155,8 @@ export default {
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
@nextStep="trackStepTransition"
@stepEdit="trackStepEdit"
>
<template #body>
<gl-form-group :label="$options.i18n.countryLabel" label-size="sm" class="mb-3">
......
<script>
import { GlSprintf } from '@gitlab/ui';
import { mapState } from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import { STEP_PAYMENT_METHOD, TRACK_SUCCESS_MESSAGE } from 'ee/subscriptions/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__ } from '~/locale';
import Tracking from '~/tracking';
import Zuora from './zuora.vue';
export default {
......@@ -12,6 +13,7 @@ export default {
Step,
Zuora,
},
mixins: [Tracking.mixin()],
computed: {
...mapState(['paymentMethodId', 'creditCardDetails']),
isValid() {
......@@ -24,18 +26,43 @@ export default {
});
},
},
methods: {
trackStepSuccess() {
this.track('click_button', {
label: 'review_order',
property: TRACK_SUCCESS_MESSAGE,
});
},
trackStepError(errorMessage) {
this.track('click_button', {
label: 'review_order',
property: errorMessage,
});
},
trackStepEdit() {
this.track('click_button', {
label: 'edit',
property: STEP_PAYMENT_METHOD,
});
},
},
i18n: {
stepTitle: s__('Checkout|Payment method'),
creditCardDetails: s__('Checkout|%{cardType} ending in %{lastFourDigits}'),
expirationDate: s__('Checkout|Exp %{expirationMonth}/%{expirationYear}'),
},
stepId: STEPS[2].id,
stepId: STEP_PAYMENT_METHOD,
};
</script>
<template>
<step :step-id="$options.stepId" :title="$options.i18n.stepTitle" :is-valid="isValid">
<step
:step-id="$options.stepId"
:title="$options.i18n.stepTitle"
:is-valid="isValid"
@stepEdit="trackStepEdit"
>
<template #body="props">
<zuora :active="props.active" />
<zuora :active="props.active" @success="trackStepSuccess" @error="trackStepError" />
</template>
<template #summary>
<div class="js-summary-line-1">
......
......@@ -2,11 +2,12 @@
import { GlFormGroup, GlFormSelect, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapState, mapGetters, mapActions } from 'vuex';
import { STEPS } from 'ee/subscriptions/constants';
import { STEP_SUBSCRIPTION_DETAILS } from 'ee/subscriptions/constants';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
import Step from 'ee/vue_shared/purchase_flow/components/step.vue';
import { sprintf, s__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import Tracking from '~/tracking';
export default {
components: {
......@@ -20,6 +21,7 @@ export default {
directives: {
autofocusonshow,
},
mixins: [Tracking.mixin()],
computed: {
...mapState([
'availablePlans',
......@@ -33,6 +35,8 @@ export default {
]),
...mapGetters([
'selectedPlanText',
'selectedPlanDetails',
'selectedGroupId',
'isGroupSelected',
'selectedGroupUsers',
'selectedGroupName',
......@@ -134,6 +138,21 @@ export default {
'updateNumberOfUsers',
'updateOrganizationName',
]),
trackStepTransition() {
this.track('click_button', {
label: 'update_plan_type',
property: this.selectedPlanDetails.code,
});
this.track('click_button', { label: 'update_group', property: this.selectedGroupId });
this.track('click_button', { label: 'update_seat_count', property: this.numberOfUsers });
this.track('click_button', { label: 'continue_billing' });
},
trackStepEdit() {
this.track('click_button', {
label: 'edit',
property: STEP_SUBSCRIPTION_DETAILS,
});
},
},
i18n: {
stepTitle: s__('Checkout|Subscription details'),
......@@ -152,7 +171,7 @@ export default {
group: s__('Checkout|Group'),
users: s__('Checkout|Users'),
},
stepId: STEPS[0].id,
stepId: STEP_SUBSCRIPTION_DETAILS,
};
</script>
<template>
......@@ -161,6 +180,8 @@ export default {
:title="$options.i18n.stepTitle"
:is-valid="isValid"
:next-step-button-text="$options.i18n.nextStepButtonText"
@nextStep="trackStepTransition"
@stepEdit="trackStepEdit"
>
<template #body>
<gl-form-group :label="$options.i18n.selectedPlanLabel" label-size="sm" class="mb-3">
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import Tracking from '~/tracking';
import { ZUORA_SCRIPT_URL, ZUORA_IFRAME_OVERRIDE_PARAMS } from 'ee/subscriptions/constants';
export default {
components: {
GlLoadingIcon,
},
mixins: [Tracking.mixin()],
props: {
active: {
type: Boolean,
required: true,
},
},
emits: ['success', 'error'],
computed: {
...mapState([
'paymentFormParams',
......@@ -51,10 +54,18 @@ export default {
this.fetchPaymentFormParams();
}
},
handleZuoraCallback(response) {
this.paymentFormSubmitted(response);
if (response?.success === 'true') {
this.$emit('success');
} else {
this.$emit('error', response?.errorMessage);
}
},
renderZuoraIframe() {
const params = { ...this.paymentFormParams, ...ZUORA_IFRAME_OVERRIDE_PARAMS };
window.Z.runAfterRender(this.zuoraIframeRendered);
window.Z.render(params, {}, this.paymentFormSubmitted);
window.Z.render(params, {}, this.handleZuoraCallback);
},
},
};
......
......@@ -2,6 +2,7 @@
import { GlLink, GlSprintf } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import formattingMixins from '../../formatting_mixins';
export default {
......@@ -9,7 +10,7 @@ export default {
GlLink,
GlSprintf,
},
mixins: [formattingMixins],
mixins: [formattingMixins, Tracking.mixin()],
computed: {
...mapState(['startDate', 'taxRate', 'numberOfUsers']),
...mapGetters([
......@@ -81,6 +82,7 @@ export default {
href="https://about.gitlab.com/handbook/tax/#indirect-taxes-management"
target="_blank"
data-testid="tax-help-link"
@click="track('click_button', { label: 'tax_link' })"
>{{ content }}</gl-link
>
</template>
......
......@@ -72,9 +72,9 @@ export default {
throw new Error(JSON.stringify(data.errors));
}
})
.catch((error) =>
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true }),
)
.catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
})
.finally(() => {
this.isLoading = false;
});
......
......@@ -41,6 +41,7 @@ export default {
default: '',
},
},
emits: ['nextStep', 'stepEdit'],
data() {
return {
activeStep: {},
......@@ -71,7 +72,7 @@ export default {
const activeIndex = this.stepList.findIndex(({ id }) => id === this.activeStep.id);
return this.isFinished && index < activeIndex;
},
snakeCasedStep() {
dasherizedStep() {
return dasherize(convertToSnakeCase(this.stepId));
},
},
......@@ -93,10 +94,12 @@ export default {
})
.finally(() => {
this.loading = false;
this.$emit('nextStep');
});
},
async edit() {
this.loading = true;
this.$emit('stepEdit', this.stepId);
await this.$apollo
.mutate({
mutation: updateStepMutation,
......@@ -115,7 +118,7 @@ export default {
<template>
<div class="mb-3 mb-lg-5 gl-w-full">
<step-header :title="title" :is-finished="isFinished" />
<div :class="['card', snakeCasedStep]">
<div class="card" :class="dasherizedStep">
<div v-show="isActive" @keyup.enter="nextStep">
<slot name="body" :active="isActive"></slot>
<gl-form-group
......
......@@ -83,7 +83,11 @@ class SubscriptionsController < ApplicationController
group = params[:selected_group] ? current_group : create_group
return not_found if group.nil?
return render json: group.errors.to_json unless group.persisted?
unless group.persisted?
track_purchase message: group.errors.full_messages.to_s
return render json: group.errors.to_json
end
response = Subscriptions::CreateService.new(
current_user,
......@@ -93,7 +97,10 @@ class SubscriptionsController < ApplicationController
).execute
if response[:success]
track_purchase message: 'Success', namespace: group
response[:data] = { location: redirect_location(group) }
else
track_purchase message: response.dig(:data, :errors), namespace: group
end
render json: response[:data]
......@@ -101,6 +108,15 @@ class SubscriptionsController < ApplicationController
private
def track_purchase(message:, namespace: nil)
Gitlab::Tracking.event(self.class.name, 'click_button',
label: 'confirm_purchase',
property: message,
user: current_user,
namespace: namespace
)
end
def redirect_location(group)
return safe_redirect_path(params[:redirect_after_success]) if params[:redirect_after_success]
......
!!! 5
%html.subscriptions-layout-html{ lang: 'en' }
= render 'layouts/head'
%body.ui-indigo.d-flex.vh-100
%body.ui-indigo.d-flex.vh-100{ data: body_data }
= render "layouts/header/logo_with_title"
= render "layouts/broadcast"
.container.d-flex.gl-flex-direction-column.m-0
......
......@@ -10,7 +10,7 @@
- if show_plans?(namespace)
- plans = billing_available_plans(plans_data, current_plan)
.billing-plans.gl-mt-7
.billing-plans.gl-mt-7{ data: { track: { action: 'render', label: 'billing' } } }
- plans.each do |plan|
- next if plan.hide_card
- is_default_plan = current_plan.nil? && plan.default?
......
......@@ -264,7 +264,7 @@ RSpec.describe SubscriptionsController do
end
end
describe 'POST #create' do
describe 'POST #create', :snowplow do
subject do
post :create,
params: params,
......@@ -350,6 +350,20 @@ RSpec.describe SubscriptionsController do
expect(Gitlab::Json.parse(response.body)['name']).to match_array([Gitlab::Regex.group_name_regex_message, HtmlSafetyValidator.error_message])
end
it 'tracks errors' do
group.valid?
subject
expect_snowplow_event(
category: 'SubscriptionsController',
label: 'confirm_purchase',
action: 'click_button',
property: group.errors.full_messages.to_s,
user: user,
namespace: nil
)
end
end
end
......@@ -372,6 +386,14 @@ RSpec.describe SubscriptionsController do
subject
expect(response.body).to eq('{"errors":"error message"}')
expect_snowplow_event(
category: 'SubscriptionsController',
label: 'confirm_purchase',
action: 'click_button',
property: 'error message',
user: user,
namespace: group
)
end
end
......@@ -430,6 +452,19 @@ RSpec.describe SubscriptionsController do
expect(response.body).to eq({ location: redirect_after_success }.to_json)
end
it 'tracks the creation of the subscriptions' do
subject
expect_snowplow_event(
category: 'SubscriptionsController',
label: 'confirm_purchase',
action: 'click_button',
property: 'Success',
namespace: selected_group,
user: user
)
end
end
end
......
......@@ -3,6 +3,7 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import { STEPS } from 'ee/subscriptions/constants';
import BillingAddress from 'ee/subscriptions/new/components/checkout/billing_address.vue';
import { getStoreConfig } from 'ee/subscriptions/new/store';
......@@ -83,6 +84,48 @@ describe('Billing Address', () => {
});
});
describe('tracking', () => {
beforeEach(() => {
store.commit(types.UPDATE_COUNTRY, 'US');
store.commit(types.UPDATE_ZIP_CODE, '10467');
store.commit(types.UPDATE_COUNTRY_STATE, 'NY');
});
it('tracks completion details', () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Step).vm.$emit('nextStep');
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'select_country',
property: 'US',
});
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'state',
property: 'NY',
});
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'saas_checkout_postal_code',
property: '10467',
});
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'continue_payment',
});
});
it('tracks step edits', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Step).vm.$emit('stepEdit', 'stepID');
await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'edit',
property: 'billingAddress',
});
});
});
describe('validations', () => {
const isStepValid = () => wrapper.findComponent(Step).props('isValid');
......
import Vue from 'vue';
import Vuex from 'vuex';
import { triggerEvent, mockTracking, unmockTracking } from 'helpers/tracking_helper';
import Component from 'ee/subscriptions/new/components/order_summary.vue';
import createStore from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types';
......@@ -9,6 +10,7 @@ describe('Order Summary', () => {
Vue.use(Vuex);
let wrapper;
let trackingSpy;
const availablePlans = [
{ id: 'firstPlanId', code: 'bronze', price_per_year: 48, name: 'bronze plan' },
......@@ -36,9 +38,11 @@ describe('Order Summary', () => {
beforeEach(() => {
createComponent();
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
});
afterEach(() => {
unmockTracking();
wrapper.destroy();
});
......@@ -188,6 +192,17 @@ describe('Order Summary', () => {
});
describe('tax rate', () => {
describe('tracking', () => {
it('track click on tax_link', () => {
trackingSpy = mockTracking(undefined, findTaxHelpLink().element, jest.spyOn);
triggerEvent(findTaxHelpLink().element);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'tax_link',
});
});
});
describe('with a tax rate of 0', () => {
it('displays the total amount excluding vat', () => {
expect(wrapper.find('.js-total-ex-vat').exists()).toBe(true);
......
......@@ -2,6 +2,8 @@ import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import Zuora from 'ee/subscriptions/new/components/checkout/zuora.vue';
import { mockTracking } from 'helpers/tracking_helper';
import { STEPS } from 'ee/subscriptions/constants';
import PaymentMethod from 'ee/subscriptions/new/components/checkout/payment_method.vue';
import createStore from 'ee/subscriptions/new/store';
......@@ -68,4 +70,42 @@ describe('Payment Method', () => {
expect(wrapper.find('.js-summary-line-2').text()).toEqual('Exp 12/09');
});
});
describe('tracking', () => {
it('tracks step completion details', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Zuora).vm.$emit('success');
await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'review_order',
property: 'Success',
});
});
it('tracks zuora errors on step transition', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Zuora).vm.$emit('error', 'This was a mistake.');
await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'review_order',
property: 'This was a mistake.',
});
});
it('tracks step edits', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Step).vm.$emit('stepEdit', 'stepID');
await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'edit',
property: 'paymentMethod',
});
});
});
});
......@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import { STEPS } from 'ee/subscriptions/constants';
import Component from 'ee/subscriptions/new/components/checkout/subscription_details.vue';
import { NEW_GROUP } from 'ee/subscriptions/new/constants';
......@@ -340,6 +341,58 @@ describe('Subscription Details', () => {
});
});
describe('tracking', () => {
let store;
beforeEach(() => {
const mockApollo = createMockApolloProvider(STEPS);
store = createStore(
createDefaultInitialStoreData({
isNewUser: 'false',
namespaceId: '132',
setupForCompany: 'false',
}),
);
store.commit(types.UPDATE_NUMBER_OF_USERS, 13);
wrapper = createComponent({ apolloProvider: mockApollo, store });
});
it('tracks completion details', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Step).vm.$emit('nextStep');
await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'update_plan_type',
property: 'silver',
});
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'update_seat_count',
property: 13,
});
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'update_group',
property: 132,
});
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'continue_billing',
});
});
it('tracks step edits', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
wrapper.findComponent(Step).vm.$emit('stepEdit', 'stepID');
await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
label: 'edit',
property: 'subscriptionDetails',
});
});
});
describe('validations', () => {
const isStepValid = () => wrapper.findComponent(Step).props('isValid');
let store;
......
......@@ -117,7 +117,25 @@ describe('Zuora', () => {
return nextTick().then(() => {
expect(actionMocks.zuoraIframeRendered).toHaveBeenCalled();
wrapper.vm.handleZuoraCallback();
expect(actionMocks.paymentFormSubmitted).toHaveBeenCalled();
});
});
});
describe('tracking', () => {
it('emits success event on correct response', async () => {
wrapper.vm.handleZuoraCallback({ success: 'true' });
await nextTick();
expect(wrapper.emitted().success.length).toEqual(1);
});
it('emits error with message', async () => {
createComponent();
wrapper.vm.handleZuoraCallback({ errorMessage: '1337' });
await nextTick();
expect(wrapper.emitted().error.length).toEqual(1);
expect(wrapper.emitted().error[0]).toEqual(['1337']);
});
});
});
......@@ -4,6 +4,7 @@ import Vuex from 'vuex';
import ProgressBar from 'ee/registrations/components/progress_bar.vue';
import Component from 'ee/subscriptions/new/components/checkout.vue';
import createStore from 'ee/subscriptions/new/store';
import { mockTracking } from 'helpers/tracking_helper';
describe('Checkout', () => {
Vue.use(Vuex);
......@@ -58,4 +59,16 @@ describe('Checkout', () => {
expect(findProgressBar().props('currentStep')).toEqual('Checkout');
});
});
describe('tracking', () => {
it('tracks render on mount', () => {
const trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
shallowMount(Component, { store: createStore() });
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'render', {
label: 'saas_checkout',
});
});
});
});
......@@ -17,7 +17,6 @@ jest.mock('~/flash');
describe('Step', () => {
let wrapper;
const initialProps = {
stepId: STEPS[1].id,
isValid: true,
......@@ -33,6 +32,7 @@ describe('Step', () => {
}
function createComponent(options = {}) {
const { apolloProvider, propsData } = options;
return shallowMount(Step, {
propsData: { ...initialProps, ...propsData },
apolloProvider,
......@@ -198,4 +198,28 @@ describe('Step', () => {
});
});
});
describe('tracking', () => {
it('emits stepEdit', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { stepId: STEPS[1].id }, apolloProvider: mockApollo });
// button in step-summary is not rendered b/c of shallowMount
wrapper.vm.edit();
await waitForPromises();
expect(wrapper.emitted().stepEdit[0]).toEqual(['secondStep']);
});
it('emits nextStep on step transition', async () => {
const mockApollo = createMockApolloProvider(STEPS, 1);
wrapper = createComponent({ propsData: { stepId: STEPS[1].id }, apolloProvider: mockApollo });
await activateFirstStep(mockApollo);
wrapper.findComponent(GlButton).vm.$emit('click');
await waitForPromises();
expect(wrapper.emitted().nextStep).toBeTruthy();
});
});
});
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