@posthog/react 1.2.1 → 1.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/react",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Provides components and hooks for React integrations of PostHog.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,8 +14,8 @@
14
14
  "module": "dist/esm/index.js",
15
15
  "types": "dist/types/index.d.ts",
16
16
  "files": [
17
- "dist/*",
18
- "README.md"
17
+ "dist",
18
+ "src"
19
19
  ],
20
20
  "peerDependencies": {
21
21
  "@types/react": ">=16.8.0",
@@ -48,7 +48,7 @@
48
48
  "tslib": "^2.5.0",
49
49
  "typescript": "^5.5.4",
50
50
  "@posthog-tooling/rollup-utils": "1.0.0",
51
- "posthog-js": "1.269.0"
51
+ "posthog-js": "1.270.1"
52
52
  },
53
53
  "scripts": {
54
54
  "clean": "rimraf dist",
@@ -0,0 +1,88 @@
1
+ import React, { FunctionComponent } from 'react'
2
+ import { PostHogContext } from '../context'
3
+ import { isFunction } from '../utils/type-utils'
4
+
5
+ export type Properties = Record<string, any>
6
+
7
+ export type PostHogErrorBoundaryFallbackProps = {
8
+ error: unknown
9
+ exceptionEvent: unknown
10
+ componentStack: string
11
+ }
12
+
13
+ export type PostHogErrorBoundaryProps = {
14
+ children?: React.ReactNode | (() => React.ReactNode)
15
+ fallback?: React.ReactNode | FunctionComponent<PostHogErrorBoundaryFallbackProps>
16
+ additionalProperties?: Properties | ((error: unknown) => Properties)
17
+ }
18
+
19
+ type PostHogErrorBoundaryState = {
20
+ componentStack: string | null
21
+ exceptionEvent: unknown
22
+ error: unknown
23
+ }
24
+
25
+ const INITIAL_STATE: PostHogErrorBoundaryState = {
26
+ componentStack: null,
27
+ exceptionEvent: null,
28
+ error: null,
29
+ }
30
+
31
+ export const __POSTHOG_ERROR_MESSAGES = {
32
+ INVALID_FALLBACK:
33
+ '[PostHog.js][PostHogErrorBoundary] Invalid fallback prop, provide a valid React element or a function that returns a valid React element.',
34
+ }
35
+
36
+ export class PostHogErrorBoundary extends React.Component<PostHogErrorBoundaryProps, PostHogErrorBoundaryState> {
37
+ static contextType = PostHogContext
38
+
39
+ constructor(props: PostHogErrorBoundaryProps) {
40
+ super(props)
41
+ this.state = INITIAL_STATE
42
+ }
43
+
44
+ componentDidCatch(error: unknown, errorInfo: React.ErrorInfo) {
45
+ //eslint-disable-next-line react/prop-types
46
+ const { additionalProperties } = this.props
47
+ let currentProperties
48
+ if (isFunction(additionalProperties)) {
49
+ currentProperties = additionalProperties(error)
50
+ } else if (typeof additionalProperties === 'object') {
51
+ currentProperties = additionalProperties
52
+ }
53
+ const { client } = this.context
54
+ const exceptionEvent = client.captureException(error, currentProperties)
55
+
56
+ const { componentStack } = errorInfo
57
+ this.setState({
58
+ error,
59
+ componentStack,
60
+ exceptionEvent,
61
+ })
62
+ }
63
+
64
+ public render(): React.ReactNode {
65
+ //eslint-disable-next-line react/prop-types
66
+ const { children, fallback } = this.props
67
+ const state = this.state
68
+
69
+ if (state.componentStack == null) {
70
+ return isFunction(children) ? children() : children
71
+ }
72
+
73
+ const element = isFunction(fallback)
74
+ ? (React.createElement(fallback, {
75
+ error: state.error,
76
+ componentStack: state.componentStack,
77
+ exceptionEvent: state.exceptionEvent,
78
+ }) as React.ReactNode)
79
+ : fallback
80
+
81
+ if (React.isValidElement(element)) {
82
+ return element as React.ReactElement
83
+ }
84
+ //eslint-disable-next-line no-console
85
+ console.warn(__POSTHOG_ERROR_MESSAGES.INVALID_FALLBACK)
86
+ return <></>
87
+ }
88
+ }
@@ -0,0 +1,174 @@
1
+ import { useFeatureFlagPayload, useFeatureFlagVariantKey, usePostHog } from '../hooks'
2
+ import React, { Children, ReactNode, useCallback, useEffect, useRef } from 'react'
3
+ import { PostHog } from '../context'
4
+ import { isFunction, isNull, isUndefined } from '../utils/type-utils'
5
+
6
+ export type PostHogFeatureProps = React.HTMLProps<HTMLDivElement> & {
7
+ flag: string
8
+ children: React.ReactNode | ((payload: any) => React.ReactNode)
9
+ fallback?: React.ReactNode
10
+ match?: string | boolean
11
+ visibilityObserverOptions?: IntersectionObserverInit
12
+ trackInteraction?: boolean
13
+ trackView?: boolean
14
+ }
15
+
16
+ export function PostHogFeature({
17
+ flag,
18
+ match,
19
+ children,
20
+ fallback,
21
+ visibilityObserverOptions,
22
+ trackInteraction,
23
+ trackView,
24
+ ...props
25
+ }: PostHogFeatureProps): JSX.Element | null {
26
+ const payload = useFeatureFlagPayload(flag)
27
+ const variant = useFeatureFlagVariantKey(flag)
28
+
29
+ const shouldTrackInteraction = trackInteraction ?? true
30
+ const shouldTrackView = trackView ?? true
31
+
32
+ if (isUndefined(match) || variant === match) {
33
+ const childNode: React.ReactNode = isFunction(children) ? children(payload) : children
34
+ return (
35
+ <VisibilityAndClickTrackers
36
+ flag={flag}
37
+ options={visibilityObserverOptions}
38
+ trackInteraction={shouldTrackInteraction}
39
+ trackView={shouldTrackView}
40
+ {...props}
41
+ >
42
+ {childNode}
43
+ </VisibilityAndClickTrackers>
44
+ )
45
+ }
46
+ return <>{fallback}</>
47
+ }
48
+
49
+ function captureFeatureInteraction({
50
+ flag,
51
+ posthog,
52
+ flagVariant,
53
+ }: {
54
+ flag: string
55
+ posthog: PostHog
56
+ flagVariant?: string | boolean
57
+ }) {
58
+ const properties: Record<string, any> = {
59
+ feature_flag: flag,
60
+ $set: { [`$feature_interaction/${flag}`]: flagVariant ?? true },
61
+ }
62
+ if (typeof flagVariant === 'string') {
63
+ properties.feature_flag_variant = flagVariant
64
+ }
65
+ posthog.capture('$feature_interaction', properties)
66
+ }
67
+
68
+ function captureFeatureView({
69
+ flag,
70
+ posthog,
71
+ flagVariant,
72
+ }: {
73
+ flag: string
74
+ posthog: PostHog
75
+ flagVariant?: string | boolean
76
+ }) {
77
+ const properties: Record<string, any> = {
78
+ feature_flag: flag,
79
+ $set: { [`$feature_view/${flag}`]: flagVariant ?? true },
80
+ }
81
+ if (typeof flagVariant === 'string') {
82
+ properties.feature_flag_variant = flagVariant
83
+ }
84
+ posthog.capture('$feature_view', properties)
85
+ }
86
+
87
+ function VisibilityAndClickTracker({
88
+ flag,
89
+ children,
90
+ onIntersect,
91
+ onClick,
92
+ trackView,
93
+ options,
94
+ ...props
95
+ }: {
96
+ flag: string
97
+ children: React.ReactNode
98
+ onIntersect: (entry: IntersectionObserverEntry) => void
99
+ onClick: () => void
100
+ trackView: boolean
101
+ options?: IntersectionObserverInit
102
+ }): JSX.Element {
103
+ const ref = useRef<HTMLDivElement>(null)
104
+ const posthog = usePostHog()
105
+
106
+ useEffect(() => {
107
+ if (isNull(ref.current) || !trackView) return
108
+
109
+ // eslint-disable-next-line compat/compat
110
+ const observer = new IntersectionObserver(([entry]) => onIntersect(entry), {
111
+ threshold: 0.1,
112
+ ...options,
113
+ })
114
+ observer.observe(ref.current)
115
+ return () => observer.disconnect()
116
+ }, [flag, options, posthog, ref, trackView, onIntersect])
117
+
118
+ return (
119
+ <div ref={ref} {...props} onClick={onClick}>
120
+ {children}
121
+ </div>
122
+ )
123
+ }
124
+
125
+ function VisibilityAndClickTrackers({
126
+ flag,
127
+ children,
128
+ trackInteraction,
129
+ trackView,
130
+ options,
131
+ ...props
132
+ }: {
133
+ flag: string
134
+ children: React.ReactNode
135
+ trackInteraction: boolean
136
+ trackView: boolean
137
+ options?: IntersectionObserverInit
138
+ }): JSX.Element {
139
+ const clickTrackedRef = useRef(false)
140
+ const visibilityTrackedRef = useRef(false)
141
+ const posthog = usePostHog()
142
+ const variant = useFeatureFlagVariantKey(flag)
143
+
144
+ const cachedOnClick = useCallback(() => {
145
+ if (!clickTrackedRef.current && trackInteraction) {
146
+ captureFeatureInteraction({ flag, posthog, flagVariant: variant })
147
+ clickTrackedRef.current = true
148
+ }
149
+ }, [flag, posthog, trackInteraction, variant])
150
+
151
+ const onIntersect = (entry: IntersectionObserverEntry) => {
152
+ if (!visibilityTrackedRef.current && entry.isIntersecting) {
153
+ captureFeatureView({ flag, posthog, flagVariant: variant })
154
+ visibilityTrackedRef.current = true
155
+ }
156
+ }
157
+
158
+ const trackedChildren = Children.map(children, (child: ReactNode) => {
159
+ return (
160
+ <VisibilityAndClickTracker
161
+ flag={flag}
162
+ onClick={cachedOnClick}
163
+ onIntersect={onIntersect}
164
+ trackView={trackView}
165
+ options={options}
166
+ {...props}
167
+ >
168
+ {child}
169
+ </VisibilityAndClickTracker>
170
+ )
171
+ })
172
+
173
+ return <>{trackedChildren}</>
174
+ }
@@ -0,0 +1,109 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import * as React from 'react'
4
+ import { render } from '@testing-library/react'
5
+ import { __POSTHOG_ERROR_MESSAGES, PostHogErrorBoundary } from '../PostHogErrorBoundary'
6
+ import posthog from 'posthog-js'
7
+
8
+ describe('PostHogErrorBoundary component', () => {
9
+ mockFunction(console, 'error')
10
+ mockFunction(console, 'warn')
11
+ mockFunction(posthog, 'captureException')
12
+
13
+ given('render_with_error', () => (props) => render(<RenderWithError {...props} />))
14
+ given('render_without_error', () => (props) => render(<RenderWithoutError {...props} />))
15
+
16
+ it('should call captureException with error message', () => {
17
+ const { container } = given.render_with_error({ message: 'Test error', fallback: <div></div> })
18
+ expect(posthog.captureException).toHaveBeenCalledWith(new Error('Test error'), undefined)
19
+ expect(container.innerHTML).toBe('<div></div>')
20
+ expect(console.error).toHaveBeenCalledTimes(2)
21
+ })
22
+
23
+ it('should warn user when fallback is null', () => {
24
+ const { container } = given.render_with_error({ fallback: null })
25
+ expect(posthog.captureException).toHaveBeenCalledWith(new Error('Error'), undefined)
26
+ expect(container.innerHTML).toBe('')
27
+ expect(console.warn).toHaveBeenCalledWith(__POSTHOG_ERROR_MESSAGES.INVALID_FALLBACK)
28
+ })
29
+
30
+ it('should warn user when fallback is a string', () => {
31
+ const { container } = given.render_with_error({ fallback: 'hello' })
32
+ expect(posthog.captureException).toHaveBeenCalledWith(new Error('Error'), undefined)
33
+ expect(container.innerHTML).toBe('')
34
+ expect(console.warn).toHaveBeenCalledWith(__POSTHOG_ERROR_MESSAGES.INVALID_FALLBACK)
35
+ })
36
+
37
+ it('should add additional properties before sending event (as object)', () => {
38
+ const props = { team_id: '1234' }
39
+ given.render_with_error({ message: 'Kaboom', additionalProperties: props })
40
+ expect(posthog.captureException).toHaveBeenCalledWith(new Error('Kaboom'), props)
41
+ })
42
+
43
+ it('should add additional properties before sending event (as function)', () => {
44
+ const props = { team_id: '1234' }
45
+ given.render_with_error({
46
+ message: 'Kaboom',
47
+ additionalProperties: (err) => {
48
+ expect(err.message).toBe('Kaboom')
49
+ return props
50
+ },
51
+ })
52
+ expect(posthog.captureException).toHaveBeenCalledWith(new Error('Kaboom'), props)
53
+ })
54
+
55
+ it('should render children without errors', () => {
56
+ const { container } = given.render_without_error()
57
+ expect(container.innerHTML).toBe('<div>Amazing content</div>')
58
+ })
59
+ })
60
+
61
+ describe('captureException processing', () => {
62
+ mockFunction(console, 'error')
63
+ mockFunction(console, 'warn')
64
+ mockFunction(posthog, 'capture')
65
+
66
+ given('render_with_error', () => (props) => render(<RenderWithError {...props} />))
67
+
68
+ it('should call capture with a stacktrace', () => {
69
+ given.render_with_error({ message: 'Kaboom', fallback: <div></div>, additionalProperties: {} })
70
+ const captureCalls = posthog.capture.mock.calls
71
+ expect(captureCalls.length).toBe(1)
72
+ const exceptionList = captureCalls[0][1].$exception_list
73
+ expect(exceptionList.length).toBe(1)
74
+ const stacktrace = exceptionList[0].stacktrace
75
+ expect(stacktrace.frames.length).toBe(44)
76
+ })
77
+ })
78
+
79
+ function mockFunction(object, funcName) {
80
+ const originalFunc = object[funcName]
81
+
82
+ beforeEach(() => {
83
+ object[funcName] = jest.fn()
84
+ })
85
+
86
+ afterEach(() => {
87
+ object[funcName] = originalFunc
88
+ })
89
+ }
90
+
91
+ function ComponentWithError({ message }) {
92
+ throw new Error(message)
93
+ }
94
+
95
+ function RenderWithError({ message = 'Error', fallback, additionalProperties }) {
96
+ return (
97
+ <PostHogErrorBoundary fallback={fallback} additionalProperties={additionalProperties}>
98
+ <ComponentWithError message={message} />
99
+ </PostHogErrorBoundary>
100
+ )
101
+ }
102
+
103
+ function RenderWithoutError({ additionalProperties }) {
104
+ return (
105
+ <PostHogErrorBoundary fallback={<div></div>} additionalProperties={additionalProperties}>
106
+ <div>Amazing content</div>
107
+ </PostHogErrorBoundary>
108
+ )
109
+ }
@@ -0,0 +1,298 @@
1
+ import * as React from 'react'
2
+ import { useState } from 'react'
3
+ import { render, screen, fireEvent } from '@testing-library/react'
4
+ import { PostHogProvider } from '../../context'
5
+ import { PostHogFeature } from '../'
6
+ import '@testing-library/jest-dom'
7
+
8
+ const FEATURE_FLAG_STATUS = {
9
+ multivariate_feature: 'string-value',
10
+ example_feature_payload: 'test',
11
+ test: true,
12
+ test_false: false,
13
+ }
14
+
15
+ const FEATURE_FLAG_PAYLOADS = {
16
+ example_feature_payload: {
17
+ id: 1,
18
+ name: 'example_feature_1_payload',
19
+ key: 'example_feature_1_payload',
20
+ },
21
+ }
22
+
23
+ describe('PostHogFeature component', () => {
24
+ given('featureFlag', () => 'test')
25
+ given('matchValue', () => true)
26
+ given(
27
+ 'render',
28
+ () => () =>
29
+ render(
30
+ <PostHogProvider client={given.posthog}>
31
+ <PostHogFeature flag={given.featureFlag} match={given.matchValue}>
32
+ <div data-testid="helloDiv">Hello</div>
33
+ </PostHogFeature>
34
+ </PostHogProvider>
35
+ )
36
+ )
37
+ given('posthog', () => ({
38
+ isFeatureEnabled: (flag) => !!FEATURE_FLAG_STATUS[flag],
39
+ getFeatureFlag: (flag) => FEATURE_FLAG_STATUS[flag],
40
+ getFeatureFlagPayload: (flag) => FEATURE_FLAG_PAYLOADS[flag],
41
+ onFeatureFlags: (callback) => {
42
+ const activeFlags = []
43
+ for (const flag in FEATURE_FLAG_STATUS) {
44
+ if (FEATURE_FLAG_STATUS[flag]) {
45
+ activeFlags.push(flag)
46
+ }
47
+ }
48
+ callback(activeFlags)
49
+ return () => {}
50
+ },
51
+ capture: jest.fn(),
52
+ }))
53
+
54
+ beforeEach(() => {
55
+ // IntersectionObserver isn't available in test environment
56
+ const mockIntersectionObserver = jest.fn()
57
+ mockIntersectionObserver.mockReturnValue({
58
+ observe: () => null,
59
+ unobserve: () => null,
60
+ disconnect: () => null,
61
+ })
62
+
63
+ // eslint-disable-next-line compat/compat
64
+ window.IntersectionObserver = mockIntersectionObserver
65
+ })
66
+
67
+ it('should track interactions with the feature component', () => {
68
+ given.render()
69
+
70
+ fireEvent.click(screen.getByTestId('helloDiv'))
71
+ expect(given.posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
72
+ feature_flag: 'test',
73
+ $set: { '$feature_interaction/test': true },
74
+ })
75
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
76
+ })
77
+
78
+ it('should not fire for every interaction with the feature component', () => {
79
+ given.render()
80
+
81
+ fireEvent.click(screen.getByTestId('helloDiv'))
82
+ expect(given.posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
83
+ feature_flag: 'test',
84
+ $set: { '$feature_interaction/test': true },
85
+ })
86
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
87
+
88
+ fireEvent.click(screen.getByTestId('helloDiv'))
89
+ fireEvent.click(screen.getByTestId('helloDiv'))
90
+ fireEvent.click(screen.getByTestId('helloDiv'))
91
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
92
+ })
93
+
94
+ it('should track an interaction with each child node of the feature component', () => {
95
+ given(
96
+ 'render',
97
+ () => () =>
98
+ render(
99
+ <PostHogProvider client={given.posthog}>
100
+ <PostHogFeature flag={given.featureFlag} match={given.matchValue}>
101
+ <div data-testid="helloDiv">Hello</div>
102
+ <div data-testid="worldDiv">World!</div>
103
+ </PostHogFeature>
104
+ </PostHogProvider>
105
+ )
106
+ )
107
+ given.render()
108
+
109
+ fireEvent.click(screen.getByTestId('helloDiv'))
110
+ fireEvent.click(screen.getByTestId('helloDiv'))
111
+ fireEvent.click(screen.getByTestId('worldDiv'))
112
+ fireEvent.click(screen.getByTestId('worldDiv'))
113
+ fireEvent.click(screen.getByTestId('worldDiv'))
114
+ expect(given.posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
115
+ feature_flag: 'test',
116
+ $set: { '$feature_interaction/test': true },
117
+ })
118
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
119
+ })
120
+
121
+ it('should not fire events when interaction is disabled', () => {
122
+ given(
123
+ 'render',
124
+ () => () =>
125
+ render(
126
+ <PostHogProvider client={given.posthog}>
127
+ <PostHogFeature flag={given.featureFlag} match={given.matchValue} trackInteraction={false}>
128
+ <div data-testid="helloDiv">Hello</div>
129
+ </PostHogFeature>
130
+ </PostHogProvider>
131
+ )
132
+ )
133
+ given.render()
134
+
135
+ fireEvent.click(screen.getByTestId('helloDiv'))
136
+ expect(given.posthog.capture).not.toHaveBeenCalled()
137
+
138
+ fireEvent.click(screen.getByTestId('helloDiv'))
139
+ fireEvent.click(screen.getByTestId('helloDiv'))
140
+ fireEvent.click(screen.getByTestId('helloDiv'))
141
+ expect(given.posthog.capture).not.toHaveBeenCalled()
142
+ })
143
+
144
+ it('should fire events when interaction is disabled but re-enabled after', () => {
145
+ const DynamicUpdateComponent = () => {
146
+ const [trackInteraction, setTrackInteraction] = useState(false)
147
+
148
+ return (
149
+ <>
150
+ <div
151
+ data-testid="clicker"
152
+ onClick={() => {
153
+ setTrackInteraction(true)
154
+ }}
155
+ >
156
+ Click me
157
+ </div>
158
+ <PostHogFeature
159
+ flag={given.featureFlag}
160
+ match={given.matchValue}
161
+ trackInteraction={trackInteraction}
162
+ >
163
+ <div data-testid="helloDiv">Hello</div>
164
+ </PostHogFeature>
165
+ </>
166
+ )
167
+ }
168
+
169
+ given(
170
+ 'render',
171
+ () => () =>
172
+ render(
173
+ <PostHogProvider client={given.posthog}>
174
+ <DynamicUpdateComponent />
175
+ </PostHogProvider>
176
+ )
177
+ )
178
+ given.render()
179
+
180
+ fireEvent.click(screen.getByTestId('helloDiv'))
181
+ expect(given.posthog.capture).not.toHaveBeenCalled()
182
+
183
+ fireEvent.click(screen.getByTestId('clicker'))
184
+ fireEvent.click(screen.getByTestId('helloDiv'))
185
+ fireEvent.click(screen.getByTestId('helloDiv'))
186
+ expect(given.posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
187
+ feature_flag: 'test',
188
+ $set: { '$feature_interaction/test': true },
189
+ })
190
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
191
+ })
192
+
193
+ it('should not show the feature component if the flag is not enabled', () => {
194
+ given('featureFlag', () => 'test_false')
195
+ given.render()
196
+
197
+ expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
198
+ expect(given.posthog.capture).not.toHaveBeenCalled()
199
+
200
+ // check if any elements are found
201
+ const allTags = screen.queryAllByText(/.*/)
202
+
203
+ // Assert that no random elements are found
204
+ expect(allTags.length).toEqual(2)
205
+ expect(allTags[0].tagName).toEqual('BODY')
206
+ expect(allTags[1].tagName).toEqual('DIV')
207
+ })
208
+
209
+ it('should fallback when provided', () => {
210
+ given('featureFlag', () => 'test_false')
211
+ given(
212
+ 'render',
213
+ () => () =>
214
+ render(
215
+ <PostHogProvider client={given.posthog}>
216
+ <PostHogFeature
217
+ flag={given.featureFlag}
218
+ match={given.matchValue}
219
+ fallback={<div data-testid="nope">Nope</div>}
220
+ >
221
+ <div data-testid="helloDiv">Hello</div>
222
+ </PostHogFeature>
223
+ </PostHogProvider>
224
+ )
225
+ )
226
+ given.render()
227
+
228
+ expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
229
+ expect(given.posthog.capture).not.toHaveBeenCalled()
230
+
231
+ fireEvent.click(screen.getByTestId('nope'))
232
+ expect(given.posthog.capture).not.toHaveBeenCalled()
233
+ })
234
+
235
+ it('should handle showing multivariate flags with bool match', () => {
236
+ given('featureFlag', () => 'multivariate_feature')
237
+ given('matchValue', () => true)
238
+
239
+ given.render()
240
+
241
+ expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
242
+ expect(given.posthog.capture).not.toHaveBeenCalled()
243
+ })
244
+
245
+ it('should handle showing multivariate flags with incorrect match', () => {
246
+ given('featureFlag', () => 'multivariate_feature')
247
+ given('matchValue', () => 'string-valueCXCC')
248
+
249
+ given.render()
250
+
251
+ expect(screen.queryByTestId('helloDiv')).not.toBeInTheDocument()
252
+ expect(given.posthog.capture).not.toHaveBeenCalled()
253
+ })
254
+
255
+ it('should handle showing multivariate flags', () => {
256
+ given('featureFlag', () => 'multivariate_feature')
257
+ given('matchValue', () => 'string-value')
258
+
259
+ given.render()
260
+
261
+ expect(screen.queryByTestId('helloDiv')).toBeInTheDocument()
262
+ expect(given.posthog.capture).not.toHaveBeenCalled()
263
+
264
+ fireEvent.click(screen.getByTestId('helloDiv'))
265
+ expect(given.posthog.capture).toHaveBeenCalledWith('$feature_interaction', {
266
+ feature_flag: 'multivariate_feature',
267
+ feature_flag_variant: 'string-value',
268
+ $set: { '$feature_interaction/multivariate_feature': 'string-value' },
269
+ })
270
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
271
+ })
272
+
273
+ it('should handle payload flags', () => {
274
+ given('featureFlag', () => 'example_feature_payload')
275
+ given('matchValue', () => 'test')
276
+ given(
277
+ 'render',
278
+ () => () =>
279
+ render(
280
+ <PostHogProvider client={given.posthog}>
281
+ <PostHogFeature flag={given.featureFlag} match={given.matchValue}>
282
+ {(payload) => {
283
+ return <div data-testid={`hi_${payload.name}`}>Hullo</div>
284
+ }}
285
+ </PostHogFeature>
286
+ </PostHogProvider>
287
+ )
288
+ )
289
+
290
+ given.render()
291
+
292
+ expect(screen.queryByTestId('hi_example_feature_1_payload')).toBeInTheDocument()
293
+ expect(given.posthog.capture).not.toHaveBeenCalled()
294
+
295
+ fireEvent.click(screen.getByTestId('hi_example_feature_1_payload'))
296
+ expect(given.posthog.capture).toHaveBeenCalledTimes(1)
297
+ })
298
+ })