UNPKG

dlest

Version:

Jest for your data layer - test runner for analytics tracking implementations

144 lines (125 loc) 4.74 kB
const { matcherHint, printReceived, printExpected } = require('jest-matcher-utils'); /** * toHaveEvent Matcher * * Validates that dataLayer contains specific events * Usage: expect(dataLayer).toHaveEvent('purchase', { value: 99.90 }) */ async function toHaveEvent(received, eventName, eventData) { const isNot = this.isNot || false; const hint = matcherHint('toHaveEvent', 'dataLayer', 'eventName, eventData', { isNot, promise: this.promise, }); // Validate received is a dataLayer proxy 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)}` ); } // Get all captured events const events = await received.getEvents(); // Find events matching the name const matchingEvents = events.filter(event => { return event.event === eventName || event.eventName === eventName || event.name === eventName; }); const hasMatchingEvent = matchingEvents.length > 0; // If no eventData specified, just check event name if (!eventData) { const pass = hasMatchingEvent; if (pass) { return { message: () => hint + '\n\n' + `Expected dataLayer not to have event: ${printExpected(eventName)}\n` + `But found ${matchingEvents.length} occurrence(s):\n` + matchingEvents.map(event => printReceived(event)).join('\n'), pass: true, }; } else { return { message: () => hint + '\n\n' + `Expected dataLayer to have event: ${printExpected(eventName)}\n` + `But event was not found.\n\n` + `Available events: ${events.map(e => e.event || e.eventName || e.name || 'unnamed').join(', ') || 'none'}`, pass: false, }; } } // Check if any matching event has the expected data const hasMatchingData = matchingEvents.some(event => { return Object.keys(eventData).every(key => { const expectedValue = eventData[key]; const actualValue = event[key]; // Handle special Jest matchers if (expectedValue && typeof expectedValue === 'object' && expectedValue.asymmetricMatch) { return expectedValue.asymmetricMatch(actualValue); } // Handle deep object comparison if (typeof expectedValue === 'object' && expectedValue !== null) { if (typeof actualValue !== 'object' || actualValue === null) { return false; } return JSON.stringify(actualValue) === JSON.stringify(expectedValue); } return actualValue === expectedValue; }); }); const pass = hasMatchingEvent && hasMatchingData; if (pass) { const matchingEvent = matchingEvents.find(event => { return Object.keys(eventData).every(key => { const expectedValue = eventData[key]; const actualValue = event[key]; if (expectedValue && typeof expectedValue === 'object' && expectedValue.asymmetricMatch) { return expectedValue.asymmetricMatch(actualValue); } if (typeof expectedValue === 'object' && expectedValue !== null) { if (typeof actualValue !== 'object' || actualValue === null) { return false; } return JSON.stringify(actualValue) === JSON.stringify(expectedValue); } return actualValue === expectedValue; }); }); return { message: () => hint + '\n\n' + `Expected dataLayer not to have event: ${printExpected(eventName)}\n` + `With data: ${printExpected(eventData)}\n` + `But found matching event:\n${printReceived(matchingEvent)}`, pass: true, }; } else { if (!hasMatchingEvent) { return { message: () => hint + '\n\n' + `Expected dataLayer to have event: ${printExpected(eventName)}\n` + `With data: ${printExpected(eventData)}\n` + `But event was not found.\n\n` + `Available events: ${events.map(e => e.event || e.eventName || e.name || 'unnamed').join(', ') || 'none'}`, pass: false, }; } else { return { message: () => hint + '\n\n' + `Expected dataLayer to have event: ${printExpected(eventName)}\n` + `With data: ${printExpected(eventData)}\n` + `Found ${matchingEvents.length} event(s) with name '${eventName}' but none matched the expected data:\n\n` + matchingEvents.map((event, index) => `Event ${index + 1}:\n${printReceived(event)}` ).join('\n\n'), pass: false, }; } } } module.exports = { toHaveEvent };