dlest
Version:
Jest for your data layer - test runner for analytics tracking implementations
256 lines (230 loc) • 6.69 kB
JavaScript
const { toHaveEvent } = require('./toHaveEvent');
const { toHaveEventData } = require('./toHaveEventData');
const { matcherHint, printReceived, printExpected } = require('jest-matcher-utils');
/**
* toHaveEventCount Matcher
*
* Validates the count of specific events in dataLayer
* Usage: expect(dataLayer).toHaveEventCount('page_view', 1)
*/
async function toHaveEventCount(received, eventName, expectedCount) {
const isNot = this.isNot || false;
const hint = matcherHint('toHaveEventCount', 'dataLayer', 'eventName, count', {
isNot,
promise: this.promise,
});
if (!received || typeof received.getEvents !== 'function') {
throw new Error(
hint + '\n\n' +
'Received value must be a dataLayer object from DLest test context.\n' +
`Received: ${printReceived(received)}`
);
}
const events = await received.getEvents();
const matchingEvents = events.filter(event => {
return event.event === eventName ||
event.eventName === eventName ||
event.name === eventName;
});
const actualCount = matchingEvents.length;
const pass = actualCount === expectedCount;
if (pass) {
return {
message: () =>
hint + '\n\n' +
`Expected dataLayer not to have ${printExpected(expectedCount)} occurrence(s) of event: ${printExpected(eventName)}\n` +
`But found exactly ${actualCount} occurrence(s).`,
pass: true,
};
} else {
return {
message: () =>
hint + '\n\n' +
`Expected dataLayer to have ${printExpected(expectedCount)} occurrence(s) of event: ${printExpected(eventName)}\n` +
`But found ${printReceived(actualCount)} occurrence(s).\n\n` +
`Available events: ${events.map(e => e.event || e.eventName || e.name || 'unnamed').join(', ') || 'none'}`,
pass: false,
};
}
}
/**
* toHaveEventSequence Matcher
*
* Validates a sequence of events in dataLayer
* Usage: expect(dataLayer).toHaveEventSequence(['page_view', 'add_to_cart', 'purchase'])
*/
async function toHaveEventSequence(received, expectedSequence) {
const isNot = this.isNot || false;
const hint = matcherHint('toHaveEventSequence', 'dataLayer', 'eventSequence', {
isNot,
promise: this.promise,
});
if (!received || typeof received.getEvents !== 'function') {
throw new Error(
hint + '\n\n' +
'Received value must be a dataLayer object from DLest test context.\n' +
`Received: ${printReceived(received)}`
);
}
const events = await received.getEvents();
const eventNames = events.map(event =>
event.event || event.eventName || event.name || 'unnamed'
);
// Find the sequence in the events array
const sequenceFound = expectedSequence.every((expectedEvent, index) => {
const startIndex = eventNames.indexOf(expectedEvent);
if (startIndex === -1) return false;
// Check if the rest of the sequence follows
for (let i = 0; i < expectedSequence.length; i++) {
if (eventNames[startIndex + i] !== expectedSequence[i]) {
return false;
}
}
return true;
});
const pass = sequenceFound;
if (pass) {
return {
message: () =>
hint + '\n\n' +
`Expected dataLayer not to have event sequence: ${printExpected(expectedSequence)}\n` +
`But the sequence was found in events: [${eventNames.join(', ')}]`,
pass: true,
};
} else {
return {
message: () =>
hint + '\n\n' +
`Expected dataLayer to have event sequence: ${printExpected(expectedSequence)}\n` +
`But the sequence was not found.\n\n` +
`Actual event sequence: [${eventNames.join(', ') || 'none'}]`,
pass: false,
};
}
}
/**
* toBeDefined Matcher
*
* Basic Jest-like matcher for checking if value is defined
* Usage: expect(value).toBeDefined()
*/
function toBeDefined(received) {
const isNot = this.isNot || false;
const hint = matcherHint('toBeDefined', 'received', '', {
isNot,
});
const pass = received !== undefined && received !== null;
if (pass) {
return {
message: () =>
hint + '\n\n' +
`Expected value not to be defined, but received: ${printReceived(received)}`,
pass: true,
};
} else {
return {
message: () =>
hint + '\n\n' +
`Expected value to be defined, but received: ${printReceived(received)}`,
pass: false,
};
}
}
/**
* toBeUndefined Matcher
*
* Basic Jest-like matcher for checking if value is undefined
* Usage: expect(value).toBeUndefined()
*/
function toBeUndefined(received) {
const isNot = this.isNot || false;
const hint = matcherHint('toBeUndefined', 'received', '', {
isNot,
});
const pass = received === undefined;
if (pass) {
return {
message: () =>
hint + '\n\n' +
`Expected value not to be undefined, but received: ${printReceived(received)}`,
pass: true,
};
} else {
return {
message: () =>
hint + '\n\n' +
`Expected value to be undefined, but received: ${printReceived(received)}`,
pass: false,
};
}
}
/**
* toBeTruthy Matcher
*
* Basic Jest-like matcher for checking if value is truthy
* Usage: expect(value).toBeTruthy()
*/
function toBeTruthy(received) {
const isNot = this.isNot || false;
const hint = matcherHint('toBeTruthy', 'received', '', {
isNot,
});
const pass = Boolean(received);
if (pass) {
return {
message: () =>
hint + '\n\n' +
`Expected value not to be truthy, but received: ${printReceived(received)}`,
pass: true,
};
} else {
return {
message: () =>
hint + '\n\n' +
`Expected value to be truthy, but received: ${printReceived(received)}`,
pass: false,
};
}
}
/**
* toBeFalsy Matcher
*
* Basic Jest-like matcher for checking if value is falsy
* Usage: expect(value).toBeFalsy()
*/
function toBeFalsy(received) {
const isNot = this.isNot || false;
const hint = matcherHint('toBeFalsy', 'received', '', {
isNot,
});
const pass = !Boolean(received);
if (pass) {
return {
message: () =>
hint + '\n\n' +
`Expected value not to be falsy, but received: ${printReceived(received)}`,
pass: true,
};
} else {
return {
message: () =>
hint + '\n\n' +
`Expected value to be falsy, but received: ${printReceived(received)}`,
pass: false,
};
}
}
/**
* All DLest custom matchers
*/
const matchers = {
toHaveEvent,
toHaveEventData,
toHaveEventCount,
toHaveEventSequence,
toBeDefined,
toBeUndefined,
toBeTruthy,
toBeFalsy,
};
module.exports = matchers;