@posthog/react 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.js +68 -36
- package/dist/esm/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -1
- package/dist/umd/index.js +70 -35
- package/dist/umd/index.js.map +1 -1
- package/package.json +7 -9
- package/src/components/PostHogCaptureOnViewed.tsx +126 -0
- package/src/components/PostHogErrorBoundary.tsx +88 -0
- package/src/components/PostHogFeature.tsx +89 -0
- package/src/components/__tests__/PostHogCaptureOnViewed.test.tsx +110 -0
- package/src/components/__tests__/PostHogErrorBoundary.test.tsx +109 -0
- package/src/components/__tests__/PostHogFeature.test.tsx +249 -0
- package/src/components/index.ts +7 -0
- package/src/components/internal/VisibilityAndClickTracker.tsx +49 -0
- package/src/components/internal/VisibilityAndClickTrackers.tsx +60 -0
- package/src/context/PostHogContext.ts +6 -0
- package/src/context/PostHogProvider.tsx +128 -0
- package/src/context/__tests__/PostHogContext.test.tsx +35 -0
- package/src/context/__tests__/PostHogProvider.test.tsx +131 -0
- package/src/context/index.ts +2 -0
- package/src/helpers/error-helpers.ts +15 -0
- package/src/helpers/index.ts +1 -0
- package/src/hooks/__tests__/featureFlags.test.tsx +97 -0
- package/src/hooks/__tests__/usePostHog.test.tsx +19 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useActiveFeatureFlags.ts +16 -0
- package/src/hooks/useFeatureFlagEnabled.ts +16 -0
- package/src/hooks/useFeatureFlagPayload.ts +17 -0
- package/src/hooks/useFeatureFlagVariantKey.ts +18 -0
- package/src/hooks/usePostHog.ts +7 -0
- package/src/index.ts +4 -0
- package/src/utils/__tests__/object-utils.test.ts +42 -0
- package/src/utils/object-utils.ts +36 -0
- package/src/utils/type-utils.ts +16 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { render, act } from '@testing-library/react'
|
|
3
|
+
import { PostHogProvider, PostHog } from '..'
|
|
4
|
+
import posthogJs from 'posthog-js'
|
|
5
|
+
|
|
6
|
+
// Mock posthog-js
|
|
7
|
+
jest.mock('posthog-js', () => ({
|
|
8
|
+
__esModule: true,
|
|
9
|
+
default: {
|
|
10
|
+
init: jest.fn(),
|
|
11
|
+
set_config: jest.fn(),
|
|
12
|
+
__loaded: false,
|
|
13
|
+
},
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
describe('PostHogProvider component', () => {
|
|
17
|
+
it('should render children components', () => {
|
|
18
|
+
const posthog = {} as unknown as PostHog
|
|
19
|
+
const { getByText } = render(
|
|
20
|
+
<PostHogProvider client={posthog}>
|
|
21
|
+
<div>Test</div>
|
|
22
|
+
</PostHogProvider>
|
|
23
|
+
)
|
|
24
|
+
expect(getByText('Test')).toBeTruthy()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('when using apiKey initialization', () => {
|
|
28
|
+
const apiKey = 'test-api-key'
|
|
29
|
+
const initialOptions = { api_host: 'https://app.posthog.com' }
|
|
30
|
+
const updatedOptions = { api_host: 'https://eu.posthog.com' }
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should call set_config when options change', () => {
|
|
37
|
+
const { rerender } = render(
|
|
38
|
+
<PostHogProvider apiKey={apiKey} options={initialOptions}>
|
|
39
|
+
<div>Test</div>
|
|
40
|
+
</PostHogProvider>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// First render should initialize
|
|
44
|
+
expect(posthogJs.init).toHaveBeenCalledWith(apiKey, initialOptions)
|
|
45
|
+
|
|
46
|
+
// Rerender with new options
|
|
47
|
+
act(() => {
|
|
48
|
+
rerender(
|
|
49
|
+
<PostHogProvider apiKey={apiKey} options={updatedOptions}>
|
|
50
|
+
<div>Test</div>
|
|
51
|
+
</PostHogProvider>
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Should call set_config with new options
|
|
56
|
+
expect(posthogJs.set_config).toHaveBeenCalledWith(updatedOptions)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should NOT call set_config when we pass new options that are the same as the previous options', () => {
|
|
60
|
+
const { rerender } = render(
|
|
61
|
+
<PostHogProvider apiKey={apiKey} options={initialOptions}>
|
|
62
|
+
<div>Test</div>
|
|
63
|
+
</PostHogProvider>
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// First render should initialize
|
|
67
|
+
expect(posthogJs.init).toHaveBeenCalledWith(apiKey, initialOptions)
|
|
68
|
+
|
|
69
|
+
// Rerender with new options
|
|
70
|
+
const sameOptionsButDifferentReference = { ...initialOptions }
|
|
71
|
+
act(() => {
|
|
72
|
+
rerender(
|
|
73
|
+
<PostHogProvider apiKey={apiKey} options={sameOptionsButDifferentReference}>
|
|
74
|
+
<div>Test</div>
|
|
75
|
+
</PostHogProvider>
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Should NOT call set_config
|
|
80
|
+
expect(posthogJs.set_config).not.toHaveBeenCalled()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should warn when attempting to change apiKey', () => {
|
|
84
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
85
|
+
const newApiKey = 'different-api-key'
|
|
86
|
+
|
|
87
|
+
const { rerender } = render(
|
|
88
|
+
<PostHogProvider apiKey={apiKey} options={initialOptions}>
|
|
89
|
+
<div>Test</div>
|
|
90
|
+
</PostHogProvider>
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// First render should initialize
|
|
94
|
+
expect(posthogJs.init).toHaveBeenCalledWith(apiKey, initialOptions)
|
|
95
|
+
|
|
96
|
+
// Rerender with new apiKey
|
|
97
|
+
act(() => {
|
|
98
|
+
rerender(
|
|
99
|
+
<PostHogProvider apiKey={newApiKey} options={initialOptions}>
|
|
100
|
+
<div>Test</div>
|
|
101
|
+
</PostHogProvider>
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Should warn about apiKey change
|
|
106
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
107
|
+
expect.stringContaining('You have provided a different `apiKey` to `PostHogProvider`')
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
consoleSpy.mockRestore()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('warns if posthogJs has been loaded elsewhere', () => {
|
|
114
|
+
;(posthogJs as any).__loaded = true
|
|
115
|
+
|
|
116
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
117
|
+
render(
|
|
118
|
+
<PostHogProvider apiKey={apiKey} options={initialOptions}>
|
|
119
|
+
<div>Test</div>
|
|
120
|
+
</PostHogProvider>
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
124
|
+
expect.stringContaining('`posthog` was already loaded elsewhere. This may cause issues.')
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
consoleSpy.mockRestore()
|
|
128
|
+
;(posthogJs as any).__loaded = false
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ErrorInfo } from 'react'
|
|
2
|
+
import { PostHog } from '../context'
|
|
3
|
+
import { CaptureResult } from 'posthog-js'
|
|
4
|
+
|
|
5
|
+
export const setupReactErrorHandler = (
|
|
6
|
+
client: PostHog,
|
|
7
|
+
callback?: (event: CaptureResult | undefined, error: any, errorInfo: ErrorInfo) => void
|
|
8
|
+
) => {
|
|
9
|
+
return (error: any, errorInfo: ErrorInfo): void => {
|
|
10
|
+
const event = client.captureException(error)
|
|
11
|
+
if (callback) {
|
|
12
|
+
callback(event, error, errorInfo)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './error-helpers'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { renderHook } from '@testing-library/react-hooks'
|
|
3
|
+
import { PostHogProvider, PostHog } from '../../context'
|
|
4
|
+
import { useFeatureFlagPayload, useFeatureFlagVariantKey, useFeatureFlagEnabled, useActiveFeatureFlags } from '../index'
|
|
5
|
+
|
|
6
|
+
jest.useFakeTimers()
|
|
7
|
+
|
|
8
|
+
const ACTIVE_FEATURE_FLAGS = ['example_feature_true', 'multivariate_feature', 'example_feature_payload']
|
|
9
|
+
|
|
10
|
+
const FEATURE_FLAG_STATUS: Record<string, string | boolean> = {
|
|
11
|
+
example_feature_true: true,
|
|
12
|
+
example_feature_false: false,
|
|
13
|
+
multivariate_feature: 'string-value',
|
|
14
|
+
example_feature_payload: 'test',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const FEATURE_FLAG_PAYLOADS: Record<string, any> = {
|
|
18
|
+
example_feature_payload: {
|
|
19
|
+
id: 1,
|
|
20
|
+
name: 'example_feature_1_payload',
|
|
21
|
+
key: 'example_feature_1_payload',
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('useFeatureFlagPayload hook', () => {
|
|
26
|
+
let posthog: PostHog
|
|
27
|
+
let renderProvider: React.FC<{ children: React.ReactNode }>
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
posthog = {
|
|
31
|
+
isFeatureEnabled: (flag: string) => !!FEATURE_FLAG_STATUS[flag],
|
|
32
|
+
getFeatureFlag: (flag: string) => FEATURE_FLAG_STATUS[flag],
|
|
33
|
+
getFeatureFlagPayload: (flag: string) => FEATURE_FLAG_PAYLOADS[flag],
|
|
34
|
+
onFeatureFlags: (callback: any) => {
|
|
35
|
+
const activeFlags: string[] = []
|
|
36
|
+
for (const flag in FEATURE_FLAG_STATUS) {
|
|
37
|
+
if (FEATURE_FLAG_STATUS[flag]) {
|
|
38
|
+
activeFlags.push(flag)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
callback(activeFlags)
|
|
42
|
+
return () => {}
|
|
43
|
+
},
|
|
44
|
+
featureFlags: {
|
|
45
|
+
getFlags: () => ACTIVE_FEATURE_FLAGS,
|
|
46
|
+
} as unknown as PostHog['featureFlags'],
|
|
47
|
+
} as unknown as PostHog
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line react/display-name
|
|
50
|
+
renderProvider = ({ children }) => <PostHogProvider client={posthog}>{children}</PostHogProvider>
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it.each([
|
|
54
|
+
['example_feature_true', true],
|
|
55
|
+
['example_feature_false', false],
|
|
56
|
+
['missing', false],
|
|
57
|
+
['multivariate_feature', true],
|
|
58
|
+
['example_feature_payload', true],
|
|
59
|
+
])('should get the boolean feature flag', (flag, expected) => {
|
|
60
|
+
const { result } = renderHook(() => useFeatureFlagEnabled(flag), {
|
|
61
|
+
wrapper: renderProvider,
|
|
62
|
+
})
|
|
63
|
+
expect(result.current).toEqual(expected)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it.each([
|
|
67
|
+
['example_feature_true', undefined],
|
|
68
|
+
['example_feature_false', undefined],
|
|
69
|
+
['missing', undefined],
|
|
70
|
+
['multivariate_feature', undefined],
|
|
71
|
+
['example_feature_payload', FEATURE_FLAG_PAYLOADS.example_feature_payload],
|
|
72
|
+
])('should get the payload feature flag', (flag, expected) => {
|
|
73
|
+
const { result } = renderHook(() => useFeatureFlagPayload(flag), {
|
|
74
|
+
wrapper: renderProvider,
|
|
75
|
+
})
|
|
76
|
+
expect(result.current).toEqual(expected)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should return the active feature flags', () => {
|
|
80
|
+
const { result } = renderHook(() => useActiveFeatureFlags(), {
|
|
81
|
+
wrapper: renderProvider,
|
|
82
|
+
})
|
|
83
|
+
expect(result.current).toEqual(['example_feature_true', 'multivariate_feature', 'example_feature_payload'])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it.each([
|
|
87
|
+
['example_feature_true', true],
|
|
88
|
+
['example_feature_false', false],
|
|
89
|
+
['missing', undefined],
|
|
90
|
+
['multivariate_feature', 'string-value'],
|
|
91
|
+
])('should get the feature flag variant key', (flag, expected) => {
|
|
92
|
+
const { result } = renderHook(() => useFeatureFlagVariantKey(flag), {
|
|
93
|
+
wrapper: renderProvider,
|
|
94
|
+
})
|
|
95
|
+
expect(result.current).toEqual(expected)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { renderHook } from '@testing-library/react-hooks'
|
|
3
|
+
import { PostHogProvider, PostHog } from '../../context'
|
|
4
|
+
import { usePostHog } from '..'
|
|
5
|
+
|
|
6
|
+
jest.useFakeTimers()
|
|
7
|
+
|
|
8
|
+
const posthog = { posthog_client: true } as unknown as PostHog
|
|
9
|
+
|
|
10
|
+
describe('usePostHog hook', () => {
|
|
11
|
+
it('should return the client', () => {
|
|
12
|
+
const { result } = renderHook(() => usePostHog(), {
|
|
13
|
+
wrapper: ({ children }: { children: React.ReactNode }) => (
|
|
14
|
+
<PostHogProvider client={posthog}>{children}</PostHogProvider>
|
|
15
|
+
),
|
|
16
|
+
})
|
|
17
|
+
expect(result.current).toEqual(posthog)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { usePostHog } from './usePostHog'
|
|
3
|
+
|
|
4
|
+
export function useActiveFeatureFlags(): string[] {
|
|
5
|
+
const client = usePostHog()
|
|
6
|
+
|
|
7
|
+
const [featureFlags, setFeatureFlags] = useState<string[]>(() => client.featureFlags.getFlags())
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
return client.onFeatureFlags((flags) => {
|
|
11
|
+
setFeatureFlags(flags)
|
|
12
|
+
})
|
|
13
|
+
}, [client])
|
|
14
|
+
|
|
15
|
+
return featureFlags
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { usePostHog } from './usePostHog'
|
|
3
|
+
|
|
4
|
+
export function useFeatureFlagEnabled(flag: string): boolean | undefined {
|
|
5
|
+
const client = usePostHog()
|
|
6
|
+
|
|
7
|
+
const [featureEnabled, setFeatureEnabled] = useState<boolean | undefined>(() => client.isFeatureEnabled(flag))
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
return client.onFeatureFlags(() => {
|
|
11
|
+
setFeatureEnabled(client.isFeatureEnabled(flag))
|
|
12
|
+
})
|
|
13
|
+
}, [client, flag])
|
|
14
|
+
|
|
15
|
+
return featureEnabled
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { JsonType } from 'posthog-js'
|
|
3
|
+
import { usePostHog } from './usePostHog'
|
|
4
|
+
|
|
5
|
+
export function useFeatureFlagPayload(flag: string): JsonType {
|
|
6
|
+
const client = usePostHog()
|
|
7
|
+
|
|
8
|
+
const [featureFlagPayload, setFeatureFlagPayload] = useState<JsonType>(() => client.getFeatureFlagPayload(flag))
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
return client.onFeatureFlags(() => {
|
|
12
|
+
setFeatureFlagPayload(client.getFeatureFlagPayload(flag))
|
|
13
|
+
})
|
|
14
|
+
}, [client, flag])
|
|
15
|
+
|
|
16
|
+
return featureFlagPayload
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { usePostHog } from './usePostHog'
|
|
3
|
+
|
|
4
|
+
export function useFeatureFlagVariantKey(flag: string): string | boolean | undefined {
|
|
5
|
+
const client = usePostHog()
|
|
6
|
+
|
|
7
|
+
const [featureFlagVariantKey, setFeatureFlagVariantKey] = useState<string | boolean | undefined>(() =>
|
|
8
|
+
client.getFeatureFlag(flag)
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
return client.onFeatureFlags(() => {
|
|
13
|
+
setFeatureFlagVariantKey(client.getFeatureFlag(flag))
|
|
14
|
+
})
|
|
15
|
+
}, [client, flag])
|
|
16
|
+
|
|
17
|
+
return featureFlagVariantKey
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isDeepEqual } from '../object-utils'
|
|
2
|
+
|
|
3
|
+
const circularArray1: any[] = []
|
|
4
|
+
circularArray1.push(circularArray1)
|
|
5
|
+
const circularArray2: any[] = []
|
|
6
|
+
circularArray2.push(circularArray2)
|
|
7
|
+
|
|
8
|
+
function f1() {}
|
|
9
|
+
function f2() {}
|
|
10
|
+
|
|
11
|
+
describe('object-utils', () => {
|
|
12
|
+
describe('isDeepEqual', () => {
|
|
13
|
+
it.each([
|
|
14
|
+
[true, { a: 1, b: 2 }, { a: 1, b: 2 }],
|
|
15
|
+
[true, { a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }],
|
|
16
|
+
[false, { a: 1, b: 2 }, { a: 1, b: 3 }],
|
|
17
|
+
[false, { a: 1, b: 2 }, { a: 1 }],
|
|
18
|
+
[true, 'a', 'a'],
|
|
19
|
+
[false, 'a', 'b'],
|
|
20
|
+
[false, 1, 2],
|
|
21
|
+
[true, 0, -0],
|
|
22
|
+
[false, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
|
23
|
+
[false, 1, '1'],
|
|
24
|
+
[false, Number.NaN, Number.NaN],
|
|
25
|
+
[true, null, null],
|
|
26
|
+
[false, undefined, null],
|
|
27
|
+
[true, [], []],
|
|
28
|
+
[true, [[[[]]]], [[[[]]]]],
|
|
29
|
+
[false, [[[[]]]], [[[[[]]]]]],
|
|
30
|
+
[true, [1, 2, 3], [1, 2, 3]],
|
|
31
|
+
[false, [1, 2, 3], [1, 2, 4]],
|
|
32
|
+
[true, { a: circularArray1 }, { a: circularArray1 }],
|
|
33
|
+
[true, { a: circularArray1 }, { a: circularArray2 }],
|
|
34
|
+
[true, circularArray1, [circularArray1]],
|
|
35
|
+
[true, f1, f1],
|
|
36
|
+
[false, f1, f2],
|
|
37
|
+
])('returns %s for %s and %s', (expected, obj1, obj2) => {
|
|
38
|
+
expect(isDeepEqual(obj1, obj2)).toBe(expected)
|
|
39
|
+
expect(isDeepEqual(obj2, obj1)).toBe(expected)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Deeply compares two objects for equality.
|
|
2
|
+
// Use a WeakMap to keep track of visited objects to avoid infinite recursion.
|
|
3
|
+
// WeakMap is supported in IE11, see https://caniuse.com/?search=JavaScript%20WeakMap
|
|
4
|
+
|
|
5
|
+
export function isDeepEqual(obj1: any, obj2: any, visited = new WeakMap()): boolean {
|
|
6
|
+
if (obj1 === obj2) {
|
|
7
|
+
return true
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (visited.has(obj1) && visited.get(obj1) === obj2) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
visited.set(obj1, obj2)
|
|
18
|
+
|
|
19
|
+
const keys1 = Object.keys(obj1)
|
|
20
|
+
const keys2 = Object.keys(obj2)
|
|
21
|
+
|
|
22
|
+
if (keys1.length !== keys2.length) {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const key of keys1) {
|
|
27
|
+
if (!keys2.includes(key)) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
if (!isDeepEqual(obj1[key], obj2[key], visited)) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// from a comment on http://dbj.org/dbj/?p=286
|
|
2
|
+
// fails on only one very rare and deliberate custom object:
|
|
3
|
+
// let bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
|
|
4
|
+
export const isFunction = function (f: any): f is (...args: any[]) => any {
|
|
5
|
+
// eslint-disable-next-line posthog-js/no-direct-function-check
|
|
6
|
+
return typeof f === 'function'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const isUndefined = function (x: unknown): x is undefined {
|
|
10
|
+
return x === void 0
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const isNull = function (x: unknown): x is null {
|
|
14
|
+
// eslint-disable-next-line posthog-js/no-direct-null-check
|
|
15
|
+
return x === null
|
|
16
|
+
}
|