function prepareEnvironment() { document.head.innerHTML = '' vi.spyOn(Storage.prototype, 'getItem').mockReturnValue('visitor-123') vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {}) globalThis.fetch = vi.fn(() => Promise.resolve({ ok: true, headers: { get: () => 'application/json' }, json: () => Promise.resolve({ ok: true }) })) } function cleanupEnvironment() { vi.restoreAllMocks() document.head.innerHTML = '' } test('academy search click attribution uses sendBeacon without blocking navigation', async () => { prepareEnvironment() const { trackAcademySearchResultClick } = await import('./academyAnalytics.js') Object.defineProperty(navigator, 'sendBeacon', { configurable: true, value: vi.fn(() => true), }) const result = trackAcademySearchResultClick({ eventUrl: '/academy/analytics/events', pageName: 'academy_prompts_index', }, { query: 'robot mascot', resultsCount: 12, filters: { difficulty: 'beginner' }, }, { contentType: 'academy_prompt', contentId: 123, position: 3, }) expect(result).toBeUndefined() expect(navigator.sendBeacon).toHaveBeenCalledTimes(1) expect(globalThis.fetch).not.toHaveBeenCalled() cleanupEnvironment() }) test('academy search click attribution falls back to keepalive fetch when sendBeacon cannot queue', async () => { prepareEnvironment() const { trackAcademySearchResultClick } = await import('./academyAnalytics.js') Object.defineProperty(navigator, 'sendBeacon', { configurable: true, value: vi.fn(() => false), }) trackAcademySearchResultClick({ eventUrl: '/academy/analytics/events', pageName: 'academy_prompts_index', }, { query: 'robot mascot', resultsCount: 12, filters: { difficulty: 'beginner' }, }, { contentType: 'academy_prompt', contentId: 123, position: 3, }) expect(globalThis.fetch).toHaveBeenCalledTimes(1) expect(globalThis.fetch.mock.calls[0][1].keepalive).toBe(true) cleanupEnvironment() })