@clearbit-dcp/clearbit.js-core
Version:
The hassle-free way to integrate analytics into any web application.
1,491 lines (1,292 loc) • 47.3 kB
JavaScript
;
var Analytics = require('../lib').constructor;
var Facade = require('segmentio-facade');
var analytics = require('../lib');
var assert = require('proclaim');
var bind = require('component-event').bind;
var createIntegration = require('@segment/analytics.js-integration');
var extend = require('@ndhoule/extend');
var type = require('component-type');
var jQuery = require('jquery');
var pageDefaults = require('../lib/pageDefaults');
var sinon = require('sinon');
var tick = require('next-tick');
var trigger = require('compat-trigger-event');
var Identify = Facade.Identify;
var cookie = Analytics.cookie;
var group = analytics.group();
var store = Analytics.store;
var user = analytics.user();
describe('Analytics', function() {
var analytics;
var contextPage;
var Test;
var settings;
beforeEach(function() {
settings = {
Test: {
key: 'key'
}
};
contextPage = pageDefaults();
});
beforeEach(function() {
analytics = new Analytics();
analytics.timeout(0);
Test = createIntegration('Test');
});
afterEach(function() {
user.reset();
group.reset();
user.anonymousId(null);
// clear the hash
// FIXME(ndhoule): Uhhh... causes Safari 9 to freak out. Maybe Karma issue?
// if (window.history && window.history.pushState) {
// window.history.pushState('', '', window.location.pathname);
// }
});
it('should setup an Integrations object', function() {
assert(type(analytics.Integrations) === 'object');
});
it('should setup an _integrations object', function() {
assert(type(analytics._integrations) === 'object');
});
it('should set a _readied state', function() {
assert(analytics._readied === false);
});
it('should set a default timeout', function() {
analytics = new Analytics();
assert(analytics._timeout === 300);
});
it('should set the _user for backwards compatibility', function() {
assert(analytics._user === user);
});
describe('#use', function() {
it('should work', function(done) {
analytics.use(function(singleton) {
assert(analytics === singleton);
done();
});
});
});
describe('#addIntegration', function() {
it('should add an integration', function() {
analytics.addIntegration(Test);
assert(analytics.Integrations.Test === Test);
});
});
describe('#setAnonymousId', function() {
it('should set the user\'s anonymous id', function() {
var prev = analytics.user().anonymousId();
assert(prev.length === 36);
analytics.setAnonymousId('new-id');
var curr = analytics.user().anonymousId();
assert(curr === 'new-id');
});
});
describe('#initialize', function() {
beforeEach(function() {
sinon.spy(user, 'load');
sinon.spy(group, 'load');
});
afterEach(function() {
user.load.restore();
group.load.restore();
});
it('should not error without settings', function() {
analytics.initialize();
});
it('should set options', function() {
analytics._options = sinon.spy();
analytics.initialize({}, { option: true });
assert(analytics._options.calledWith({ option: true }));
});
it('should reset analytics._readied to false', function() {
analytics.addIntegration(Test);
analytics._readied = true;
analytics.initialize(settings);
assert(!analytics._readied);
});
it('should add integration instance', function(done) {
Test.readyOnInitialize();
analytics.addIntegration(Test);
analytics.ready(done);
var test = new Test(settings.Test);
analytics.add(test);
analytics.initialize();
});
it('should set `.analytics` to self on integration', function(done) {
Test.readyOnInitialize();
analytics.addIntegration(Test);
analytics.ready(done);
var test = new Test(settings.Test);
analytics.add(test);
analytics.initialize();
assert(test.analytics === analytics);
});
it('should listen on integration ready events', function(done) {
Test.readyOnInitialize();
analytics.addIntegration(Test);
analytics.ready(done);
analytics.initialize(settings);
});
it('should still call ready with unknown integrations', function(done) {
analytics.ready(done);
analytics.initialize({ Unknown: { key: 'key' } });
});
it('should set analytics._readied to true', function(done) {
analytics.ready(function() {
assert(analytics._readied);
done();
});
analytics.initialize();
});
it('should call #load on the user', function() {
analytics.initialize();
assert(user.load.called);
});
it('should call #load on the group', function() {
analytics.initialize();
assert(group.load.called);
});
it('should store enabled integrations', function(done) {
Test.readyOnInitialize();
analytics.addIntegration(Test);
analytics.ready(function() {
assert(analytics._integrations.Test instanceof Test);
done();
});
analytics.initialize(settings);
});
it('should send settings to an integration', function(done) {
Test = function(options) {
assert.deepEqual(settings.Test, options);
done();
};
Test.prototype.name = 'Test';
Test.prototype.once = Test.prototype.initialize = function() {};
analytics.addIntegration(Test);
analytics.initialize(settings);
});
it('should parse the query string', function() {
sinon.stub(analytics, '_parseQuery');
analytics.initialize();
assert(analytics._parseQuery.called);
});
it('should set initialized state', function() {
analytics.initialize();
assert(analytics.initialized);
});
it('should emit initialize', function(done) {
analytics.once('initialize', function() {
done();
});
analytics.initialize();
});
});
describe('#ready', function() {
it('should push a handler on to the queue', function(done) {
analytics.ready(done);
analytics.emit('ready');
});
it('should callback on next tick when already ready', function(done) {
analytics.ready(function() {
var spy = sinon.spy();
analytics.ready(spy);
assert(!spy.called);
tick(function() {
assert(spy.called);
done();
});
});
analytics.initialize();
});
it('should emit ready', function(done) {
analytics.once('ready', done);
analytics.initialize();
});
it('should not error when passed a non-function', function() {
analytics.ready('callback');
});
});
describe('#_invoke', function() {
beforeEach(function(done) {
Test.readyOnInitialize();
Test.prototype.invoke = sinon.spy();
analytics.addIntegration(Test);
analytics.ready(done);
analytics.initialize(settings);
});
it('should invoke a method on integration with facade', function() {
var a = new Identify({ userId: 'id', traits: { trait: true } });
analytics._invoke('identify', a);
var b = Test.prototype.invoke.args[0][1];
assert(b === a);
assert(b.userId() === 'id');
assert(b.traits().trait === true);
});
it('shouldnt call a method when the `all` option is false', function() {
var opts = { providers: { all: false } };
var facade = new Facade({ options: opts });
analytics._invoke('identify', facade);
assert(!Test.prototype.invoke.called);
});
it('shouldnt call a method when the integration option is false', function() {
var opts = { providers: { Test: false } };
var facade = new Facade({ options: opts });
analytics._invoke('identify', facade);
assert(!Test.prototype.invoke.called);
});
it('should support .integrations to disable / select integrations', function() {
var opts = { integrations: { Test: false } };
analytics.identify('123', {}, opts);
assert(!Test.prototype.invoke.called);
});
it('should emit "invoke" with facade', function(done) {
var opts = { All: false };
var identify = new Identify({ options: opts });
analytics.on('invoke', function(msg) {
assert(msg === identify);
assert(msg.action() === 'identify');
done();
});
analytics._invoke('identify', identify);
});
});
describe('#_options', function() {
beforeEach(function() {
sinon.stub(cookie, 'options');
sinon.stub(store, 'options');
sinon.stub(user, 'options');
sinon.stub(group, 'options');
});
afterEach(function() {
cookie.options.restore();
store.options.restore();
user.options.restore();
group.options.restore();
});
it('should set cookie options', function() {
analytics._options({ cookie: { option: true } });
assert(cookie.options.calledWith({ option: true }));
});
it('should set store options', function() {
analytics._options({ localStorage: { option: true } });
assert(store.options.calledWith({ option: true }));
});
it('should set user options', function() {
analytics._options({ user: { option: true } });
assert(user.options.calledWith({ option: true }));
});
it('should set group options', function() {
analytics._options({ group: { option: true } });
assert(group.options.calledWith({ option: true }));
});
});
describe('#_parseQuery', function() {
describe('user settings', function() {
beforeEach(function() {
sinon.spy(analytics, 'identify');
});
it('should parse `cb_aid` and set anonymousId', function() {
sinon.spy(user, 'anonymousId');
analytics._parseQuery('?cb_aid=123');
assert(user.anonymousId.calledWith('123'));
});
it('should parse `cb_uid` and call identify', function() {
analytics._parseQuery('?cb_uid=123');
assert(analytics.identify.calledWith('123', {}));
});
it('should include traits in identify', function() {
analytics._parseQuery('?cb_uid=123&cb_trait_name=chris');
assert(analytics.identify.calledWith('123', { name: 'chris' }));
});
});
describe('events', function() {
beforeEach(function() {
sinon.spy(analytics, 'track');
});
it('should parse `cb_event` and call track', function() {
analytics._parseQuery('?cb_event=test');
assert(analytics.track.calledWith('test', {}));
});
it('should include properties in track', function() {
analytics._parseQuery('?cb_event=Started+Trial&cb_prop_plan=Silver');
assert(analytics.track.calledWith('Started Trial', { plan: 'Silver' }));
});
});
});
describe('#_timeout', function() {
it('should set the timeout for callbacks', function() {
analytics.timeout(500);
assert(analytics._timeout === 500);
});
});
describe('#_callback', function() {
it('should callback after a timeout', function(done) {
var spy = sinon.spy();
analytics._callback(spy);
assert(!spy.called);
tick(function() {
assert(spy.called);
done();
});
});
});
describe('#page', function() {
var head = document.getElementsByTagName('head')[0];
var defaults;
beforeEach(function() {
defaults = {
path: window.location.pathname,
referrer: document.referrer,
title: document.title,
url: window.location.href,
search: window.location.search
};
sinon.spy(analytics, '_invoke');
});
it('should call #_invoke', function() {
analytics.page();
assert(analytics._invoke.calledWith('page'));
});
it('should default .anonymousId', function() {
analytics.page();
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId().length === 36);
});
it('should override .anonymousId', function() {
analytics.page('category', 'name', {}, { anonymousId: 'anon-id' });
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId() === 'anon-id');
});
it('should call #_invoke with Page instance', function() {
analytics.page();
var page = analytics._invoke.args[0][1];
assert(page.action() === 'page');
});
it('should default .url to .location.href', function() {
analytics.page();
var page = analytics._invoke.args[0][1];
assert(page.properties().url === window.location.href);
});
it('should respect canonical', function() {
var el = document.createElement('link');
el.rel = 'canonical';
el.href = 'baz.com';
head.appendChild(el);
analytics.page();
var page = analytics._invoke.args[0][1];
assert(page.properties().url === 'baz.com' + window.location.search);
el.parentNode.removeChild(el);
});
it('should accept (category, name, properties, options, callback)', function(done) {
defaults.category = 'category';
defaults.name = 'name';
analytics.page('category', 'name', {}, {}, function() {
var page = analytics._invoke.args[0][1];
assert(page.category() === 'category');
assert(page.name() === 'name');
assert(typeof page.properties() === 'object');
assert(typeof page.options() === 'object');
done();
});
});
it('should accept (category, name, properties, callback)', function(done) {
defaults.category = 'category';
defaults.name = 'name';
analytics.page('category', 'name', {}, function() {
var page = analytics._invoke.args[0][1];
assert(page.category() === 'category');
assert(page.name() === 'name');
assert(typeof page.properties() === 'object');
done();
});
});
it('should accept (category, name, callback)', function(done) {
defaults.category = 'category';
defaults.name = 'name';
analytics.page('category', 'name', function() {
var page = analytics._invoke.args[0][1];
assert(page.category() === 'category');
assert(page.name() === 'name');
done();
});
});
it('should accept (name, properties, options, callback)', function(done) {
defaults.name = 'name';
analytics.page('name', {}, {}, function() {
var page = analytics._invoke.args[0][1];
assert(page.name() === 'name');
assert(typeof page.properties() === 'object');
assert(typeof page.options() === 'object');
done();
});
});
it('should accept (name, properties, callback)', function(done) {
defaults.name = 'name';
analytics.page('name', {}, function() {
var page = analytics._invoke.args[0][1];
assert(page.name() === 'name');
assert(typeof page.properties() === 'object');
done();
});
});
it('should accept (name, callback)', function(done) {
defaults.name = 'name';
analytics.page('name', function() {
var page = analytics._invoke.args[0][1];
assert(page.name() === 'name');
done();
});
});
it('should accept (properties, options, callback)', function(done) {
analytics.page({}, {}, function() {
var page = analytics._invoke.args[0][1];
assert(page.category() === null);
assert(page.name() === null);
assert(typeof page.properties() === 'object');
assert(typeof page.options() === 'object');
done();
});
});
it('should accept (properties, callback)', function(done) {
analytics.page({}, function() {
var page = analytics._invoke.args[0][1];
assert(page.category() === null);
assert(page.name() === null);
assert(typeof page.options() === 'object');
done();
});
});
it('should back properties with defaults', function() {
defaults.property = true;
analytics.page({ property: true });
var page = analytics._invoke.args[0][1];
assert.deepEqual(page.properties(), defaults);
});
it('should accept top level option .timestamp', function() {
var date = new Date();
analytics.page({ prop: true }, { timestamp: date });
var page = analytics._invoke.args[0][1];
assert.deepEqual(page.timestamp(), date);
});
it('should accept top level option .integrations', function() {
analytics.page({ prop: true }, { integrations: { AdRoll: { opt: true } } });
var page = analytics._invoke.args[0][1];
assert.deepEqual(page.options('AdRoll'), { opt: true });
});
it('should accept top level option .context', function() {
var app = { name: 'segment' };
analytics.page({ prop: true }, { context: { app: app } });
var page = analytics._invoke.args[0][1];
assert.deepEqual(app, page.obj.context.app);
});
it('should accept top level option .anonymousId', function() {
analytics.page({ prop: true }, { anonymousId: 'id' });
var page = analytics._invoke.args[0][1];
assert(page.obj.anonymousId === 'id');
});
it('should include context.page', function() {
analytics.page();
var page = analytics._invoke.args[0][1];
assert.deepEqual(page.context(), { page: defaults });
});
it('should accept context.traits', function() {
analytics.page({ prop: true }, { traits: { trait: true } });
var page = analytics._invoke.args[0][1];
assert.deepEqual(page.context(), {
page: defaults,
traits: { trait: true }
});
});
it('should emit page', function(done) {
analytics.once('page', function(category, name, props, opts) {
assert(category === 'category');
assert(name === 'name');
assert.deepEqual(opts, { context: { page: defaults } });
assert.deepEqual(props, extend(defaults, { category: 'category', name: 'name' }));
done();
});
analytics.page('category', 'name', {}, {});
});
});
describe('#pageview', function() {
beforeEach(function() {
analytics.initialize();
sinon.spy(analytics, 'page');
});
it('should call #page with a path', function() {
analytics.pageview('/path');
assert(analytics.page.calledWith({ path: '/path' }));
});
});
describe('#identify', function() {
beforeEach(function() {
sinon.spy(analytics, '_invoke');
sinon.spy(user, 'identify');
});
afterEach(function() {
user.identify.restore();
});
it('should call #_invoke', function() {
analytics.identify();
assert(analytics._invoke.calledWith('identify'));
});
it('should default .anonymousId', function() {
analytics.identify('user-id');
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId().length === 36);
});
it('should override .anonymousId', function() {
analytics.identify('user-id', {}, { anonymousId: 'anon-id' });
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId() === 'anon-id');
});
it('should call #_invoke with Identify', function() {
analytics.identify();
var identify = analytics._invoke.getCall(0).args[1];
assert(identify.action() === 'identify');
});
it('should accept (id, traits, options, callback)', function(done) {
analytics.identify('id', {}, {}, function() {
var identify = analytics._invoke.getCall(0).args[1];
assert(identify.userId() === 'id');
assert(typeof identify.traits() === 'object');
assert(typeof identify.options() === 'object');
done();
});
});
it('should accept (id, traits, callback)', function(done) {
analytics.identify('id', { trait: true }, function() {
var identify = analytics._invoke.getCall(0).args[1];
assert(identify.userId() === 'id');
assert(typeof identify.traits() === 'object');
done();
});
});
it('should accept (id, callback)', function(done) {
analytics.identify('id', function() {
var identify = analytics._invoke.getCall(0).args[1];
assert(identify.action() === 'identify');
assert(identify.userId() === 'id');
done();
});
});
it('should accept (traits, options, callback)', function(done) {
analytics.identify({}, {}, function() {
var identify = analytics._invoke.getCall(0).args[1];
assert(typeof identify.traits() === 'object');
assert(typeof identify.options() === 'object');
done();
});
});
it('should accept (traits, callback)', function(done) {
analytics.identify({}, function() {
var identify = analytics._invoke.getCall(0).args[1];
assert(typeof identify.traits() === 'object');
done();
});
});
it('should identify the user', function() {
analytics.identify('id', { trait: true });
assert(user.identify.calledWith('id', { trait: true }));
});
it('should back traits with stored traits', function() {
user.traits({ one: 1 });
user.save();
analytics.identify('id', { two: 2 });
var call = analytics._invoke.getCall(0);
var identify = call.args[1];
assert(call.args[0] === 'identify');
assert(identify.userId() === 'id');
assert(identify.traits().one === 1);
assert(identify.traits().two === 2);
});
it('should emit identify', function(done) {
analytics.once('identify', function(id, traits, options) {
assert(id === 'id');
assert.deepEqual(traits, { a: 1 });
assert.deepEqual(options, { b: 2 });
done();
});
analytics.identify('id', { a: 1 }, { b: 2 });
});
it('should parse a created string into a date', function() {
var date = new Date();
var string = date.getTime().toString();
analytics.identify({ created: string });
var created = analytics._invoke.args[0][1].created();
assert(type(created) === 'date');
assert(created.getTime() === date.getTime());
});
it('should parse created milliseconds into a date', function() {
var date = new Date();
var milliseconds = date.getTime();
analytics.identify({ created: milliseconds });
var created = analytics._invoke.args[0][1].created();
assert(type(created) === 'date');
assert(created.getTime() === milliseconds);
});
it('should parse created seconds into a date', function() {
var date = new Date();
var seconds = Math.floor(date.getTime() / 1000);
analytics.identify({ created: seconds });
var identify = analytics._invoke.args[0][1];
var created = identify.created();
assert(type(created) === 'date');
assert(created.getTime() === seconds * 1000);
});
it('should parse a company created string into a date', function() {
var date = new Date();
var string = date.getTime() + '';
analytics.identify({ company: { created: string } });
var identify = analytics._invoke.args[0][1];
var created = identify.companyCreated();
assert(type(created) === 'date');
assert(created.getTime() === date.getTime());
});
it('should parse company created milliseconds into a date', function() {
var date = new Date();
var milliseconds = date.getTime();
analytics.identify({ company: { created: milliseconds } });
var identify = analytics._invoke.args[0][1];
var created = identify.companyCreated();
assert(type(created) === 'date');
assert(created.getTime() === milliseconds);
});
it('should parse company created seconds into a date', function() {
var date = new Date();
var seconds = Math.floor(date.getTime() / 1000);
analytics.identify({ company: { created: seconds } });
var identify = analytics._invoke.args[0][1];
var created = identify.companyCreated();
assert(type(created) === 'date');
assert(created.getTime() === seconds * 1000);
});
it('should accept top level option .timestamp', function() {
var date = new Date();
analytics.identify(1, { trait: true }, { timestamp: date });
var identify = analytics._invoke.args[0][1];
assert.deepEqual(identify.timestamp(), date);
});
it('should accept top level option .integrations', function() {
analytics.identify(1, { trait: true }, { integrations: { AdRoll: { opt: true } } });
var identify = analytics._invoke.args[0][1];
assert.deepEqual({ opt: true }, identify.options('AdRoll'));
});
it('should accept top level option .context', function() {
analytics.identify(1, { trait: true }, { context: { app: { name: 'segment' } } });
var identify = analytics._invoke.args[0][1];
assert.deepEqual(identify.obj.context.app, { name: 'segment' });
});
it('should include context.page', function() {
analytics.identify(1);
var identify = analytics._invoke.args[0][1];
assert.deepEqual(identify.context(), { page: contextPage });
});
it('should accept context.traits', function() {
analytics.identify(1, { trait: 1 }, { traits: { trait: true } });
var identify = analytics._invoke.args[0][1];
assert.deepEqual(identify.traits(), { trait: 1, id: 1 });
assert.deepEqual(identify.context(), {
page: contextPage,
traits: { trait: true }
});
});
});
describe('#user', function() {
it('should return the user singleton', function() {
assert(analytics.user() === user);
});
});
describe('#group', function() {
beforeEach(function() {
sinon.spy(analytics, '_invoke');
sinon.spy(group, 'identify');
});
afterEach(function() {
group.identify.restore();
});
it('should return the group singleton', function() {
assert(analytics.group() === group);
});
it('should call #_invoke', function() {
analytics.group('id');
assert(analytics._invoke.calledWith('group'));
});
it('should default .anonymousId', function() {
analytics.group('group-id');
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId().length === 36);
});
it('should override .anonymousId', function() {
analytics.group('group-id', {}, { anonymousId: 'anon-id' });
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId() === 'anon-id');
});
it('should call #_invoke with group facade instance', function() {
analytics.group('id');
var group = analytics._invoke.args[0][1];
assert(group.action() === 'group');
});
it('should accept (id, properties, options, callback)', function(done) {
analytics.group('id', {}, {}, function() {
var group = analytics._invoke.args[0][1];
assert(group.groupId() === 'id');
assert(typeof group.properties() === 'object');
assert(typeof group.options() === 'object');
done();
});
});
it('should accept (id, properties, callback)', function(done) {
analytics.group('id', {}, function() {
var group = analytics._invoke.args[0][1];
assert(group.groupId() === 'id');
assert(typeof group.properties() === 'object');
done();
});
});
it('should accept (id, callback)', function(done) {
analytics.group('id', function() {
var group = analytics._invoke.args[0][1];
assert(group.groupId() === 'id');
done();
});
});
it('should accept (properties, options, callback)', function(done) {
analytics.group({}, {}, function() {
var group = analytics._invoke.args[0][1];
assert(typeof group.properties() === 'object');
assert(typeof group.options() === 'object');
done();
});
});
it('should accept (properties, callback)', function(done) {
analytics.group({}, function() {
var group = analytics._invoke.args[0][1];
assert(typeof group.properties() === 'object');
done();
});
});
it('should call #identify on the group', function() {
analytics.group('id', { property: true });
assert(group.identify.calledWith('id', { property: true }));
});
it('should back properties with stored properties', function() {
group.properties({ one: 1 });
group.save();
analytics.group('id', { two: 2 });
var g = analytics._invoke.args[0][1];
assert(g.groupId() === 'id');
assert(typeof g.properties() === 'object');
assert(g.properties().one === 1);
assert(g.properties().two === 2);
});
it('should emit group', function(done) {
analytics.once('group', function(groupId, traits, options) {
assert(groupId === 'id');
assert.deepEqual(traits, { a: 1 });
assert.deepEqual(options, { b: 2 });
done();
});
analytics.group('id', { a: 1 }, { b: 2 });
});
it('should parse a created string into a date', function() {
var date = new Date();
var string = date.getTime().toString();
analytics.group({ created: string });
var g = analytics._invoke.args[0][1];
var created = g.created();
assert(type(created) === 'date');
assert(created.getTime() === date.getTime());
});
it('should parse created milliseconds into a date', function() {
var date = new Date();
var milliseconds = date.getTime();
analytics.group({ created: milliseconds });
var g = analytics._invoke.args[0][1];
var created = g.created();
assert(type(created) === 'date');
assert(created.getTime() === milliseconds);
});
it('should parse created seconds into a date', function() {
var date = new Date();
var seconds = Math.floor(date.getTime() / 1000);
analytics.group({ created: seconds });
var g = analytics._invoke.args[0][1];
var created = g.created();
assert(type(created) === 'date');
assert(created.getTime() === seconds * 1000);
});
it('should accept top level option .timestamp', function() {
var date = new Date();
analytics.group(1, { trait: true }, { timestamp: date });
var group = analytics._invoke.args[0][1];
assert.deepEqual(group.timestamp(), date);
});
it('should accept top level option .integrations', function() {
analytics.group(1, { trait: true }, { integrations: { AdRoll: { opt: true } } });
var group = analytics._invoke.args[0][1];
assert.deepEqual(group.options('AdRoll'), { opt: true });
});
it('should accept top level option .context', function() {
var app = { name: 'segment' };
analytics.group(1, { trait: true }, { context: { app: app } });
var group = analytics._invoke.args[0][1];
assert.deepEqual(group.obj.context.app, app);
});
it('should include context.page', function() {
analytics.group(1);
var group = analytics._invoke.args[0][1];
assert.deepEqual(group.context(), { page: contextPage });
});
it('should accept context.traits', function() {
analytics.group(1, { trait: 1 }, { traits: { trait: true } });
var group = analytics._invoke.args[0][1];
assert.deepEqual(group.traits(), { trait: 1, id: 1 });
assert.deepEqual(group.context(), {
page: contextPage,
traits: { trait: true }
});
});
});
describe('#track', function() {
beforeEach(function() {
sinon.spy(analytics, '_invoke');
});
it('should call #_invoke', function() {
analytics.track();
assert(analytics._invoke.calledWith('track'));
});
it('should default .anonymousId', function() {
analytics.track();
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId().length === 36);
});
it('should override .anonymousId', function() {
analytics.track('event', {}, { anonymousId: 'anon-id' });
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId() === 'anon-id');
});
it('should transform arguments into Track', function() {
analytics.track();
var track = analytics._invoke.getCall(0).args[1];
assert(track.action() === 'track');
});
it('should accept (event, properties, options, callback)', function(done) {
analytics.track('event', {}, {}, function() {
var track = analytics._invoke.args[0][1];
assert(track.event() === 'event');
assert(typeof track.properties() === 'object');
assert(typeof track.options() === 'object');
done();
});
});
it('should accept (event, properties, callback)', function(done) {
analytics.track('event', {}, function() {
var track = analytics._invoke.args[0][1];
assert(track.event() === 'event');
assert(typeof track.properties() === 'object');
done();
});
});
it('should accept (event, callback)', function(done) {
analytics.track('event', function() {
var track = analytics._invoke.args[0][1];
assert(track.event() === 'event');
done();
});
});
it('should emit track', function(done) {
analytics.once('track', function(event, properties, options) {
assert(event === 'event');
assert.deepEqual(properties, { a: 1 });
assert.deepEqual(options, { b: 2 });
done();
});
analytics.track('event', { a: 1 }, { b: 2 });
});
it('should safely convert ISO dates to date objects', function() {
var date = new Date(Date.UTC(2013, 9, 5));
analytics.track('event', {
date: '2013-10-05T00:00:00.000Z',
nonDate: '2013'
});
var track = analytics._invoke.args[0][1];
assert(track.properties().date.getTime() === date.getTime());
assert(track.properties().nonDate === '2013');
});
it('should accept top level option .timestamp', function() {
var date = new Date();
analytics.track('event', { prop: true }, { timestamp: date });
var track = analytics._invoke.args[0][1];
assert.deepEqual(date, track.timestamp());
});
it('should accept top level option .integrations', function() {
analytics.track('event', { prop: true }, { integrations: { AdRoll: { opt: true } } });
var track = analytics._invoke.args[0][1];
assert.deepEqual({ opt: true }, track.options('AdRoll'));
});
it('should accept top level option .context', function() {
var app = { name: 'segment' };
analytics.track('event', { prop: true }, { context: { app: app } });
var track = analytics._invoke.args[0][1];
assert.deepEqual(app, track.obj.context.app);
});
it('should not call #_invoke if the event is disabled', function() {
analytics.options.plan = {
track: {
event: { enabled: false }
}
};
analytics.track('event');
assert(!analytics._invoke.called);
});
it('should call #_invoke if the event is enabled', function() {
analytics.options.plan = {
track: {
event: { enabled: true }
}
};
analytics.track('event');
assert(analytics._invoke.called);
});
it('should call the callback even if the event is disabled', function(done) {
analytics.options.plan = {
track: {
event: { enabled: false }
}
};
assert(!analytics._invoke.called);
analytics.track('event', {}, {}, function() {
done();
});
});
it('should default .integrations to plan.integrations', function() {
analytics.options.plan = {
track: {
event: {
integrations: { All: true }
}
}
};
analytics.track('event', {}, { integrations: { Segment: true } });
var msg = analytics._invoke.args[0][1];
assert(msg.event() === 'event');
assert.deepEqual(msg.integrations(), { All: true, Segment: true });
});
it('should not set ctx.integrations if plan.integrations is empty', function() {
analytics.options.plan = { track: { event: {} } };
analytics.track('event', {}, { campaign: {} });
var msg = analytics._invoke.args[0][1];
assert.deepEqual({}, msg.proxy('context.campaign'));
});
it('should include context.page', function() {
analytics.track('event');
var track = analytics._invoke.args[0][1];
assert.deepEqual(track.context(), { page: contextPage });
});
it('should accept context.traits', function() {
analytics.track('event', { prop: 1 }, { traits: { trait: true } });
var track = analytics._invoke.args[0][1];
assert.deepEqual(track.properties(), { prop: 1 });
assert.deepEqual(track.context(), {
page: contextPage,
traits: { trait: true }
});
});
});
describe('#trackLink', function() {
var link;
var wrap;
var svg;
beforeEach(function() {
// FIXME: IE8 doesn't have createElementNS.
if (!document.createElementNS) return;
wrap = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg = document.createElementNS('http://www.w3.org/2000/svg', 'a');
wrap.appendChild(svg);
document.body.appendChild(wrap);
});
beforeEach(function() {
sinon.spy(analytics, 'track');
link = document.createElement('a');
link.href = '#';
document.body.appendChild(link);
});
afterEach(function() {
window.location.hash = '';
if (wrap) document.body.removeChild(wrap);
document.body.removeChild(link);
});
it('should trigger a track on an element click', function() {
analytics.trackLink(link);
trigger(link, 'click');
assert(analytics.track.called);
});
it('should accept a jquery object for an element', function() {
var $link = jQuery(link);
analytics.trackLink($link);
trigger(link, 'click');
assert(analytics.track.called);
});
it('should not accept a string for an element', function() {
assert['throws'](function() {
analytics.trackLink('a');
}, TypeError, 'Must pass HTMLElement to `analytics.trackLink`.');
trigger(link, 'click');
assert(!analytics.track.called);
});
it('should send an event and properties', function() {
analytics.trackLink(link, 'event', { property: true });
trigger(link, 'click');
assert(analytics.track.calledWith('event', { property: true }));
});
it('should accept an event function', function() {
function event(el) { return el.nodeName; }
analytics.trackLink(link, event);
trigger(link, 'click');
assert(analytics.track.calledWith('A'));
});
it('should accept a properties function', function() {
function properties(el) { return { type: el.nodeName }; }
analytics.trackLink(link, 'event', properties);
trigger(link, 'click');
assert(analytics.track.calledWith('event', { type: 'A' }));
});
it('should load an href on click', function(done) {
link.href = '#test';
analytics.trackLink(link);
trigger(link, 'click');
tick(function() {
assert(window.location.hash === '#test');
done();
});
});
it('should support svg .href attribute', function(done) {
if (!svg) return done();
// not correct svg, but should work.
svg.setAttribute('href', '#svg');
analytics.trackLink(svg);
trigger(svg, 'click');
tick(function() {
assert.equal(window.location.hash, '#svg');
done();
});
});
it('should fallback to getAttributeNS', function(done) {
if (!wrap) return done();
svg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#svg');
analytics.trackLink(svg);
trigger(svg, 'click');
tick(function() {
assert.equal(window.location.hash, '#svg');
done();
});
});
it('should support xlink:href', function(done) {
if (!wrap) return done();
svg.setAttribute('xlink:href', '#svg');
analytics.trackLink(svg);
trigger(svg, 'click');
tick(function() {
assert.equal(window.location.hash, '#svg');
done();
});
});
it('should not load an href for a link with a blank target', function(done) {
link.href = '/base/test/support/mock.html';
link.target = '_blank';
analytics.trackLink(link);
trigger(link, 'click');
tick(function() {
assert(window.location.hash !== '#test');
done();
});
});
});
describe('#trackForm', function() {
var form;
var submit;
before(function() {
window.jQuery = jQuery;
});
after(function() {
window.jQuery = null;
});
beforeEach(function() {
sinon.spy(analytics, 'track');
form = document.createElement('form');
form.action = '/base/test/support/mock.html';
form.target = '_blank';
submit = document.createElement('input');
submit.type = 'submit';
form.appendChild(submit);
document.body.appendChild(form);
});
afterEach(function() {
window.location.hash = '';
document.body.removeChild(form);
});
it('should trigger a track on a form submit', function() {
analytics.trackForm(form);
submit.click();
assert(analytics.track.called);
});
it('should accept a jquery object for an element', function() {
analytics.trackForm(form);
submit.click();
assert(analytics.track.called);
});
it('should not accept a string for an element', function() {
var str = 'form';
assert['throws'](function() {
analytics.trackForm(str);
}, TypeError, 'Must pass HTMLElement to `analytics.trackForm`.');
submit.click();
assert(!analytics.track.called);
});
it('should send an event and properties', function() {
analytics.trackForm(form, 'event', { property: true });
submit.click();
assert(analytics.track.calledWith('event', { property: true }));
});
it('should accept an event function', function() {
function event() { return 'event'; }
analytics.trackForm(form, event);
submit.click();
assert(analytics.track.calledWith('event'));
});
it('should accept a properties function', function() {
function properties() { return { property: true }; }
analytics.trackForm(form, 'event', properties);
submit.click();
assert(analytics.track.calledWith('event', { property: true }));
});
it('should call submit after a timeout', function(done) {
var spy = form.submit = sinon.spy();
analytics.trackForm(form);
submit.click();
setTimeout(function() {
assert(spy.called);
done();
}, 50);
});
it('should trigger an existing submit handler', function(done) {
bind(form, 'submit', function() { done(); });
analytics.trackForm(form);
submit.click();
});
it('should trigger an existing jquery submit handler', function(done) {
var $form = jQuery(form);
$form.submit(function() { done(); });
analytics.trackForm(form);
submit.click();
});
it('should track on a form submitted via jquery', function() {
var $form = jQuery(form);
analytics.trackForm(form);
$form.submit();
assert(analytics.track.called);
});
it('should trigger an existing jquery submit handler on a form submitted via jquery', function(done) {
var $form = jQuery(form);
$form.submit(function() { done(); });
analytics.trackForm(form);
$form.submit();
});
});
describe('#alias', function() {
beforeEach(function() {
sinon.spy(analytics, '_invoke');
});
it('should call #_invoke', function() {
analytics.alias();
assert(analytics._invoke.calledWith('alias'));
});
it('should call #_invoke with instanceof Alias', function() {
analytics.alias();
var alias = analytics._invoke.args[0][1];
assert(alias.action() === 'alias');
});
it('should default .anonymousId', function() {
analytics.alias('previous-id', 'user-id');
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId().length === 36);
});
it('should override .anonymousId', function() {
analytics.alias('previous-id', 'user-id', { anonymousId: 'anon-id' });
var msg = analytics._invoke.args[0][1];
assert(msg.anonymousId() === 'anon-id');
});
it('should accept (new, old, options, callback)', function(done) {
analytics.alias('new', 'old', {}, function() {
var alias = analytics._invoke.args[0][1];
assert(alias.from() === 'old');
assert(alias.to() === 'new');
assert(typeof alias.options() === 'object');
done();
});
});
it('should accept (new, old, callback)', function(done) {
analytics.alias('new', 'old', function() {
var alias = analytics._invoke.args[0][1];
assert(alias.from() === 'old');
assert(alias.to() === 'new');
assert(typeof alias.options() === 'object');
done();
});
});
it('should accept (new, callback)', function(done) {
analytics.alias('new', function() {
var alias = analytics._invoke.args[0][1];
assert(alias.to() === 'new');
assert(typeof alias.options() === 'object');
done();
});
});
it('should include context.page', function() {
analytics.alias();
var alias = analytics._invoke.args[0][1];
assert.deepEqual(alias.context(), { page: contextPage });
});
it('should emit alias', function(done) {
analytics.once('alias', function(newId, oldId, options) {
assert(newId === 'new');
assert(oldId === 'old');
assert.deepEqual(options, { opt: true });
done();
});
analytics.alias('new', 'old', { opt: true });
});
});
describe('#push', function() {
beforeEach(function() {
analytics.track = sinon.spy();
});
it('should call methods with args', function() {
analytics.push(['track', 'event', { prop: true }]);
assert(analytics.track.calledWith('event', { prop: true }));
});
});
describe('#reset', function() {
beforeEach(function() {
user.id('user-id');
user.traits({ name: 'John Doe' });
group.id('group-id');
group.traits({ name: 'Example' });
});
it('should remove persisted group and user', function() {
assert(user.id() === 'user-id');
assert(user.traits().name === 'John Doe');
assert(group.id() === 'group-id');
assert(group.traits().name === 'Example');
analytics.reset();
assert(user.id() === null);
assert.deepEqual({}, user.traits());
assert(group.id() === null);
assert.deepEqual({}, group.traits());
});
});
});