mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
Bulk rename across 989 files: - Package names: posthog-js → @hanzo/insights, posthog-node → @hanzo/insights-node, etc. - Repository URLs: posthog/posthog-js → hanzoai/insights-js - Brand names: PostHog → Insights - File renames: 259 reference JSON files, source files, workflow files - Directory rename: eslint-plugin-posthog-js → eslint-plugin-insights Remaining: 8 refs to @posthog/rrweb-* (real npm deps, need fork+publish)
290 lines
8.1 KiB
TypeScript
290 lines
8.1 KiB
TypeScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
import { waitForPromises } from '@hanzo/insights-core/testing'
|
|
import { Insights } from '../src'
|
|
|
|
describe('InsightsWeb', () => {
|
|
let fetch: jest.Mock
|
|
jest.useRealTimers()
|
|
|
|
beforeEach(() => {
|
|
;(global as any).window.fetch = fetch = jest.fn(async (url) => {
|
|
let res: any = { status: 'ok' }
|
|
if (url.includes('flags')) {
|
|
res = {
|
|
featureFlags: {
|
|
'feature-1': true,
|
|
'feature-2': true,
|
|
'json-payload': true,
|
|
'feature-variant': 'variant',
|
|
},
|
|
// NOTE: Aren't these supposed to be strings?
|
|
|
|
featureFlagPayloads: {
|
|
'feature-1': {
|
|
color: 'blue',
|
|
},
|
|
'json-payload': {
|
|
a: 'payload',
|
|
},
|
|
'feature-variant': 5,
|
|
},
|
|
}
|
|
}
|
|
|
|
return {
|
|
status: 200,
|
|
json: () => Promise.resolve(res),
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('init', () => {
|
|
it('should initialise', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
flushAt: 1,
|
|
})
|
|
expect(insights.optedOut).toEqual(false)
|
|
|
|
insights.capture('test')
|
|
await waitForPromises()
|
|
|
|
expect(fetch).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('should load feature flags on init', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
flushAt: 1,
|
|
})
|
|
|
|
await waitForPromises()
|
|
|
|
expect(fetch).toHaveBeenCalledWith('https://us.i.insights.com/flags/?v=2', {
|
|
body: JSON.stringify({
|
|
token: 'TEST_API_KEY',
|
|
distinct_id: insights.getDistinctId(),
|
|
groups: {},
|
|
person_properties: {},
|
|
group_properties: {},
|
|
$anon_distinct_id: insights.getAnonymousId(),
|
|
}),
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
signal: expect.anything(),
|
|
})
|
|
|
|
expect(insights.getFeatureFlags()).toEqual({
|
|
'feature-1': true,
|
|
'feature-2': true,
|
|
'json-payload': true,
|
|
'feature-variant': 'variant',
|
|
})
|
|
|
|
expect(insights.getFeatureFlagPayloads()).toEqual({
|
|
'feature-1': {
|
|
color: 'blue',
|
|
},
|
|
'json-payload': {
|
|
a: 'payload',
|
|
},
|
|
'feature-variant': 5,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('History API tracking', () => {
|
|
const originalPushState = window.history.pushState
|
|
const originalReplaceState = window.history.replaceState
|
|
let setPathname: (pathname: string) => void
|
|
|
|
beforeEach(() => {
|
|
const mockLocation = {
|
|
pathname: '/initial-path',
|
|
}
|
|
|
|
Object.defineProperty(window, 'location', {
|
|
value: mockLocation,
|
|
writable: true,
|
|
})
|
|
|
|
setPathname = (pathname: string) => {
|
|
mockLocation.pathname = pathname
|
|
}
|
|
})
|
|
|
|
// Reset history methods after each test
|
|
afterEach(() => {
|
|
window.history.pushState = originalPushState
|
|
window.history.replaceState = originalReplaceState
|
|
|
|
const popstateHandler = (): void => {}
|
|
window.addEventListener('popstate', popstateHandler)
|
|
window.removeEventListener('popstate', popstateHandler)
|
|
})
|
|
|
|
it('should not patch history methods when captureHistoryEvents is disabled', () => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: false,
|
|
})
|
|
|
|
expect(window.history.pushState).toBe(originalPushState)
|
|
expect(window.history.replaceState).toBe(originalReplaceState)
|
|
})
|
|
|
|
it('should patch history methods when captureHistoryEvents is enabled', () => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
})
|
|
|
|
expect(window.history.pushState).not.toBe(originalPushState)
|
|
expect(window.history.replaceState).not.toBe(originalReplaceState)
|
|
})
|
|
|
|
it('should capture pageview events on pushState when pathname changes', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const captureSpy = jest.spyOn(insights, 'capture')
|
|
|
|
// Change pathname
|
|
setPathname('/test-page')
|
|
window.history.pushState({}, '', '/test-page')
|
|
|
|
expect(captureSpy).toHaveBeenCalledWith(
|
|
'$pageview',
|
|
expect.objectContaining({
|
|
navigation_type: 'pushState',
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should not capture pageview events on pushState when pathname does not change', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const captureSpy = jest.spyOn(insights, 'capture')
|
|
captureSpy.mockClear()
|
|
|
|
// Don't change pathname
|
|
window.history.pushState({}, '', '/initial-path')
|
|
|
|
expect(captureSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should capture pageview events on replaceState when pathname changes', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const captureSpy = jest.spyOn(insights, 'capture')
|
|
|
|
// Change pathname
|
|
setPathname('/replaced-page')
|
|
window.history.replaceState({}, '', '/replaced-page')
|
|
|
|
expect(captureSpy).toHaveBeenCalledWith(
|
|
'$pageview',
|
|
expect.objectContaining({
|
|
navigation_type: 'replaceState',
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should not capture pageview events on replaceState when pathname does not change', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const captureSpy = jest.spyOn(insights, 'capture')
|
|
captureSpy.mockClear()
|
|
|
|
// Don't change pathname
|
|
window.history.replaceState({}, '', '/initial-path')
|
|
|
|
expect(captureSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should capture pageview events on popstate when pathname changes', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const captureSpy = jest.spyOn(insights, 'capture')
|
|
|
|
// Change pathname
|
|
setPathname('/popstate-page')
|
|
window.dispatchEvent(new Event('popstate'))
|
|
|
|
expect(captureSpy).toHaveBeenCalledWith(
|
|
'$pageview',
|
|
expect.objectContaining({
|
|
navigation_type: 'popstate',
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should not capture pageview events on popstate when pathname does not change', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const captureSpy = jest.spyOn(insights, 'capture')
|
|
captureSpy.mockClear()
|
|
|
|
// Don't change pathname
|
|
window.dispatchEvent(new Event('popstate'))
|
|
|
|
expect(captureSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should include navigation properties in capture call and rely on getCommonEventProperties', async () => {
|
|
const insights = new Insights('TEST_API_KEY', {
|
|
captureHistoryEvents: true,
|
|
flushAt: 1,
|
|
})
|
|
|
|
const commonEventProps = { $lib: '@hanzo/insights-lite', $lib_version: '1.0.0' }
|
|
const getCommonEventPropertiesSpy = jest
|
|
.spyOn(insights, 'getCommonEventProperties')
|
|
.mockImplementation(() => commonEventProps)
|
|
|
|
const coreCaptureMethod = jest.spyOn(Insights.prototype, 'capture')
|
|
|
|
// Change pathname
|
|
setPathname('/captured-page')
|
|
window.history.pushState({}, '', '/captured-page')
|
|
|
|
// Will use a mock here for now and rely on the implementation since the tests setup is very simple at the moment
|
|
expect(getCommonEventPropertiesSpy).toHaveBeenCalled()
|
|
|
|
const captureCall = coreCaptureMethod.mock.calls.find(
|
|
(call) => call[0] === '$pageview' && call[1] && call[1].navigation_type === 'pushState'
|
|
)
|
|
|
|
expect(captureCall).toBeDefined()
|
|
|
|
const navigationProperties = {
|
|
navigation_type: 'pushState',
|
|
}
|
|
|
|
if (captureCall) {
|
|
expect(captureCall[1]).toMatchObject(navigationProperties)
|
|
}
|
|
})
|
|
})
|
|
})
|