import { TrackingData, CustomCodeComponentTracker } from './tracking' describe('Custom Code Component Tracker', () => { let mockIntersectionObserver: jest.Mock let mockObserve: jest.Mock let mockUnobserve: jest.Mock let mockDisconnectIntersection: jest.Mock let mockComponent: HTMLElement let mockTrackingData: TrackingData let dispatchEventSpy: jest.SpyInstance beforeEach(() => { mockObserve = jest.fn() mockUnobserve = jest.fn() mockDisconnectIntersection = jest.fn() mockIntersectionObserver = jest.fn(() => { return { observe: mockObserve, unobserve: mockUnobserve, disconnect: mockDisconnectIntersection, } }) window.IntersectionObserver = mockIntersectionObserver window.MutationObserver = MutationObserver mockComponent = document.createElement('custom-code-component') mockTrackingData = { component: { id: '123', name: 'test-component', type: 'custom-code-component', }, } document.body.appendChild(mockComponent) dispatchEventSpy = jest.spyOn(document.body, 'dispatchEvent') }) afterEach(() => { jest.clearAllMocks() document.body.removeChild(mockComponent) }) describe('tracking initialisation and teardown', () => { it('should initialise the component tracking', () => { new CustomCodeComponentTracker(mockComponent, mockTrackingData) const mountEvent = dispatchEventSpy.mock.calls[0][0] as CustomEvent expect(mountEvent.type).toBe('oTracking.event') expect(mountEvent.detail).toEqual({ category: 'component', action: 'mount', component: mockTrackingData.component, }) }) it('should send an error event on initialisation if the component has errored', () => { mockComponent.dataset.cccError = 'import-failure' new CustomCodeComponentTracker(mockComponent, mockTrackingData) const errorEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent expect(errorEvent.type).toBe('oTracking.event') expect(errorEvent.detail).toEqual({ category: 'component', action: 'act', component: mockTrackingData.component, trigger_action: 'error: import-failure', }) }) it('should send a success event on initialisation if the component has already loaded', () => { mockComponent.dataset.cccReady = 'true' new CustomCodeComponentTracker(mockComponent, mockTrackingData) const successEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent expect(successEvent.type).toBe('oTracking.event') expect(successEvent.detail).toEqual({ category: 'component', action: 'act', component: mockTrackingData.component, trigger_action: 'success', }) }) it('should set the component as initialised', () => { new CustomCodeComponentTracker(mockComponent, mockTrackingData) expect(mockComponent.getAttribute('data-initialised')).toBe('true') }) it('should teardown the instance on destroy', () => { const tracker = new CustomCodeComponentTracker( mockComponent, mockTrackingData ) tracker.destroy() expect(mockComponent.getAttribute('data-initialised')).toBeNull() expect(mockDisconnectIntersection).toHaveBeenCalledTimes(1) }) }) describe('full component initalisation events', () => { it('should send a success event when the component has loaded', (done) => { new CustomCodeComponentTracker(mockComponent, mockTrackingData) Promise.resolve((mockComponent.dataset.cccReady = 'true')).then(() => { const successEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent expect(successEvent.type).toBe('oTracking.event') expect(successEvent.detail).toEqual({ category: 'component', action: 'act', component: mockTrackingData.component, trigger_action: 'success', }) done() }) }) it('should send an error import-timeout event when the component has not loaded', (done) => { new CustomCodeComponentTracker(mockComponent, mockTrackingData) Promise.resolve((mockComponent.dataset.cccError = 'import-timeout')).then( () => { const successEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent expect(successEvent.type).toBe('oTracking.event') expect(successEvent.detail).toEqual({ category: 'component', action: 'act', component: mockTrackingData.component, trigger_action: 'error: import-timeout', }) done() } ) }) it('should send an error import-failure event when the component has errored', (done) => { new CustomCodeComponentTracker(mockComponent, mockTrackingData) Promise.resolve((mockComponent.dataset.cccError = 'import-failure')).then( () => { const successEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent expect(successEvent.type).toBe('oTracking.event') expect(successEvent.detail).toEqual({ category: 'component', action: 'act', component: mockTrackingData.component, trigger_action: 'error: import-failure', }) done() } ) }) it('should send a timeout then success if the component loads slowly, after a time threshold defined as timeout', (done) => { new CustomCodeComponentTracker(mockComponent, mockTrackingData) Promise.resolve((mockComponent.dataset.cccError = 'import-timeout')) .then(() => (mockComponent.dataset.cccReady = 'true')) .then(() => { const successEvent = dispatchEventSpy.mock.calls[2][0] as CustomEvent expect(successEvent.type).toBe('oTracking.event') expect(successEvent.detail).toEqual({ category: 'component', action: 'act', component: mockTrackingData.component, trigger_action: 'success', }) done() }) }) }) describe('component visibility changes', () => { it('should send a view event when the component enters and exits the viewport', () => { const tracker = new CustomCodeComponentTracker( mockComponent, mockTrackingData ) const mockEnterView: IntersectionObserverEntry[] = [ { target: mockComponent, isIntersecting: true, intersectionRatio: 1.0, boundingClientRect: { height: 100, } as unknown as DOMRectReadOnly, intersectionRect: {} as DOMRectReadOnly, rootBounds: null, time: 1, }, ] tracker['onChange'](mockEnterView) const viewEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent expect(viewEvent.type).toBe('oTracking.event') expect(viewEvent.detail).toEqual({ category: 'component', action: 'view', component: mockTrackingData.component, }) const mockExitView: IntersectionObserverEntry[] = [ { target: mockComponent, isIntersecting: false, intersectionRatio: 0, boundingClientRect: { height: 100, } as unknown as DOMRectReadOnly, intersectionRect: {} as DOMRectReadOnly, rootBounds: null, time: 1, }, ] tracker['onChange'](mockExitView) const exitEvent = dispatchEventSpy.mock.calls[2][0] as CustomEvent expect(exitEvent.type).toBe('oTracking.event') expect(exitEvent.detail).toEqual({ category: 'component', action: 'act', trigger_action: 'exit-view', component: mockTrackingData.component, }) }) }) })