// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest'; import type { WidgetConfig } from './types'; import { trackSizeCompleted, trackTryOnCompleted } from './events'; const config = { eventsUrl: 'https://brain.test/events', publishableKey: 'pk_test', analyticsEnabled: true, visitorKey: 'visitor-1', customerId: 'customer-2', } as WidgetConfig; describe( 'trackTryOnCompleted', () => { afterEach( () => { delete window.aisthetixTrackEvent; vi.unstubAllGlobals(); } ); it( 'uses the bootstrap analytics contract when available', () => { window.aisthetixTrackEvent = vi.fn(); trackTryOnCompleted( config, '42' ); expect( window.aisthetixTrackEvent ).toHaveBeenCalledWith( 'aisthetix_tryon_completed', { productId: '42' } ); } ); it( 'falls back to a fire-and-forget POST with the same shaped fields', () => { const fetchMock = vi.fn().mockResolvedValue( {} ); vi.stubGlobal( 'fetch', fetchMock ); trackTryOnCompleted( config, '' ); const [ endpoint, request ] = fetchMock.mock.calls[ 0 ]; expect( endpoint ).toBe( config.eventsUrl ); expect( request.keepalive ).toBe( true ); expect( request.credentials ).toBe( 'omit' ); expect( JSON.parse( request.body ) ).toEqual( expect.objectContaining( { eventName: 'aisthetix_tryon_completed', visitorKey: 'visitor-1', customerId: 'customer-2', productId: null, } ) ); } ); it( 'stays inert without fallback credentials and swallows analytics failures', () => { const fetchMock = vi.fn(); vi.stubGlobal( 'fetch', fetchMock ); trackTryOnCompleted( { ...config, eventsUrl: '' }, '42' ); expect( fetchMock ).not.toHaveBeenCalled(); Object.defineProperty( window, 'aisthetixTrackEvent', { configurable: true, get: () => { throw new Error( 'blocked' ); }, } ); expect( () => trackTryOnCompleted( config, '42' ) ).not.toThrow(); } ); } ); describe( 'trackSizeCompleted', () => { afterEach( () => { delete window.aisthetixTrackEvent; vi.unstubAllGlobals(); } ); it( 'uses the same attribution transport with product and variation', () => { window.aisthetixTrackEvent = vi.fn(); trackSizeCompleted( config, '42', '7' ); expect( window.aisthetixTrackEvent ).toHaveBeenCalledWith( 'aisthetix_size_completed', { productId: '42', variantId: '7' } ); } ); it( 'falls back to one privacy-safe keepalive request', () => { const fetchMock = vi.fn().mockResolvedValue( {} ); vi.stubGlobal( 'fetch', fetchMock ); trackSizeCompleted( config, '42', null ); const body = JSON.parse( fetchMock.mock.calls[ 0 ][ 1 ].body ); expect( body ).toEqual( expect.objectContaining( { eventName: 'aisthetix_size_completed', productId: '42', variantId: null, visitorKey: 'visitor-1', } ) ); expect( JSON.stringify( body ) ).not.toContain( 'height' ); } ); it( 'fails closed when the bootstrap consent gate is unavailable and attribution is disabled', () => { const fetchMock = vi.fn(); vi.stubGlobal( 'fetch', fetchMock ); trackSizeCompleted( { ...config, analyticsEnabled: false }, '42', '7' ); expect( fetchMock ).not.toHaveBeenCalled(); } ); } );