Skip to content

Commit

Permalink
Add unit tests for capturing lifecycle events
Browse files Browse the repository at this point in the history
  • Loading branch information
robbie-c committed Sep 4, 2023
1 parent ed03c5d commit 671f5c3
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 2 deletions.
4 changes: 4 additions & 0 deletions posthog-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export abstract class PostHogCoreStateless {
}
this.requestTimeout = options?.requestTimeout ?? 10000 // 10 seconds
this.disableGeoip = options?.disableGeoip ?? true

if (options?.__onConstructed) {
options.__onConstructed(this)
}
}

protected getCommonEventProperties(): any {
Expand Down
5 changes: 5 additions & 0 deletions posthog-core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { PostHogCoreStateless } from './index'

export type PosthogCoreOptions = {
// PostHog API host (https://app.posthog.com by default)
host?: string
Expand Down Expand Up @@ -29,6 +31,9 @@ export type PosthogCoreOptions = {
// Whether to post events to PostHog in JSON or compressed format
captureMode?: 'json' | 'form'
disableGeoip?: boolean

// For testing purposes only, called at the end of the PostHogCoreStateless constructor
__onConstructed?: (posthog: PostHogCoreStateless) => void
}

export enum PostHogPersistedProperty {
Expand Down
7 changes: 5 additions & 2 deletions posthog-react-native/src/posthog-rn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class PostHog extends PostHogCore {
private _memoryStorage = new PostHogMemoryStorage()
private _semiAsyncStorage?: SemiAsyncStorage
private _appProperties: PostHogCustomAppProperties = {}
private _setupPromise?: Promise<void>

static _resetClientCache(): void {
// NOTE: this method is intended for testing purposes only
Expand All @@ -56,7 +57,9 @@ export class PostHog extends PostHogCore {
console.warn('PostHog.initAsync called twice with the same apiKey. The first instance will be used.')
}

return posthog
const resolved = await posthog
await resolved._setupPromise
return resolved
}

constructor(apiKey: string, options?: PostHogOptions, storage?: SemiAsyncStorage) {
Expand Down Expand Up @@ -110,7 +113,7 @@ export class PostHog extends PostHogCore {
}
}

void setupAsync()
this._setupPromise = setupAsync()
}

getPersistedProperty<T>(key: PostHogPersistedProperty): T | undefined {
Expand Down
169 changes: 169 additions & 0 deletions posthog-react-native/test/posthog.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { PostHogPersistedProperty } from 'posthog-core'
import { PostHog, PostHogCustomAsyncStorage } from '../index'
import { Linking, AppState, AppStateStatus } from 'react-native'

Linking.getInitialURL = jest.fn(() => Promise.resolve(null))
AppState.addEventListener = jest.fn()

describe('PostHog React Native', () => {
let mockStorage: PostHogCustomAsyncStorage
Expand Down Expand Up @@ -154,4 +158,169 @@ describe('PostHog React Native', () => {
expect(posthog.getPersistedProperty(PostHogPersistedProperty.Props)).toEqual(undefined)
})
})

describe('captureNativeAppLifecycleEvents', () => {
it('should trigger an Application Installed event', async () => {
// arrange
const onCapture = jest.fn()

// act
posthog = await PostHog.initAsync('test-install', {
customAsyncStorage: mockStorage,
flushInterval: 0,
captureNativeAppLifecycleEvents: true,
__onConstructed: (p) => {
p.on('capture', onCapture)
},
customAppProperties: {
$app_build: '1',
$app_version: '1.0.0',
},
})

// assert
expect(onCapture).toHaveBeenCalledTimes(2)
expect(onCapture.mock.calls[0][0]).toMatchObject({
event: 'Application Installed',
properties: {
$app_build: '1',
$app_version: '1.0.0',
},
})
expect(onCapture.mock.calls[1][0]).toMatchObject({
event: 'Application Opened',
properties: {
$app_build: '1',
$app_version: '1.0.0',
from_background: false,
},
})
})
it('should trigger an Application Updated event', async () => {
// arrange
const onCapture = jest.fn()
posthog = await PostHog.initAsync('test-update', {
customAsyncStorage: mockStorage,
captureNativeAppLifecycleEvents: true,
customAppProperties: {
$app_build: '1',
$app_version: '1.0.0',
},
})
PostHog._resetClientCache()

// act
posthog = await PostHog.initAsync('test-update', {
customAsyncStorage: mockStorage,
flushInterval: 0,
captureNativeAppLifecycleEvents: true,
__onConstructed: (p) => {
p.on('capture', onCapture)
},
customAppProperties: {
$app_build: '2',
$app_version: '2.0.0',
},
})

// assert
expect(onCapture).toHaveBeenCalledTimes(2)
expect(onCapture.mock.calls[0][0]).toMatchObject({
event: 'Application Updated',
properties: {
$app_build: '2',
$app_version: '2.0.0',
previous_build: '1',
previous_version: '1.0.0',
},
})
expect(onCapture.mock.calls[1][0]).toMatchObject({
event: 'Application Opened',
properties: {
$app_build: '2',
$app_version: '2.0.0',
from_background: false,
},
})
})
it('should only trigger an open event if the build number has not changed', async () => {
// arrange
Linking.getInitialURL = jest.fn(() => Promise.resolve('https://example.com'))
const onCapture = jest.fn()
posthog = await PostHog.initAsync('test-open', {
customAsyncStorage: mockStorage,
captureNativeAppLifecycleEvents: true,
customAppProperties: {
$app_build: '1',
$app_version: '1.0.0',
},
})
PostHog._resetClientCache()

// act
posthog = await PostHog.initAsync('test-open', {
customAsyncStorage: mockStorage,
flushInterval: 0,
captureNativeAppLifecycleEvents: true,
__onConstructed: (p) => {
p.on('capture', onCapture)
},
customAppProperties: {
$app_build: '1',
$app_version: '1.0.0',
},
})

// assert
expect(onCapture).toHaveBeenCalledTimes(1)
expect(onCapture.mock.calls[0][0]).toMatchObject({
event: 'Application Opened',
properties: {
$app_build: '1',
$app_version: '1.0.0',
from_background: false,
url: 'https://example.com',
},
})
})

it('should track app background and foreground', async () => {
// arrange
const onCapture = jest.fn()
posthog = await PostHog.initAsync('test-change', {
customAsyncStorage: mockStorage,
captureNativeAppLifecycleEvents: true,
__onConstructed: (p) => {
p.on('capture', onCapture)
},
customAppProperties: {
$app_build: '1',
$app_version: '1.0.0',
},
})
const cb: (state: AppStateStatus) => void = (AppState.addEventListener as jest.Mock).mock.calls[1][1]

// act
cb('background')
cb('active')

// assert
expect(onCapture).toHaveBeenCalledTimes(4)
expect(onCapture.mock.calls[2][0]).toMatchObject({
event: 'Application Backgrounded',
properties: {
$app_build: '1',
$app_version: '1.0.0',
},
})
expect(onCapture.mock.calls[3][0]).toMatchObject({
event: 'Application Opened',
properties: {
$app_build: '1',
$app_version: '1.0.0',
from_background: true,
},
})
})
})
})

0 comments on commit 671f5c3

Please sign in to comment.