Commit 143d35c3 authored by Jeremy Jackson's avatar Jeremy Jackson Committed by Clement Ho

Improve tracking performance

This restructures how tracking is implemented and will enable
better tracking on DOM changes as it eliminates the need to
re-look up elements.
parent 80eca67f
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;
}
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));
});
}
static bindDocument(category = document.body.dataset.page, documentOverride = null) {
const el = documentOverride || document;
if (!this.enabled() || el.trackingBound) return [];
customHandlingFor(el) {
const classes = el.classList;
el.trackingBound = true;
// 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
$dropdown.trigger('show.bs.dropdown'); // showing
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {});
expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {
label: '',
property: '',
value: '',
});
trigger('[data-track-event="toggle_dropdown"]', 'hide.bs.dropdown'); // hiding
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