Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/tracking_spec.js')
-rw-r--r--spec/frontend/tracking_spec.js693
1 files changed, 0 insertions, 693 deletions
diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js
deleted file mode 100644
index 21fed51ff10..00000000000
--- a/spec/frontend/tracking_spec.js
+++ /dev/null
@@ -1,693 +0,0 @@
-import { setHTMLFixture } from 'helpers/fixtures';
-import { TEST_HOST } from 'helpers/test_constants';
-import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants';
-import { getExperimentData, getAllExperimentContexts } from '~/experimentation/utils';
-import Tracking, { initUserTracking, initDefaultTrackers } from '~/tracking';
-import { REFERRER_TTL, URLS_CACHE_STORAGE_KEY } from '~/tracking/constants';
-import getStandardContext from '~/tracking/get_standard_context';
-
-jest.mock('~/experimentation/utils', () => ({
- getExperimentData: jest.fn(),
- getAllExperimentContexts: jest.fn(),
-}));
-
-describe('Tracking', () => {
- let standardContext;
- let snowplowSpy;
- let bindDocumentSpy;
- let trackLoadEventsSpy;
- let enableFormTracking;
- let setAnonymousUrlsSpy;
-
- beforeAll(() => {
- window.gl = window.gl || {};
- window.gl.snowplowUrls = {};
- window.gl.snowplowStandardContext = {
- schema: 'iglu:com.gitlab/gitlab_standard',
- data: {
- environment: 'testing',
- source: 'unknown',
- extra: {},
- },
- };
-
- standardContext = getStandardContext();
- });
-
- beforeEach(() => {
- getExperimentData.mockReturnValue(undefined);
- getAllExperimentContexts.mockReturnValue([]);
-
- window.snowplow = window.snowplow || (() => {});
- window.snowplowOptions = {
- namespace: '_namespace_',
- hostname: 'app.gitfoo.com',
- cookieDomain: '.gitfoo.com',
- };
- snowplowSpy = jest.spyOn(window, 'snowplow');
- });
-
- describe('initUserTracking', () => {
- it('calls through to get a new tracker with the expected options', () => {
- initUserTracking();
- expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
- namespace: '_namespace_',
- hostname: 'app.gitfoo.com',
- cookieDomain: '.gitfoo.com',
- appId: '',
- userFingerprint: false,
- respectDoNotTrack: true,
- forceSecureTracker: true,
- eventMethod: 'post',
- contexts: { webPage: true, performanceTiming: true },
- formTracking: false,
- linkClickTracking: false,
- pageUnloadTimer: 10,
- formTrackingConfig: {
- fields: { allow: [] },
- forms: { allow: [] },
- },
- });
- });
- });
-
- describe('initDefaultTrackers', () => {
- beforeEach(() => {
- bindDocumentSpy = jest.spyOn(Tracking, 'bindDocument').mockImplementation(() => null);
- trackLoadEventsSpy = jest.spyOn(Tracking, 'trackLoadEvents').mockImplementation(() => null);
- enableFormTracking = jest
- .spyOn(Tracking, 'enableFormTracking')
- .mockImplementation(() => null);
- setAnonymousUrlsSpy = jest.spyOn(Tracking, 'setAnonymousUrls').mockImplementation(() => null);
- });
-
- it('should activate features based on what has been enabled', () => {
- initDefaultTrackers();
- expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
- expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', null, [standardContext]);
- expect(snowplowSpy).not.toHaveBeenCalledWith('enableFormTracking');
- expect(snowplowSpy).not.toHaveBeenCalledWith('enableLinkClickTracking');
-
- window.snowplowOptions = {
- ...window.snowplowOptions,
- formTracking: true,
- linkClickTracking: true,
- formTrackingConfig: { forms: { whitelist: ['foo'] }, fields: { whitelist: ['bar'] } },
- };
-
- initDefaultTrackers();
- expect(enableFormTracking).toHaveBeenCalledWith(window.snowplowOptions.formTrackingConfig);
- expect(snowplowSpy).toHaveBeenCalledWith('enableLinkClickTracking');
- });
-
- it('binds the document event handling', () => {
- initDefaultTrackers();
- expect(bindDocumentSpy).toHaveBeenCalled();
- });
-
- it('tracks page loaded events', () => {
- initDefaultTrackers();
- expect(trackLoadEventsSpy).toHaveBeenCalled();
- });
-
- it('calls the anonymized URLs method', () => {
- initDefaultTrackers();
- expect(setAnonymousUrlsSpy).toHaveBeenCalled();
- });
-
- describe('when there are experiment contexts', () => {
- const experimentContexts = [
- {
- schema: TRACKING_CONTEXT_SCHEMA,
- data: { experiment: 'experiment1', variant: 'control' },
- },
- {
- schema: TRACKING_CONTEXT_SCHEMA,
- data: { experiment: 'experiment_two', variant: 'candidate' },
- },
- ];
-
- beforeEach(() => {
- getAllExperimentContexts.mockReturnValue(experimentContexts);
- });
-
- it('includes those contexts alongside the standard context', () => {
- initDefaultTrackers();
- expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', null, [
- standardContext,
- ...experimentContexts,
- ]);
- });
- });
- });
-
- describe('.event', () => {
- afterEach(() => {
- window.doNotTrack = undefined;
- navigator.doNotTrack = undefined;
- navigator.msDoNotTrack = undefined;
- });
-
- it('tracks to snowplow (our current tracking system)', () => {
- Tracking.event('_category_', '_eventName_', { label: '_label_' });
-
- expect(snowplowSpy).toHaveBeenCalledWith(
- 'trackStructEvent',
- '_category_',
- '_eventName_',
- '_label_',
- undefined,
- undefined,
- [standardContext],
- );
- });
-
- it('allows adding extra data to the default context', () => {
- const extra = { foo: 'bar' };
-
- Tracking.event('_category_', '_eventName_', { extra });
-
- expect(snowplowSpy).toHaveBeenCalledWith(
- 'trackStructEvent',
- '_category_',
- '_eventName_',
- undefined,
- undefined,
- undefined,
- [
- {
- ...standardContext,
- data: {
- ...standardContext.data,
- extra,
- },
- },
- ],
- );
- });
-
- it('skips tracking if snowplow is unavailable', () => {
- window.snowplow = false;
- Tracking.event('_category_', '_eventName_');
-
- expect(snowplowSpy).not.toHaveBeenCalled();
- });
-
- it('skips tracking if the user does not want to be tracked (general spec)', () => {
- window.doNotTrack = '1';
- Tracking.event('_category_', '_eventName_');
-
- expect(snowplowSpy).not.toHaveBeenCalled();
- });
-
- it('skips tracking if the user does not want to be tracked (firefox legacy)', () => {
- navigator.doNotTrack = 'yes';
- Tracking.event('_category_', '_eventName_');
-
- expect(snowplowSpy).not.toHaveBeenCalled();
- });
-
- it('skips tracking if the user does not want to be tracked (IE legacy)', () => {
- navigator.msDoNotTrack = '1';
- Tracking.event('_category_', '_eventName_');
-
- expect(snowplowSpy).not.toHaveBeenCalled();
- });
- });
-
- describe('.enableFormTracking', () => {
- it('tells snowplow to enable form tracking, with only explicit contexts', () => {
- const config = { forms: { allow: ['form-class1'] }, fields: { allow: ['input-class1'] } };
- Tracking.enableFormTracking(config, ['_passed_context_', standardContext]);
-
- expect(snowplowSpy).toHaveBeenCalledWith(
- 'enableFormTracking',
- { forms: { whitelist: ['form-class1'] }, fields: { whitelist: ['input-class1'] } },
- ['_passed_context_'],
- );
- });
-
- it('throws an error if no allow rules are provided', () => {
- const expectedError = new Error('Unable to enable form event tracking without allow rules.');
-
- expect(() => Tracking.enableFormTracking()).toThrow(expectedError);
- expect(() => Tracking.enableFormTracking({ fields: { allow: true } })).toThrow(expectedError);
- expect(() => Tracking.enableFormTracking({ fields: { allow: [] } })).not.toThrow(
- expectedError,
- );
- });
-
- it('does not add empty form whitelist rules', () => {
- Tracking.enableFormTracking({ fields: { allow: ['input-class1'] } });
-
- expect(snowplowSpy).toHaveBeenCalledWith(
- 'enableFormTracking',
- { fields: { whitelist: ['input-class1'] } },
- [],
- );
- });
-
- describe('when `document.readyState` does not equal `complete`', () => {
- const originalReadyState = document.readyState;
- const setReadyState = (value) => {
- Object.defineProperty(document, 'readyState', {
- value,
- configurable: true,
- });
- };
- const fireReadyStateChangeEvent = () => {
- document.dispatchEvent(new Event('readystatechange'));
- };
-
- beforeEach(() => {
- setReadyState('interactive');
- });
-
- afterEach(() => {
- setReadyState(originalReadyState);
- });
-
- it('does not call `window.snowplow` until `readystatechange` is fired and `document.readyState` equals `complete`', () => {
- Tracking.enableFormTracking({ fields: { allow: ['input-class1'] } });
-
- expect(snowplowSpy).not.toHaveBeenCalled();
-
- fireReadyStateChangeEvent();
-
- expect(snowplowSpy).not.toHaveBeenCalled();
-
- setReadyState('complete');
- fireReadyStateChangeEvent();
-
- expect(snowplowSpy).toHaveBeenCalled();
- });
- });
- });
-
- describe('.flushPendingEvents', () => {
- it('flushes any pending events', () => {
- Tracking.initialized = false;
- Tracking.event('_category_', '_eventName_', { label: '_label_' });
-
- expect(snowplowSpy).not.toHaveBeenCalled();
-
- Tracking.flushPendingEvents();
-
- expect(snowplowSpy).toHaveBeenCalledWith(
- 'trackStructEvent',
- '_category_',
- '_eventName_',
- '_label_',
- undefined,
- undefined,
- [standardContext],
- );
- });
- });
-
- describe('.setAnonymousUrls', () => {
- afterEach(() => {
- window.gl.snowplowPseudonymizedPageUrl = '';
- localStorage.removeItem(URLS_CACHE_STORAGE_KEY);
- });
-
- it('does nothing if URLs are not provided', () => {
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).not.toHaveBeenCalled();
- expect(localStorage.getItem(URLS_CACHE_STORAGE_KEY)).toBe(null);
- });
-
- it('sets the page URL when provided and populates the cache', () => {
- window.gl.snowplowPseudonymizedPageUrl = TEST_HOST;
-
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).toHaveBeenCalledWith('setCustomUrl', TEST_HOST);
- expect(JSON.parse(localStorage.getItem(URLS_CACHE_STORAGE_KEY))[0]).toStrictEqual({
- url: TEST_HOST,
- referrer: '',
- originalUrl: window.location.href,
- timestamp: Date.now(),
- });
- });
-
- it('appends the hash/fragment to the pseudonymized URL', () => {
- const hash = 'first-heading';
- window.gl.snowplowPseudonymizedPageUrl = TEST_HOST;
- window.location.hash = hash;
-
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).toHaveBeenCalledWith('setCustomUrl', `${TEST_HOST}#${hash}`);
- });
-
- it('does not set the referrer URL by default', () => {
- window.gl.snowplowPseudonymizedPageUrl = TEST_HOST;
-
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).not.toHaveBeenCalledWith('setReferrerUrl', expect.any(String));
- });
-
- describe('with referrers cache', () => {
- const testUrl = '/namespace:1/project:2/-/merge_requests/5';
- const testOriginalUrl = '/my-namespace/my-project/-/merge_requests/';
- const setUrlsCache = (data) =>
- localStorage.setItem(URLS_CACHE_STORAGE_KEY, JSON.stringify(data));
-
- beforeEach(() => {
- window.gl.snowplowPseudonymizedPageUrl = TEST_HOST;
- Object.defineProperty(document, 'referrer', { value: '', configurable: true });
- });
-
- it('does nothing if a referrer can not be found', () => {
- setUrlsCache([
- {
- url: testUrl,
- originalUrl: TEST_HOST,
- timestamp: Date.now(),
- },
- ]);
-
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).not.toHaveBeenCalledWith('setReferrerUrl', expect.any(String));
- });
-
- it('sets referrer URL from the page URL found in cache', () => {
- Object.defineProperty(document, 'referrer', { value: testOriginalUrl });
- setUrlsCache([
- {
- url: testUrl,
- originalUrl: testOriginalUrl,
- timestamp: Date.now(),
- },
- ]);
-
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).toHaveBeenCalledWith('setReferrerUrl', testUrl);
- });
-
- it('ignores and removes old entries from the cache', () => {
- const oldTimestamp = Date.now() - (REFERRER_TTL + 1);
- Object.defineProperty(document, 'referrer', { value: testOriginalUrl });
- setUrlsCache([
- {
- url: testUrl,
- originalUrl: testOriginalUrl,
- timestamp: oldTimestamp,
- },
- ]);
-
- Tracking.setAnonymousUrls();
-
- expect(snowplowSpy).not.toHaveBeenCalledWith('setReferrerUrl', testUrl);
- expect(localStorage.getItem(URLS_CACHE_STORAGE_KEY)).not.toContain(oldTimestamp);
- });
- });
- });
-
- describe.each`
- term
- ${'event'}
- ${'action'}
- `('tracking interface events with data-track-$term', ({ term }) => {
- let eventSpy;
-
- beforeEach(() => {
- eventSpy = jest.spyOn(Tracking, 'event');
- Tracking.bindDocument('_category_'); // only happens once
- setHTMLFixture(`
- <input data-track-${term}="click_input1" data-track-label="_label_" value=0 />
- <input data-track-${term}="click_input2" data-track-value=0 value=0/>
- <input type="checkbox" data-track-${term}="toggle_checkbox" value=1 checked/>
- <input class="dropdown" data-track-${term}="toggle_dropdown"/>
- <div data-track-${term}="nested_event"><span class="nested"></span></div>
- <input data-track-bogus="click_bogusinput" data-track-label="_label_" value="_value_"/>
- <input data-track-${term}="click_input3" data-track-experiment="example" value="_value_"/>
- <input data-track-${term}="event_with_extra" data-track-extra='{ "foo": "bar" }' />
- <input data-track-${term}="event_with_invalid_extra" data-track-extra="invalid_json" />
- `);
- });
-
- it(`binds to clicks on elements matching [data-track-${term}]`, () => {
- document.querySelector(`[data-track-${term}="click_input1"]`).click();
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', {
- label: '_label_',
- value: '0',
- });
- });
-
- it(`does not bind to clicks on elements without [data-track-${term}]`, () => {
- document.querySelector('[data-track-bogus="click_bogusinput"]').click();
-
- expect(eventSpy).not.toHaveBeenCalled();
- });
-
- it('allows value override with the data-track-value attribute', () => {
- document.querySelector(`[data-track-${term}="click_input2"]`).click();
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', {
- value: '0',
- });
- });
-
- it('handles checkbox values correctly', () => {
- const checkbox = document.querySelector(`[data-track-${term}="toggle_checkbox"]`);
-
- checkbox.click(); // unchecking
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
- value: 0,
- });
-
- checkbox.click(); // checking
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
- value: '1',
- });
- });
-
- it('handles bootstrap dropdowns', () => {
- const dropdown = document.querySelector(`[data-track-${term}="toggle_dropdown"]`);
-
- dropdown.dispatchEvent(new Event('show.bs.dropdown', { bubbles: true }));
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {});
-
- dropdown.dispatchEvent(new Event('hide.bs.dropdown', { bubbles: true }));
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {});
- });
-
- it('handles nested elements inside an element with tracking', () => {
- document.querySelector('span.nested').click();
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'nested_event', {});
- });
-
- it('includes experiment data if linked to an experiment', () => {
- const mockExperimentData = {
- variant: 'candidate',
- experiment: 'example',
- key: '2bff73f6bb8cc11156c50a8ba66b9b8b',
- };
- getExperimentData.mockReturnValue(mockExperimentData);
-
- document.querySelector(`[data-track-${term}="click_input3"]`).click();
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input3', {
- value: '_value_',
- context: { schema: TRACKING_CONTEXT_SCHEMA, data: mockExperimentData },
- });
- });
-
- it('supports extra data as JSON', () => {
- document.querySelector(`[data-track-${term}="event_with_extra"]`).click();
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'event_with_extra', {
- extra: { foo: 'bar' },
- });
- });
-
- it('ignores extra if provided JSON is invalid', () => {
- document.querySelector(`[data-track-${term}="event_with_invalid_extra"]`).click();
-
- expect(eventSpy).toHaveBeenCalledWith('_category_', 'event_with_invalid_extra', {});
- });
- });
-
- describe.each`
- term
- ${'event'}
- ${'action'}
- `('tracking page loaded events with -$term', ({ term }) => {
- let eventSpy;
-
- beforeEach(() => {
- eventSpy = jest.spyOn(Tracking, 'event');
- setHTMLFixture(`
- <div data-track-${term}="click_link" data-track-label="all_nested_links">
- <input data-track-${term}="render" data-track-label="label1" value=1 data-track-property="_property_"/>
- <span data-track-${term}="render" data-track-label="label2" data-track-value=1>
- <a href="#" id="link">Something</a>
- </span>
- <input data-track-${term}="_render_bogus_" data-track-label="label3" value="_value_" data-track-property="_property_"/>
- </div>
- `);
- Tracking.trackLoadEvents('_category_'); // only happens once
- });
-
- it(`sends tracking events when [data-track-${term}="render"] is on an element`, () => {
- expect(eventSpy.mock.calls).toEqual([
- [
- '_category_',
- 'render',
- {
- label: 'label1',
- value: '1',
- property: '_property_',
- },
- ],
- [
- '_category_',
- 'render',
- {
- label: 'label2',
- value: '1',
- },
- ],
- ]);
- });
-
- describe.each`
- event | actionSuffix
- ${'click'} | ${''}
- ${'show.bs.dropdown'} | ${'_show'}
- ${'hide.bs.dropdown'} | ${'_hide'}
- `(`auto-tracking $event events on nested elements`, ({ event, actionSuffix }) => {
- let link;
-
- beforeEach(() => {
- link = document.querySelector('#link');
- eventSpy.mockClear();
- });
-
- it(`avoids using ancestor [data-track-${term}="render"] tracking configurations`, () => {
- link.dispatchEvent(new Event(event, { bubbles: true }));
-
- expect(eventSpy).not.toHaveBeenCalledWith(
- '_category_',
- `render${actionSuffix}`,
- expect.any(Object),
- );
- expect(eventSpy).toHaveBeenCalledWith(
- '_category_',
- `click_link${actionSuffix}`,
- expect.objectContaining({ label: 'all_nested_links' }),
- );
- });
- });
- });
-
- describe('tracking mixin', () => {
- describe('trackingOptions', () => {
- it('returns the options defined on initialisation', () => {
- const mixin = Tracking.mixin({ foo: 'bar' });
- expect(mixin.computed.trackingOptions()).toEqual({ foo: 'bar' });
- });
-
- it('lets local tracking value override and extend options', () => {
- const mixin = Tracking.mixin({ foo: 'bar' });
- // The value of this in the Vue lifecyle is different, but this serves the test's purposes
- mixin.computed.tracking = { foo: 'baz', baz: 'bar' };
- expect(mixin.computed.trackingOptions()).toEqual({ foo: 'baz', baz: 'bar' });
- });
-
- it('includes experiment data if linked to an experiment', () => {
- const mockExperimentData = {
- variant: 'candidate',
- experiment: 'darkMode',
- };
- getExperimentData.mockReturnValue(mockExperimentData);
-
- const mixin = Tracking.mixin({ foo: 'bar', experiment: 'darkMode' });
- expect(mixin.computed.trackingOptions()).toEqual({
- foo: 'bar',
- context: {
- schema: TRACKING_CONTEXT_SCHEMA,
- data: mockExperimentData,
- },
- });
- });
-
- it('does not include experiment data if experiment data does not exist', () => {
- const mixin = Tracking.mixin({ foo: 'bar', experiment: 'lightMode' });
- expect(mixin.computed.trackingOptions()).toEqual({
- foo: 'bar',
- });
- });
- });
-
- describe('trackingCategory', () => {
- it('returns the category set in the component properties first', () => {
- const mixin = Tracking.mixin({ category: 'foo' });
- mixin.computed.tracking = {
- category: 'bar',
- };
- expect(mixin.computed.trackingCategory()).toBe('bar');
- });
-
- it('returns the category set in the options', () => {
- const mixin = Tracking.mixin({ category: 'foo' });
- expect(mixin.computed.trackingCategory()).toBe('foo');
- });
-
- it('returns undefined if no category is selected', () => {
- const mixin = Tracking.mixin();
- expect(mixin.computed.trackingCategory()).toBe(undefined);
- });
- });
-
- describe('track', () => {
- let eventSpy;
- let mixin;
-
- beforeEach(() => {
- eventSpy = jest.spyOn(Tracking, 'event').mockReturnValue();
- mixin = Tracking.mixin();
- mixin = {
- ...mixin.computed,
- ...mixin.methods,
- };
- });
-
- it('calls the event method with no category or action defined', () => {
- mixin.trackingCategory = mixin.trackingCategory();
- mixin.trackingOptions = mixin.trackingOptions();
-
- mixin.track();
- expect(eventSpy).toHaveBeenCalledWith(undefined, undefined, {});
- });
-
- it('calls the event method', () => {
- mixin.trackingCategory = mixin.trackingCategory();
- mixin.trackingOptions = mixin.trackingOptions();
-
- mixin.track('foo');
- expect(eventSpy).toHaveBeenCalledWith(undefined, 'foo', {});
- });
-
- it('gives precedence to data for category and options', () => {
- mixin.trackingCategory = mixin.trackingCategory();
- mixin.trackingOptions = mixin.trackingOptions();
- const data = { category: 'foo', label: 'baz' };
- mixin.track('foo', data);
- expect(eventSpy).toHaveBeenCalledWith('foo', 'foo', data);
- });
- });
- });
-});