Skip to content

Instantly share code, notes, and snippets.

@erikakers
Last active November 20, 2025 17:15
Show Gist options
  • Select an option

  • Save erikakers/67c4341324c9131906dc93eb81fa0213 to your computer and use it in GitHub Desktop.

Select an option

Save erikakers/67c4341324c9131906dc93eb81fa0213 to your computer and use it in GitHub Desktop.
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { faker } from '@faker-js/faker';
import UriDecoder from '../utils/uri-decoder';
// Mock dependencies
vi.mock('../utils/uri-decoder');
describe('click.ts', () => {
let mockPublish: any;
let mockSubscribe: any;
let mockLogError: any;
let analyticsEventHandler: any;
let trackButtonClick: any;
beforeEach(async () => {
vi.resetModules(); // Ensure fresh module for each test to capture side-effects if needed
mockPublish = vi.fn();
mockSubscribe = vi.fn();
mockLogError = vi.fn();
window.phoenix = {
bus: {
publish: mockPublish,
subscribe: mockSubscribe,
},
log: {
error: mockLogError,
},
};
const originalAddEventListener = document.addEventListener;
document.addEventListener = vi.fn((event, handler) => {
if (event === 'click') {
// We can choose to not attach it to avoid side effects, or attach it.
} else {
originalAddEventListener.call(document, event, handler);
}
});
// Dynamic import to ensure window.phoenix is ready
const module = await import('./click');
analyticsEventHandler = module.analyticsEventHandler;
trackButtonClick = module.trackButtonClick;
vi.clearAllMocks();
document.body.innerHTML = '';
});
afterEach(() => {
vi.restoreAllMocks();
delete (window as any).phoenix;
});
it('should subscribe to ANALYTICS CLICK on load', async () => {
vi.resetModules();
const localMockSubscribe = vi.fn();
window.phoenix = {
bus: {
publish: vi.fn(),
subscribe: localMockSubscribe,
},
log: { error: vi.fn() }
};
const module = await import('./click');
expect(localMockSubscribe).toHaveBeenCalledWith('ANALYTICS', 'CLICK', module.trackButtonClick);
});
describe('analyticsEventHandler', () => {
// Helper to create a mock event
const createMockEvent = (target: Element) => {
return {
composedPath: () => [target],
target: target,
} as unknown as MouseEvent;
};
it('should ignore clicks without data-analytics-click', () => {
const div = document.createElement('div');
const event = createMockEvent(div);
analyticsEventHandler(event);
expect(mockPublish).not.toHaveBeenCalled();
});
it('should ignore clicks with data-analytics-click-defer', () => {
const clickId = faker.lorem.slug();
const button = document.createElement('button');
button.setAttribute('data-analytics-click', clickId);
button.setAttribute('data-analytics-click-defer', '');
const event = createMockEvent(button);
analyticsEventHandler(event);
expect(mockPublish).not.toHaveBeenCalled();
});
it('should ignore tel: links', () => {
const clickId = faker.lorem.slug();
const link = document.createElement('a');
link.href = 'tel:' + faker.phone.number();
link.setAttribute('data-analytics-click', clickId);
const event = createMockEvent(link);
analyticsEventHandler(event);
expect(mockPublish).not.toHaveBeenCalled();
});
it('should track simple clicks', () => {
const clickId = faker.lorem.slug();
const button = document.createElement('button');
button.setAttribute('data-analytics-click', clickId);
const event = createMockEvent(button);
analyticsEventHandler(event);
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', {
payload: {
analyticsEvent: 'trackClick',
analyticsPayload: {
'cdl.page.icidList': clickId,
},
},
});
});
it('should normalize analytics string', () => {
const part1 = faker.lorem.word();
const part2 = faker.lorem.word();
const rawString = ` ${part1} | ${part2} `;
const expectedString = `${part1} : ${part2}`;
const button = document.createElement('button');
button.setAttribute('data-analytics-click', rawString);
const event = createMockEvent(button);
analyticsEventHandler(event);
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', {
payload: {
analyticsEvent: 'trackClick',
analyticsPayload: {
'cdl.page.icidList': expectedString,
},
},
});
});
it('should include ICID from URL (single)', () => {
const icidValue = faker.string.alphanumeric(10);
const clickId = faker.lorem.slug();
const link = document.createElement('a');
link.href = `http://example.com?ICID=${icidValue}`;
link.setAttribute('data-analytics-click', clickId);
(UriDecoder as any).mockImplementation(function () {
return {
getQueryParameter: vi.fn().mockReturnValue({ value: icidValue }),
};
});
const event = createMockEvent(link);
analyticsEventHandler(event);
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', expect.objectContaining({
payload: expect.objectContaining({
analyticsPayload: expect.objectContaining({
'cdl.page.icidList': `${clickId} : ${icidValue}`,
}),
}),
}));
});
it('should include ICID from URL (array)', () => {
const icid1 = faker.string.alphanumeric(5);
const icid2 = faker.string.alphanumeric(5);
const clickId = faker.lorem.slug();
const link = document.createElement('a');
link.href = `http://example.com?ICID=${icid1}&ICID=${icid2}`;
link.setAttribute('data-analytics-click', clickId);
(UriDecoder as any).mockImplementation(function () {
return {
getQueryParameter: vi.fn().mockReturnValue([{ value: icid1 }, { value: icid2 }]),
};
});
const event = createMockEvent(link);
analyticsEventHandler(event);
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', expect.objectContaining({
payload: expect.objectContaining({
analyticsPayload: expect.objectContaining({
'cdl.page.icidList': `${clickId} : ${icid1},${icid2}`,
}),
}),
}));
});
it('should include analytics index', () => {
const indexValue = faker.number.int({ min: 1, max: 100 }).toString();
const clickId = faker.lorem.slug();
const container = document.createElement('div');
container.setAttribute('data-analytics-index', indexValue);
const button = document.createElement('button');
button.setAttribute('data-analytics-click', clickId);
container.appendChild(button);
const event = createMockEvent(button);
analyticsEventHandler(event);
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', expect.objectContaining({
payload: expect.objectContaining({
analyticsPayload: expect.objectContaining({
'cdl.page.icidList': `${clickId} : ${indexValue}`,
}),
}),
}));
});
it('should parse and include analytics fields', () => {
const clickId = faker.lorem.slug();
const customKey = faker.lorem.word();
const customValue = faker.lorem.word();
const fields = { [customKey]: customValue };
const button = document.createElement('button');
button.setAttribute('data-analytics-click', clickId);
button.setAttribute('data-analytics-fields', JSON.stringify(fields));
const event = createMockEvent(button);
analyticsEventHandler(event);
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', expect.objectContaining({
payload: expect.objectContaining({
analyticsPayload: expect.objectContaining({
'cdl.page.icidList': clickId,
[customKey]: customValue,
}),
}),
}));
});
it('should log error on invalid analytics fields JSON', () => {
const clickId = faker.lorem.slug();
const button = document.createElement('button');
button.setAttribute('data-analytics-click', clickId);
button.setAttribute('data-analytics-fields', '{invalid-json');
const event = createMockEvent(button);
analyticsEventHandler(event);
expect(mockLogError).toHaveBeenCalled();
expect(mockPublish).toHaveBeenCalled();
});
});
describe('trackButtonClick', () => {
it('should track button click with basic payload', () => {
const ctaName = faker.commerce.productName();
trackButtonClick({
payload: {
ctaName: ctaName,
},
});
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', {
payload: {
analyticsEvent: 'trackClick',
analyticsPayload: {
'cdl.page.icidList': `Button Click : ${ctaName}`,
},
},
});
});
it('should include all optional fields', () => {
const ctaName = faker.commerce.productName();
const id = faker.string.uuid();
const cartAdd = faker.commerce.product();
const flowName = faker.lorem.word();
const cartOpen = 'true';
const product = faker.commerce.product();
trackButtonClick({
payload: {
ctaName: ctaName,
id: id,
cartAdd: cartAdd,
flowName: flowName,
cartOpen: cartOpen,
product: product,
},
});
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'FIRE', expect.objectContaining({
payload: expect.objectContaining({
analyticsPayload: expect.objectContaining({
'cdl.page.icidList': `Button Click : ${ctaName}`,
'cdl.cart.id': id,
'cdl.eventDetails.events.cartAdd': cartAdd,
'cdl.page.flowName': flowName,
'cdl.eventDetails.events.cartOpen': cartOpen,
'cdl.product': product,
}),
}),
}));
});
it('should publish CLICK_COMPLETE if product is present', () => {
const ctaName = faker.commerce.productName();
const product = faker.commerce.product();
trackButtonClick({
payload: {
ctaName: ctaName,
product: product,
},
});
expect(mockPublish).toHaveBeenCalledWith('ANALYTICS', 'CLICK_COMPLETE');
});
it('should NOT publish CLICK_COMPLETE if product is missing', () => {
const ctaName = faker.commerce.productName();
trackButtonClick({
payload: {
ctaName: ctaName,
},
});
expect(mockPublish).not.toHaveBeenCalledWith('ANALYTICS', 'CLICK_COMPLETE');
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment