Commit 9eec4fe2 authored by Clement Ho's avatar Clement Ho

Merge branch 'ee-tracking-performance' into 'master'

Improve tracking performance and simplify tracking implementations

See merge request gitlab-org/gitlab!15759
parents 80eca67f 143d35c3
export const initSidebarTracking = () => {};
export const trackEvent = () => {};
// Noop function which has a EE counter-part
export default () => {};
import Vue from 'vue';
import { initSidebarTracking } from 'ee_else_ce/event_tracking/issue_sidebar';
import issuableApp from './components/app.vue';
import { parseIssuableData } from './utils/parse_data';
......@@ -9,9 +8,6 @@ export default function initIssueableApp() {
components: {
issuableApp,
},
mounted() {
initSidebarTracking();
},
render(createElement) {
return createElement('issuable-app', {
props: parseIssuableData(),
......
......@@ -19,7 +19,9 @@ export default {
<gl-button
ref="button"
v-gl-tooltip
class="note-action-button js-note-action-reply"
class="note-action-button"
data-track-event="click_button"
data-track-label="reply_comment_button"
variant="transparent"
:title="__('Reply to comment')"
@click="$emit('startReplying')"
......
import Vue from 'vue';
import initNoteStats from 'ee_else_ce/event_tracking/notes';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
import createStore from './stores';
......@@ -39,9 +38,6 @@ document.addEventListener('DOMContentLoaded', () => {
notesData: JSON.parse(notesDataset.notesData),
};
},
mounted() {
initNoteStats();
},
render(createElement) {
return createElement('notes-app', {
props: {
......
<script>
import { n__ } from '~/locale';
import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
name: 'AssigneeTitle',
......@@ -30,11 +29,6 @@ export default {
return n__('Assignee', `%d Assignees`, assignees);
},
},
methods: {
trackEdit() {
trackEvent('click_edit_button', 'assignee');
},
},
};
</script>
<template>
......@@ -45,7 +39,9 @@ export default {
v-if="editable"
class="js-sidebar-dropdown-toggle edit-link float-right"
href="#"
@click.prevent="trackEdit"
data-track-event="click_edit_button"
data-track-label="right_sidebar"
data-track-property="assignee"
>
{{ __('Edit') }}
</a>
......
......@@ -5,7 +5,6 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
components: {
......@@ -52,11 +51,6 @@ export default {
toggleForm() {
this.edit = !this.edit;
},
onEditClick() {
this.toggleForm();
trackEvent('click_edit_button', 'confidentiality');
},
updateConfidentialAttribute(confidential) {
this.service
.update('issue', { confidential })
......@@ -88,7 +82,10 @@ export default {
v-if="isEditable"
class="float-right confidential-edit"
href="#"
@click.prevent="onEditClick"
data-track-event="click_edit_button"
data-track-label="right_sidebar"
data-track-property="confidentiality"
@click.prevent="toggleForm"
>
{{ __('Edit') }}
</a>
......
......@@ -6,7 +6,6 @@ import issuableMixin from '~/vue_shared/mixins/issuable';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
components: {
......@@ -66,11 +65,6 @@ export default {
toggleForm() {
this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
},
onEditClick() {
this.toggleForm();
trackEvent('click_edit_button', 'lock_issue');
},
updateLockedAttribute(locked) {
this.mediator.service
.update(this.issuableType, {
......@@ -114,7 +108,10 @@ export default {
v-if="isEditable"
class="float-right lock-edit"
type="button"
@click.prevent="onEditClick"
data-track-event="click_edit_button"
data-track-label="right_sidebar"
data-track-property="lock_issue"
@click.prevent="toggleForm"
>
{{ __('Edit') }}
</button>
......
<script>
import { __ } from '~/locale';
import Tracking from '~/tracking';
import icon from '~/vue_shared/components/icon.vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
......@@ -19,6 +19,7 @@ export default {
icon,
toggleButton,
},
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
props: {
loading: {
type: Boolean,
......@@ -65,7 +66,10 @@ export default {
// Component event emission.
this.$emit('toggleSubscription', this.id);
trackEvent('toggle_button', 'notifications', this.subscribed ? 0 : 1);
this.track('toggle_button', {
property: 'notifications',
value: this.subscribed ? 0 : 1,
});
},
onClickCollapsedIcon() {
this.$emit('toggleSidebar');
......
import $ from 'jquery';
import _ from 'underscore';
const DEFAULT_SNOWPLOW_OPTIONS = {
namespace: 'gl',
......@@ -14,18 +14,31 @@ const DEFAULT_SNOWPLOW_OPTIONS = {
linkClickTracking: false,
};
const extractData = (el, opts = {}) => {
const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset;
let trackValue = el.dataset.trackValue || el.value || '';
if (el.type === 'checkbox' && !el.checked) trackValue = false;
return [
trackEvent + (opts.suffix || ''),
{
label: trackLabel,
property: trackProperty,
value: trackValue,
},
];
const eventHandler = (e, func, opts = {}) => {
const el = e.target.closest('[data-track-event]');
const action = el && el.dataset.trackEvent;
if (!action) return;
let value = el.dataset.trackValue || el.value || undefined;
if (el.type === 'checkbox' && !el.checked) value = false;
const data = {
label: el.dataset.trackLabel,
property: el.dataset.trackProperty,
value,
context: el.dataset.trackContext,
};
func(opts.category, action + (opts.suffix || ''), _.omit(data, _.isUndefined));
};
const eventHandlers = (category, func) => {
const handler = opts => e => eventHandler(e, func, { ...{ category }, ...opts });
const handlers = [];
handlers.push({ name: 'click', func: handler() });
handlers.push({ name: 'show.bs.dropdown', func: handler({ suffix: '_show' }) });
handlers.push({ name: 'hide.bs.dropdown', func: handler({ suffix: '_hide' }) });
return handlers;
};
export default class Tracking {
......@@ -39,49 +52,43 @@ export default class Tracking {
return typeof window.snowplow === 'function' && this.trackable();
}
static event(category = document.body.dataset.page, event = 'generic', data = {}) {
static event(category = document.body.dataset.page, action = 'generic', data = {}) {
if (!this.enabled()) return false;
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
if (!category) throw new Error('Tracking: no category provided for tracking.');
return window.snowplow(
'trackStructEvent',
category,
event,
Object.assign({}, { label: '', property: '', value: '' }, data),
);
const { label, property, value, context } = data;
const contexts = context ? [context] : undefined;
return window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
}
constructor(category = document.body.dataset.page) {
this.category = category;
}
static bindDocument(category = document.body.dataset.page, documentOverride = null) {
const el = documentOverride || document;
if (!this.enabled() || el.trackingBound) return [];
bind(container = document) {
if (!this.constructor.enabled()) return;
container.querySelectorAll(`[data-track-event]`).forEach(el => {
if (this.customHandlingFor(el)) return;
// jquery is required for select2, so we use it always
// see: https://github.com/select2/select2/issues/4686
$(el).on('click', this.eventHandler(this.category));
});
}
el.trackingBound = true;
customHandlingFor(el) {
const classes = el.classList;
// bootstrap dropdowns
if (classes.contains('dropdown')) {
$(el).on('show.bs.dropdown', this.eventHandler(this.category, { suffix: '_show' }));
$(el).on('hide.bs.dropdown', this.eventHandler(this.category, { suffix: '_hide' }));
return true;
}
return false;
const handlers = eventHandlers(category, (...args) => this.event(...args));
handlers.forEach(event => el.addEventListener(event.name, event.func));
return handlers;
}
eventHandler(category = null, opts = {}) {
return e => {
this.constructor.event(category || this.category, ...extractData(e.currentTarget, opts));
static mixin(opts) {
return {
data() {
return {
tracking: {
// eslint-disable-next-line no-underscore-dangle
category: this.$options.name || this.$options._componentTag,
},
};
},
methods: {
track(action, data) {
const category = opts.category || data.category || this.tracking.category;
Tracking.event(category || 'unspecified', action, { ...opts, ...this.tracking, ...data });
},
},
};
}
}
......@@ -89,7 +96,7 @@ export default class Tracking {
export function initUserTracking() {
if (!Tracking.enabled()) return;
const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions);
const opts = { ...DEFAULT_SNOWPLOW_OPTIONS, ...window.snowplowOptions };
window.snowplow('newTracker', opts.namespace, opts.hostname, opts);
window.snowplow('enableActivityTracking', 30, 30);
......@@ -97,4 +104,6 @@ export function initUserTracking() {
if (opts.formTracking) window.snowplow('enableFormTracking');
if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking');
Tracking.bindDocument();
}
import Tracking from '~/tracking';
export const initSidebarTracking = () => {
new Tracking().bind(document.querySelector('.js-issuable-sidebar'));
};
export const trackEvent = (eventType, property, value = '') => {
Tracking.event(document.body.dataset.page, eventType, {
label: 'right_sidebar',
property,
value,
});
};
......@@ -27,8 +27,6 @@ export default function trackNavbarEvents() {
const navbar = document.querySelector('.navbar-gitlab');
if (!navbar) return;
new Tracking(TRACKING_CATEGORY).bind(navbar);
// track search inputs within frequent-items component
navbar.querySelectorAll(`.frequent-items-dropdown-container input`).forEach(el => {
el.addEventListener('click', e => {
......
import Tracking from '~/tracking';
export default () => {
document.querySelector('.main-notes-list').addEventListener('click', event => {
const isReplyButtonClick = event.target.parentElement.classList.contains(
'js-note-action-reply',
);
if (isReplyButtonClick) {
Tracking.event(document.body.dataset.page, 'click_button', {
label: 'reply_comment_button',
property: '',
value: '',
});
}
});
new Tracking().bind();
};
import '~/pages/projects/issues/index/index';
import Tracking from '~/tracking';
document.addEventListener('DOMContentLoaded', () => {
new Tracking().bind();
});
import '~/pages/projects/new/index';
import initCustomProjectTemplates from 'ee/projects/custom_project_templates';
import Tracking from '~/tracking';
import { bindOnboardingEvents } from 'ee/onboarding/new_project';
document.addEventListener('DOMContentLoaded', () => {
initCustomProjectTemplates();
new Tracking().bind();
bindOnboardingEvents(document.getElementById('new_project'));
});
import '~/pages/sessions/index';
import initSignInRegisterTracking from './sign_in_register_tracking';
document.addEventListener('DOMContentLoaded', initSignInRegisterTracking);
import Tracking from '~/tracking';
export default () => {
const container = document.getElementById('#signin-container');
new Tracking().bind(container);
};
<script>
import $ from 'jquery';
import { __, s__ } from '~/locale';
import Tracking from '~/tracking';
import eventHub from '~/sidebar/event_hub';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { trackEvent } from 'ee/event_tracking/issue_sidebar';
export default {
components: {
......@@ -15,6 +15,7 @@ export default {
directives: {
tooltip,
},
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
props: {
fetching: {
type: Boolean,
......@@ -105,7 +106,7 @@ export default {
onEditClick(shouldShowEditField = true) {
this.showEditField(shouldShowEditField);
trackEvent('click_edit_button', 'weight');
this.track('click_edit_button', { property: 'weight' });
},
showEditField(bool = true) {
this.shouldShowEditField = bool;
......
import Tracking from '~/tracking';
import { initSidebarTracking } from 'ee/event_tracking/issue_sidebar';
describe('ee/event_tracking/issue_sidebar', () => {
beforeEach(() => {
setFixtures(`
<div>
<div class="js-issuable-sidebar">I'm an issuable sidebar</div>
</div>
`);
});
const findIssuableSidebar = () => document.querySelector('.js-issuable-sidebar');
describe('initSidebarTracking', () => {
beforeEach(() => {
jest.spyOn(Tracking.prototype, 'bind');
initSidebarTracking();
});
it('bind to be called with element', () => {
expect(Tracking.prototype.bind).toHaveBeenCalledWith(findIssuableSidebar());
});
});
});
import Vue from 'vue';
import Tracking from '~/tracking';
import { shallowMount } from '@vue/test-utils';
import initNoteStats from 'ee_else_ce/event_tracking/notes';
jest.mock('~/tracking');
describe('initNoteStats', () => {
let wrapper;
const createComponent = template => {
const component = Vue.component('Notes', {
name: 'Notes',
template,
});
return shallowMount(component, { attachToDocument: true });
};
afterEach(() => {
jest.clearAllMocks();
wrapper.destroy();
});
describe('is a reply', () => {
beforeEach(() => {
wrapper = createComponent(
"<div class='js-note-action-reply'><button class='main-notes-list'></button></div>",
);
initNoteStats();
});
it('calls bindTrackableContainer', () => {
expect(Tracking.prototype.bind).toHaveBeenCalledTimes(1);
});
it('calls trackEvent', () => {
wrapper.find('.main-notes-list').trigger('click');
expect(Tracking.event).toHaveBeenCalledTimes(1);
});
});
describe('is not a reply', () => {
it('does not call trackEvent', () => {
wrapper = createComponent("<div><button class='main-notes-list'></button></div>");
initNoteStats();
wrapper.find('.main-notes-list').trigger('click');
expect(Tracking.event).not.toHaveBeenCalled();
});
});
});
import testAction from 'spec/helpers/vuex_action_helper';
import Tracking from '~/tracking';
import createState from 'ee/security_dashboard/store/modules/filters/state';
import * as types from 'ee/security_dashboard/store/modules/filters/mutation_types';
import module, * as actions from 'ee/security_dashboard/store/modules/filters/actions';
describe('filters actions', () => {
beforeEach(() => {
spyOn(Tracking, 'event');
});
describe('setFilter', () => {
it('should commit the SET_FILTER mutuation', done => {
const state = createState();
......
......@@ -3,6 +3,7 @@ import weight from 'ee/sidebar/components/weight/weight.vue';
import eventHub from '~/sidebar/event_hub';
import { ENTER_KEY_CODE } from '~/lib/utils/keycodes';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
const DEFAULT_PROPS = {
weightNoneValue: 'None',
......@@ -11,10 +12,8 @@ const DEFAULT_PROPS = {
describe('Weight', function() {
let vm;
let Weight;
let statsSpy;
beforeEach(() => {
statsSpy = spyOnDependency(weight, 'trackEvent');
Weight = Vue.extend(weight);
});
......@@ -117,11 +116,12 @@ describe('Weight', function() {
editable: true,
});
vm.$el.querySelector('.js-weight-edit-link').click();
const spy = mockTracking('_category_', vm.$el, spyOn, afterEach);
triggerEvent('.js-weight-edit-link');
vm.$nextTick()
.then(() => {
expect(statsSpy).toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
......
import $ from 'jquery';
import { setHTMLFixture } from './helpers/fixtures';
import Tracking, { initUserTracking } from '~/tracking';
describe('Tracking', () => {
let snowplowSpy;
let bindDocumentSpy;
beforeEach(() => {
window.snowplow = window.snowplow || (() => {});
......@@ -17,6 +16,10 @@ describe('Tracking', () => {
});
describe('initUserTracking', () => {
beforeEach(() => {
bindDocumentSpy = jest.spyOn(Tracking, 'bindDocument').mockImplementation(() => null);
});
it('calls through to get a new tracker with the expected options', () => {
initUserTracking();
expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
......@@ -50,6 +53,11 @@ describe('Tracking', () => {
expect(snowplowSpy).toHaveBeenCalledWith('enableFormTracking');
expect(snowplowSpy).toHaveBeenCalledWith('enableLinkClickTracking');
});
it('binds the document event handling', () => {
initUserTracking();
expect(bindDocumentSpy).toHaveBeenCalled();
});
});
describe('.event', () => {
......@@ -62,11 +70,15 @@ describe('Tracking', () => {
it('tracks to snowplow (our current tracking system)', () => {
Tracking.event('_category_', '_eventName_', { label: '_label_' });
expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', '_category_', '_eventName_', {
label: '_label_',
property: '',
value: '',
});
expect(snowplowSpy).toHaveBeenCalledWith(
'trackStructEvent',
'_category_',
'_eventName_',
'_label_',
undefined,
undefined,
undefined,
);
});
it('skips tracking if snowplow is unavailable', () => {
......@@ -99,83 +111,70 @@ describe('Tracking', () => {
});
describe('tracking interface events', () => {
let eventSpy = null;
let subject = null;
let eventSpy;
const trigger = (selector, eventName = 'click') => {
const event = new Event(eventName, { bubbles: true });
document.querySelector(selector).dispatchEvent(event);
};
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
subject = new Tracking('_category_');
Tracking.bindDocument('_category_'); // only happens once
setHTMLFixture(`
<input data-track-event="click_input1" data-track-label="_label_" value="_value_"/>
<input data-track-event="click_input2" data-track-value="_value_override_" value="_value_"/>
<input type="checkbox" data-track-event="toggle_checkbox" value="_value_" checked/>
<input class="dropdown" data-track-event="toggle_dropdown"/>
<div class="js-projects-list-holder"></div>
<div data-track-event="nested_event"><span class="nested"></span></div>
`);
});
it('binds to clicks on elements matching [data-track-event]', () => {
subject.bind(document);
$('[data-track-event="click_input1"]').click();
trigger('[data-track-event="click_input1"]');
expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', {
label: '_label_',
value: '_value_',
property: '',
});
});
it('allows value override with the data-track-value attribute', () => {
subject.bind(document);
$('[data-track-event="click_input2"]').click();
trigger('[data-track-event="click_input2"]');
expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', {
label: '',
value: '_value_override_',
property: '',
});
});
it('handles checkbox values correctly', () => {
subject.bind(document);
const $checkbox = $('[data-track-event="toggle_checkbox"]');
$checkbox.click(); // unchecking
trigger('[data-track-event="toggle_checkbox"]'); // checking
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
label: '',
property: '',
value: false,
});
$checkbox.click(); // checking
trigger('[data-track-event="toggle_checkbox"]'); // unchecking
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
label: '',
property: '',
value: '_value_',
});
});
it('handles bootstrap dropdowns', () => {
new Tracking('_category_').bind(document);
const $dropdown = $('[data-track-event="toggle_dropdown"]');
trigger('[data-track-event="toggle_dropdown"]', 'show.bs.dropdown'); // showing
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {});
$dropdown.trigger('show.bs.dropdown'); // showing
trigger('[data-track-event="toggle_dropdown"]', 'hide.bs.dropdown'); // hiding
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {
label: '',
property: '',
value: '',
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {});
});
$dropdown.trigger('hide.bs.dropdown'); // hiding
it('handles nested elements inside an element with tracking', () => {
trigger('span.nested', 'click');
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {
label: '',
property: '',
value: '',
});
expect(eventSpy).toHaveBeenCalledWith('_category_', 'nested_event', {});
});
});
});
import Tracking from '~/tracking';
export default Tracking;
let document;
let handlers;
export function mockTracking(category = '_category_', documentOverride, spyMethod) {
document = documentOverride || window.document;
window.snowplow = () => {};
Tracking.bindDocument(category, document);
return spyMethod ? spyMethod(Tracking, 'event') : null;
}
export function unmockTracking() {
window.snowplow = undefined;
handlers.forEach(event => document.removeEventListener(event.name, event.func));
}
export function triggerEvent(selectorOrEl, eventName = 'click') {
const event = new Event(eventName, { bubbles: true });
const el = typeof selectorOrEl === 'string' ? document.querySelector(selectorOrEl) : selectorOrEl;
el.dispatchEvent(event);
}
import Vue from 'vue';
import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('AssigneeTitle component', () => {
let component;
let AssigneeTitleComponent;
let statsSpy;
beforeEach(() => {
statsSpy = spyOnDependency(AssigneeTitle, 'trackEvent');
AssigneeTitleComponent = Vue.extend(AssigneeTitle);
});
......@@ -105,15 +104,20 @@ describe('AssigneeTitle component', () => {
expect(component.$el.querySelector('.edit-link')).not.toBeNull();
});
it('calls trackEvent when edit is clicked', () => {
it('tracks the event when edit is clicked', () => {
component = new AssigneeTitleComponent({
propsData: {
numberOfAssignees: 0,
editable: true,
},
}).$mount();
component.$el.querySelector('.js-sidebar-dropdown-toggle').click();
expect(statsSpy).toHaveBeenCalled();
const spy = mockTracking('_category_', component.$el, spyOn);
triggerEvent('.js-sidebar-dropdown-toggle');
expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
label: 'right_sidebar',
property: 'assignee',
});
});
});
import Vue from 'vue';
import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('Confidential Issue Sidebar Block', () => {
let vm1;
let vm2;
let statsSpy;
beforeEach(() => {
statsSpy = spyOnDependency(confidentialIssueSidebar, 'trackEvent');
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
update: () => Promise.resolve(true),
......@@ -70,9 +69,13 @@ describe('Confidential Issue Sidebar Block', () => {
});
});
it('calls trackEvent when "Edit" is clicked', () => {
vm1.$el.querySelector('.confidential-edit').click();
it('tracks the event when "Edit" is clicked', () => {
const spy = mockTracking('_category_', vm1.$el, spyOn);
triggerEvent('.confidential-edit');
expect(statsSpy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
label: 'right_sidebar',
property: 'confidentiality',
});
});
});
import Vue from 'vue';
import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue';
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('LockIssueSidebar', () => {
let vm1;
let vm2;
let statsSpy;
beforeEach(() => {
statsSpy = spyOnDependency(lockIssueSidebar, 'trackEvent');
const Component = Vue.extend(lockIssueSidebar);
const mediator = {
......@@ -61,10 +60,14 @@ describe('LockIssueSidebar', () => {
});
});
it('calls trackEvent when "Edit" is clicked', () => {
vm1.$el.querySelector('.lock-edit').click();
it('tracks an event when "Edit" is clicked', () => {
const spy = mockTracking('_category_', vm1.$el, spyOn);
triggerEvent('.lock-edit');
expect(statsSpy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
label: 'right_sidebar',
property: 'lock_issue',
});
});
it('displays the edit form when opened from collapsed state', done => {
......
......@@ -2,14 +2,13 @@ import Vue from 'vue';
import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTracking } from 'spec/helpers/tracking_helper';
describe('Subscriptions', function() {
let vm;
let Subscriptions;
let statsSpy;
beforeEach(() => {
statsSpy = spyOnDependency(subscriptions, 'trackEvent');
Subscriptions = Vue.extend(subscriptions);
});
......@@ -53,6 +52,7 @@ describe('Subscriptions', function() {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(eventHub, '$emit');
spyOn(vm, '$emit');
spyOn(vm, 'track');
vm.toggleSubscription();
......@@ -60,11 +60,12 @@ describe('Subscriptions', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
});
it('calls trackEvent when toggled', () => {
it('tracks the event when toggled', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
const spy = mockTracking('_category_', vm.$el, spyOn);
vm.toggleSubscription();
expect(statsSpy).toHaveBeenCalled();
expect(spy).toHaveBeenCalled();
});
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
......
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