145 lines
4.2 KiB
JavaScript
145 lines
4.2 KiB
JavaScript
import React from 'react'
|
|
import { cleanup, render, waitFor } from '@testing-library/react'
|
|
|
|
function prepareEnvironment() {
|
|
document.head.innerHTML = '<meta name="csrf-token" content="csrf-token" />'
|
|
const storage = new Map([['academy.analytics.visitor-id', 'visitor-123']])
|
|
vi.spyOn(Storage.prototype, 'getItem').mockImplementation((key) => storage.get(String(key)) ?? null)
|
|
vi.spyOn(Storage.prototype, 'setItem').mockImplementation((key, value) => {
|
|
storage.set(String(key), String(value))
|
|
})
|
|
globalThis.fetch = vi.fn(() => Promise.resolve({ ok: true, headers: { get: () => 'application/json' }, json: () => Promise.resolve({ ok: true }) }))
|
|
}
|
|
|
|
function cleanupEnvironment() {
|
|
cleanup()
|
|
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()
|
|
})
|
|
|
|
test('academy page analytics includes custom metadata and varies page-view once keys by tracking context', async () => {
|
|
prepareEnvironment()
|
|
|
|
const { useAcademyPageAnalytics } = await import('./academyAnalytics.js')
|
|
|
|
Object.defineProperty(navigator, 'sendBeacon', {
|
|
configurable: true,
|
|
value: undefined,
|
|
})
|
|
|
|
function TestPage({ analytics }) {
|
|
useAcademyPageAnalytics(analytics)
|
|
return React.createElement('div', null, 'Academy page')
|
|
}
|
|
|
|
const { rerender } = render(
|
|
React.createElement(TestPage, {
|
|
analytics: {
|
|
enabled: true,
|
|
eventUrl: '/academy/analytics/events',
|
|
pageName: 'academy_prompts_popular',
|
|
contentType: 'academy_prompt_popular',
|
|
contentId: null,
|
|
trackingKey: 'period:30d',
|
|
metadata: { period: '30d', period_days: 30 },
|
|
},
|
|
}),
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(globalThis.fetch).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
const firstPageView = globalThis.fetch.mock.calls
|
|
.map((call) => JSON.parse(call[1].body))
|
|
.find((payload) => payload.event_type === 'academy_page_view')
|
|
|
|
expect(firstPageView.metadata.period).toBe('30d')
|
|
expect(firstPageView.metadata.period_days).toBe(30)
|
|
|
|
rerender(
|
|
React.createElement(TestPage, {
|
|
analytics: {
|
|
enabled: true,
|
|
eventUrl: '/academy/analytics/events',
|
|
pageName: 'academy_prompts_popular',
|
|
contentType: 'academy_prompt_popular',
|
|
contentId: null,
|
|
trackingKey: 'period:7d',
|
|
metadata: { period: '7d', period_days: 7 },
|
|
},
|
|
}),
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(globalThis.fetch).toHaveBeenCalledTimes(4)
|
|
})
|
|
|
|
const pageViews = globalThis.fetch.mock.calls
|
|
.map((call) => JSON.parse(call[1].body))
|
|
.filter((payload) => payload.event_type === 'academy_page_view')
|
|
|
|
expect(pageViews).toHaveLength(2)
|
|
expect(pageViews[1].metadata.period).toBe('7d')
|
|
|
|
cleanupEnvironment()
|
|
}) |