@posthog/core 1.0.2 → 1.2.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.
Files changed (204) hide show
  1. package/dist/error-tracking/chunk-ids.d.ts +5 -0
  2. package/dist/error-tracking/chunk-ids.d.ts.map +1 -0
  3. package/dist/error-tracking/chunk-ids.js +68 -0
  4. package/dist/error-tracking/chunk-ids.mjs +34 -0
  5. package/dist/error-tracking/coercers/dom-exception-coercer.d.ts +10 -0
  6. package/dist/error-tracking/coercers/dom-exception-coercer.d.ts.map +1 -0
  7. package/dist/error-tracking/coercers/dom-exception-coercer.js +65 -0
  8. package/dist/error-tracking/coercers/dom-exception-coercer.mjs +31 -0
  9. package/dist/error-tracking/coercers/error-coercer.d.ts +9 -0
  10. package/dist/error-tracking/coercers/error-coercer.d.ts.map +1 -0
  11. package/dist/error-tracking/coercers/error-coercer.js +61 -0
  12. package/dist/error-tracking/coercers/error-coercer.mjs +27 -0
  13. package/dist/error-tracking/coercers/error-event-coercer.d.ts +7 -0
  14. package/dist/error-tracking/coercers/error-event-coercer.d.ts.map +1 -0
  15. package/dist/error-tracking/coercers/error-event-coercer.js +52 -0
  16. package/dist/error-tracking/coercers/error-event-coercer.mjs +18 -0
  17. package/dist/error-tracking/coercers/event-coercer.d.ts +6 -0
  18. package/dist/error-tracking/coercers/event-coercer.d.ts.map +1 -0
  19. package/dist/error-tracking/coercers/event-coercer.js +51 -0
  20. package/dist/error-tracking/coercers/event-coercer.mjs +17 -0
  21. package/dist/error-tracking/coercers/index.d.ts +9 -0
  22. package/dist/error-tracking/coercers/index.d.ts.map +1 -0
  23. package/dist/error-tracking/coercers/index.js +123 -0
  24. package/dist/error-tracking/coercers/index.mjs +8 -0
  25. package/dist/error-tracking/coercers/object-coercer.d.ts +14 -0
  26. package/dist/error-tracking/coercers/object-coercer.d.ts.map +1 -0
  27. package/dist/error-tracking/coercers/object-coercer.js +85 -0
  28. package/dist/error-tracking/coercers/object-coercer.mjs +51 -0
  29. package/dist/error-tracking/coercers/primitive-coercer.d.ts +7 -0
  30. package/dist/error-tracking/coercers/primitive-coercer.d.ts.map +1 -0
  31. package/dist/error-tracking/coercers/primitive-coercer.js +49 -0
  32. package/dist/error-tracking/coercers/primitive-coercer.mjs +15 -0
  33. package/dist/error-tracking/coercers/promise-rejection-event.d.ts +7 -0
  34. package/dist/error-tracking/coercers/promise-rejection-event.d.ts.map +1 -0
  35. package/dist/error-tracking/coercers/promise-rejection-event.js +59 -0
  36. package/dist/error-tracking/coercers/promise-rejection-event.mjs +25 -0
  37. package/dist/error-tracking/coercers/string-coercer.d.ts +7 -0
  38. package/dist/error-tracking/coercers/string-coercer.d.ts.map +1 -0
  39. package/dist/error-tracking/coercers/string-coercer.js +63 -0
  40. package/dist/error-tracking/coercers/string-coercer.mjs +29 -0
  41. package/dist/error-tracking/coercers/utils.d.ts +8 -0
  42. package/dist/error-tracking/coercers/utils.d.ts.map +1 -0
  43. package/dist/error-tracking/coercers/utils.js +55 -0
  44. package/dist/error-tracking/coercers/utils.mjs +18 -0
  45. package/dist/error-tracking/error-properties-builder.d.ts +18 -0
  46. package/dist/error-tracking/error-properties-builder.d.ts.map +1 -0
  47. package/dist/error-tracking/error-properties-builder.js +152 -0
  48. package/dist/error-tracking/error-properties-builder.mjs +118 -0
  49. package/dist/error-tracking/index.d.ts +6 -0
  50. package/dist/error-tracking/index.d.ts.map +1 -0
  51. package/dist/error-tracking/index.js +87 -0
  52. package/dist/error-tracking/index.mjs +4 -0
  53. package/dist/error-tracking/parsers/base.d.ts +9 -0
  54. package/dist/error-tracking/parsers/base.d.ts.map +1 -0
  55. package/dist/error-tracking/parsers/base.js +71 -0
  56. package/dist/error-tracking/parsers/base.mjs +19 -0
  57. package/dist/error-tracking/parsers/chrome.d.ts +3 -0
  58. package/dist/error-tracking/parsers/chrome.d.ts.map +1 -0
  59. package/dist/error-tracking/parsers/chrome.js +61 -0
  60. package/dist/error-tracking/parsers/chrome.mjs +27 -0
  61. package/dist/error-tracking/parsers/gecko.d.ts +3 -0
  62. package/dist/error-tracking/parsers/gecko.d.ts.map +1 -0
  63. package/dist/error-tracking/parsers/gecko.js +58 -0
  64. package/dist/error-tracking/parsers/gecko.mjs +24 -0
  65. package/dist/error-tracking/parsers/index.d.ts +9 -0
  66. package/dist/error-tracking/parsers/index.d.ts.map +1 -0
  67. package/dist/error-tracking/parsers/index.js +99 -0
  68. package/dist/error-tracking/parsers/index.mjs +44 -0
  69. package/dist/error-tracking/parsers/node.d.ts +3 -0
  70. package/dist/error-tracking/parsers/node.d.ts.map +1 -0
  71. package/dist/error-tracking/parsers/node.js +99 -0
  72. package/dist/error-tracking/parsers/node.mjs +65 -0
  73. package/dist/error-tracking/parsers/opera.d.ts +4 -0
  74. package/dist/error-tracking/parsers/opera.d.ts.map +1 -0
  75. package/dist/error-tracking/parsers/opera.js +49 -0
  76. package/dist/error-tracking/parsers/opera.mjs +12 -0
  77. package/dist/error-tracking/parsers/react-native.d.ts +1 -0
  78. package/dist/error-tracking/parsers/react-native.d.ts.map +1 -0
  79. package/dist/error-tracking/parsers/react-native.js +5 -0
  80. package/dist/error-tracking/parsers/react-native.mjs +0 -0
  81. package/dist/error-tracking/parsers/safari.d.ts +22 -0
  82. package/dist/error-tracking/parsers/safari.d.ts.map +1 -0
  83. package/dist/error-tracking/parsers/safari.js +47 -0
  84. package/dist/error-tracking/parsers/safari.mjs +13 -0
  85. package/dist/error-tracking/parsers/winjs.d.ts +3 -0
  86. package/dist/error-tracking/parsers/winjs.d.ts.map +1 -0
  87. package/dist/error-tracking/parsers/winjs.js +41 -0
  88. package/dist/error-tracking/parsers/winjs.mjs +7 -0
  89. package/dist/error-tracking/types.d.ts +87 -0
  90. package/dist/error-tracking/types.d.ts.map +1 -0
  91. package/dist/error-tracking/types.js +43 -0
  92. package/dist/error-tracking/types.mjs +9 -0
  93. package/dist/error-tracking/utils.d.ts +13 -0
  94. package/dist/error-tracking/utils.d.ts.map +1 -0
  95. package/dist/error-tracking/utils.js +57 -0
  96. package/dist/error-tracking/utils.mjs +23 -0
  97. package/dist/eventemitter.js +4 -4
  98. package/dist/eventemitter.mjs +4 -4
  99. package/dist/featureFlagUtils.js +20 -45
  100. package/dist/featureFlagUtils.mjs +20 -45
  101. package/dist/gzip.js +1 -2
  102. package/dist/gzip.mjs +1 -2
  103. package/dist/index.d.ts +5 -255
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +61 -1223
  106. package/dist/index.mjs +6 -1190
  107. package/dist/posthog-core-stateless.d.ts +204 -0
  108. package/dist/posthog-core-stateless.d.ts.map +1 -0
  109. package/dist/posthog-core-stateless.js +675 -0
  110. package/dist/posthog-core-stateless.mjs +632 -0
  111. package/dist/posthog-core.d.ts +171 -0
  112. package/dist/posthog-core.d.ts.map +1 -0
  113. package/dist/posthog-core.js +554 -0
  114. package/dist/posthog-core.mjs +520 -0
  115. package/dist/testing/PostHogCoreTestClient.d.ts +2 -1
  116. package/dist/testing/PostHogCoreTestClient.d.ts.map +1 -1
  117. package/dist/testing/PostHogCoreTestClient.js +9 -11
  118. package/dist/testing/PostHogCoreTestClient.mjs +8 -10
  119. package/dist/testing/test-utils.d.ts +2 -0
  120. package/dist/testing/test-utils.d.ts.map +1 -1
  121. package/dist/testing/test-utils.js +13 -1
  122. package/dist/testing/test-utils.mjs +11 -2
  123. package/dist/utils/bucketed-rate-limiter.d.ts.map +1 -1
  124. package/dist/utils/bucketed-rate-limiter.js +8 -12
  125. package/dist/utils/bucketed-rate-limiter.mjs +8 -12
  126. package/dist/utils/index.js +3 -3
  127. package/dist/utils/index.mjs +3 -3
  128. package/dist/utils/number-utils.d.ts.map +1 -1
  129. package/dist/utils/type-utils.d.ts +8 -1
  130. package/dist/utils/type-utils.d.ts.map +1 -1
  131. package/dist/utils/type-utils.js +61 -6
  132. package/dist/utils/type-utils.mjs +36 -2
  133. package/dist/vendor/uuidv7.js +12 -16
  134. package/dist/vendor/uuidv7.mjs +12 -16
  135. package/package.json +10 -4
  136. package/src/__tests__/featureFlagUtils.spec.ts +427 -0
  137. package/src/__tests__/gzip.spec.ts +69 -0
  138. package/src/__tests__/posthog.ai.spec.ts +110 -0
  139. package/src/__tests__/posthog.capture.spec.ts +91 -0
  140. package/src/__tests__/posthog.core.spec.ts +135 -0
  141. package/src/__tests__/posthog.debug.spec.ts +36 -0
  142. package/src/__tests__/posthog.enqueue.spec.ts +93 -0
  143. package/src/__tests__/posthog.featureflags.spec.ts +1106 -0
  144. package/src/__tests__/posthog.featureflags.v1.spec.ts +922 -0
  145. package/src/__tests__/posthog.flush.spec.ts +237 -0
  146. package/src/__tests__/posthog.gdpr.spec.ts +50 -0
  147. package/src/__tests__/posthog.groups.spec.ts +96 -0
  148. package/src/__tests__/posthog.identify.spec.ts +194 -0
  149. package/src/__tests__/posthog.init.spec.ts +110 -0
  150. package/src/__tests__/posthog.listeners.spec.ts +51 -0
  151. package/src/__tests__/posthog.register.spec.ts +47 -0
  152. package/src/__tests__/posthog.reset.spec.ts +76 -0
  153. package/src/__tests__/posthog.sessions.spec.ts +63 -0
  154. package/src/__tests__/posthog.setProperties.spec.ts +102 -0
  155. package/src/__tests__/posthog.shutdown.spec.ts +88 -0
  156. package/src/__tests__/utils.spec.ts +36 -0
  157. package/src/error-tracking/chunk-ids.ts +58 -0
  158. package/src/error-tracking/coercers/dom-exception-coercer.ts +38 -0
  159. package/src/error-tracking/coercers/error-coercer.ts +36 -0
  160. package/src/error-tracking/coercers/error-event-coercer.ts +24 -0
  161. package/src/error-tracking/coercers/event-coercer.ts +19 -0
  162. package/src/error-tracking/coercers/index.ts +8 -0
  163. package/src/error-tracking/coercers/object-coercer.ts +76 -0
  164. package/src/error-tracking/coercers/primitive-coercer.ts +19 -0
  165. package/src/error-tracking/coercers/promise-rejection-event.spec.ts +77 -0
  166. package/src/error-tracking/coercers/promise-rejection-event.ts +53 -0
  167. package/src/error-tracking/coercers/string-coercer.spec.ts +26 -0
  168. package/src/error-tracking/coercers/string-coercer.ts +31 -0
  169. package/src/error-tracking/coercers/utils.ts +33 -0
  170. package/src/error-tracking/error-properties-builder.coerce.spec.ts +202 -0
  171. package/src/error-tracking/error-properties-builder.parse.spec.ts +30 -0
  172. package/src/error-tracking/error-properties-builder.ts +169 -0
  173. package/src/error-tracking/index.ts +5 -0
  174. package/src/error-tracking/parsers/base.ts +29 -0
  175. package/src/error-tracking/parsers/chrome.ts +53 -0
  176. package/src/error-tracking/parsers/gecko.ts +38 -0
  177. package/src/error-tracking/parsers/index.ts +104 -0
  178. package/src/error-tracking/parsers/node.ts +111 -0
  179. package/src/error-tracking/parsers/opera.ts +18 -0
  180. package/src/error-tracking/parsers/react-native.ts +0 -0
  181. package/src/error-tracking/parsers/safari.ts +33 -0
  182. package/src/error-tracking/parsers/winjs.ts +12 -0
  183. package/src/error-tracking/types.ts +107 -0
  184. package/src/error-tracking/utils.ts +39 -0
  185. package/src/eventemitter.ts +27 -0
  186. package/src/featureFlagUtils.ts +192 -0
  187. package/src/gzip.ts +29 -0
  188. package/src/index.ts +8 -0
  189. package/src/posthog-core-stateless.ts +1226 -0
  190. package/src/posthog-core.ts +958 -0
  191. package/src/testing/PostHogCoreTestClient.ts +91 -0
  192. package/src/testing/index.ts +2 -0
  193. package/src/testing/test-utils.ts +47 -0
  194. package/src/types.ts +544 -0
  195. package/src/utils/bucketed-rate-limiter.spec.ts +33 -0
  196. package/src/utils/bucketed-rate-limiter.ts +85 -0
  197. package/src/utils/index.ts +98 -0
  198. package/src/utils/number-utils.spec.ts +89 -0
  199. package/src/utils/number-utils.ts +30 -0
  200. package/src/utils/promise-queue.spec.ts +55 -0
  201. package/src/utils/promise-queue.ts +30 -0
  202. package/src/utils/string-utils.ts +23 -0
  203. package/src/utils/type-utils.ts +134 -0
  204. package/src/vendor/uuidv7.ts +479 -0
@@ -0,0 +1,922 @@
1
+ import { PostHogPersistedProperty, PostHogV1FlagsResponse } from '@/types'
2
+ import { normalizeFlagsResponse } from '@/featureFlagUtils'
3
+ import {
4
+ parseBody,
5
+ waitForPromises,
6
+ createTestClient,
7
+ PostHogCoreTestClient,
8
+ PostHogCoreTestClientMocks,
9
+ } from '@/testing'
10
+
11
+ describe('PostHog Feature Flags v1', () => {
12
+ let posthog: PostHogCoreTestClient
13
+ let mocks: PostHogCoreTestClientMocks
14
+
15
+ jest.useFakeTimers()
16
+ jest.setSystemTime(new Date('2022-01-01'))
17
+
18
+ const createMockFeatureFlags = (): any => ({
19
+ 'feature-1': true,
20
+ 'feature-2': true,
21
+ 'feature-variant': 'variant',
22
+ 'json-payload': true,
23
+ })
24
+
25
+ const createMockFeatureFlagPayloads = (): any => ({
26
+ 'feature-1': JSON.stringify({
27
+ color: 'blue',
28
+ }),
29
+ 'feature-variant': JSON.stringify([5]),
30
+ 'json-payload': '{"a":"payload"}',
31
+ })
32
+
33
+ const errorAPIResponse = Promise.resolve({
34
+ status: 400,
35
+ text: () => Promise.resolve('error'),
36
+ json: () =>
37
+ Promise.resolve({
38
+ status: 'error',
39
+ }),
40
+ })
41
+
42
+ beforeEach(() => {
43
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 }, (_mocks) => {
44
+ _mocks.fetch.mockImplementation((url) => {
45
+ if (url.includes('/flags/?v=2&config=true')) {
46
+ return Promise.resolve({
47
+ status: 200,
48
+ text: () => Promise.resolve('ok'),
49
+ json: () =>
50
+ Promise.resolve({
51
+ featureFlags: createMockFeatureFlags(),
52
+ featureFlagPayloads: createMockFeatureFlagPayloads(),
53
+ }),
54
+ })
55
+ }
56
+
57
+ return Promise.resolve({
58
+ status: 200,
59
+ text: () => Promise.resolve('ok'),
60
+ json: () =>
61
+ Promise.resolve({
62
+ status: 'ok',
63
+ }),
64
+ })
65
+ })
66
+ })
67
+ })
68
+
69
+ describe('featureflags', () => {
70
+ it('getFeatureFlags should return undefined if not loaded', () => {
71
+ expect(posthog.getFeatureFlags()).toEqual(undefined)
72
+ })
73
+
74
+ it('getFeatureFlagPayloads should return undefined if not loaded', () => {
75
+ expect(posthog.getFeatureFlagPayloads()).toEqual(undefined)
76
+ })
77
+
78
+ it('getFeatureFlag should return undefined if not loaded', () => {
79
+ expect(posthog.getFeatureFlag('my-flag')).toEqual(undefined)
80
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(undefined)
81
+ })
82
+
83
+ it('getFeatureFlagPayload should return undefined if not loaded', () => {
84
+ expect(posthog.getFeatureFlagPayload('my-flag')).toEqual(undefined)
85
+ })
86
+
87
+ it('isFeatureEnabled should return undefined if not loaded', () => {
88
+ expect(posthog.isFeatureEnabled('my-flag')).toEqual(undefined)
89
+ expect(posthog.isFeatureEnabled('feature-1')).toEqual(undefined)
90
+ })
91
+
92
+ it('should load legacy persisted feature flags', () => {
93
+ posthog.setPersistedProperty(PostHogPersistedProperty.FeatureFlags, createMockFeatureFlags())
94
+ expect(posthog.getFeatureFlags()).toEqual(createMockFeatureFlags())
95
+ })
96
+
97
+ it('should only call fetch once if already calling', async () => {
98
+ expect(mocks.fetch).toHaveBeenCalledTimes(0)
99
+ posthog.reloadFeatureFlagsAsync()
100
+ posthog.reloadFeatureFlagsAsync()
101
+ const flags = await posthog.reloadFeatureFlagsAsync()
102
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
103
+ expect(flags).toEqual(createMockFeatureFlags())
104
+ })
105
+
106
+ it('should emit featureflags event when flags are loaded', async () => {
107
+ const receivedFlags: any[] = []
108
+ const unsubscribe = posthog.onFeatureFlags((flags) => {
109
+ receivedFlags.push(flags)
110
+ })
111
+
112
+ await posthog.reloadFeatureFlagsAsync()
113
+ unsubscribe()
114
+
115
+ expect(receivedFlags).toEqual([createMockFeatureFlags()])
116
+ })
117
+
118
+ describe('when loaded', () => {
119
+ beforeEach(() => {
120
+ // The core doesn't reload flags by default (this is handled differently by web and RN)
121
+ posthog.reloadFeatureFlags()
122
+ })
123
+
124
+ it('should return the value of a flag', async () => {
125
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
126
+ expect(posthog.getFeatureFlag('feature-variant')).toEqual('variant')
127
+ expect(posthog.getFeatureFlag('feature-missing')).toEqual(false)
128
+ })
129
+
130
+ it('should return payload of matched flags only', async () => {
131
+ expect(posthog.getFeatureFlagPayload('feature-variant')).toEqual([5])
132
+ expect(posthog.getFeatureFlagPayload('feature-1')).toEqual({
133
+ color: 'blue',
134
+ })
135
+
136
+ expect(posthog.getFeatureFlagPayload('feature-2')).toEqual(null)
137
+ })
138
+
139
+ describe('when errored out', () => {
140
+ beforeEach(() => {
141
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 }, (_mocks) => {
142
+ _mocks.fetch.mockImplementation((url) => {
143
+ if (url.includes('/flags/')) {
144
+ return Promise.resolve({
145
+ status: 400,
146
+ text: () => Promise.resolve('ok'),
147
+ json: () =>
148
+ Promise.resolve({
149
+ error: 'went wrong',
150
+ }),
151
+ })
152
+ }
153
+
154
+ return errorAPIResponse
155
+ })
156
+ })
157
+
158
+ posthog.reloadFeatureFlags()
159
+ })
160
+
161
+ it('should return undefined', async () => {
162
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
163
+ body: JSON.stringify({
164
+ token: 'TEST_API_KEY',
165
+ distinct_id: posthog.getDistinctId(),
166
+ groups: {},
167
+ person_properties: {},
168
+ group_properties: {},
169
+ $anon_distinct_id: posthog.getAnonymousId(),
170
+ }),
171
+ method: 'POST',
172
+ headers: {
173
+ 'Content-Type': 'application/json',
174
+ 'User-Agent': 'posthog-core-tests',
175
+ },
176
+ signal: expect.anything(),
177
+ })
178
+
179
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(undefined)
180
+ expect(posthog.getFeatureFlag('feature-variant')).toEqual(undefined)
181
+ expect(posthog.getFeatureFlag('feature-missing')).toEqual(undefined)
182
+
183
+ expect(posthog.isFeatureEnabled('feature-1')).toEqual(undefined)
184
+ expect(posthog.isFeatureEnabled('feature-variant')).toEqual(undefined)
185
+ expect(posthog.isFeatureEnabled('feature-missing')).toEqual(undefined)
186
+
187
+ expect(posthog.getFeatureFlagPayloads()).toEqual(undefined)
188
+ expect(posthog.getFeatureFlagPayload('feature-1')).toEqual(undefined)
189
+ })
190
+ })
191
+
192
+ describe('when subsequent flags calls return partial results', () => {
193
+ beforeEach(() => {
194
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 }, (_mocks) => {
195
+ _mocks.fetch
196
+ .mockImplementationOnce((url) => {
197
+ if (url.includes('/flags/?v=2&config=true')) {
198
+ return Promise.resolve({
199
+ status: 200,
200
+ text: () => Promise.resolve('ok'),
201
+ json: () =>
202
+ Promise.resolve({
203
+ featureFlags: createMockFeatureFlags(),
204
+ }),
205
+ })
206
+ }
207
+ return errorAPIResponse
208
+ })
209
+ .mockImplementationOnce((url) => {
210
+ if (url.includes('/flags/?v=2&config=true')) {
211
+ return Promise.resolve({
212
+ status: 200,
213
+ text: () => Promise.resolve('ok'),
214
+ json: () =>
215
+ Promise.resolve({
216
+ featureFlags: { 'x-flag': 'x-value', 'feature-1': false },
217
+ errorsWhileComputingFlags: true,
218
+ }),
219
+ })
220
+ }
221
+
222
+ return errorAPIResponse
223
+ })
224
+ .mockImplementation(() => {
225
+ return errorAPIResponse
226
+ })
227
+ })
228
+
229
+ posthog.reloadFeatureFlags()
230
+ })
231
+
232
+ it('should return combined results', async () => {
233
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
234
+ body: JSON.stringify({
235
+ token: 'TEST_API_KEY',
236
+ distinct_id: posthog.getDistinctId(),
237
+ groups: {},
238
+ person_properties: {},
239
+ group_properties: {},
240
+ $anon_distinct_id: posthog.getAnonymousId(),
241
+ }),
242
+ method: 'POST',
243
+ headers: {
244
+ 'Content-Type': 'application/json',
245
+ 'User-Agent': 'posthog-core-tests',
246
+ },
247
+ signal: expect.anything(),
248
+ })
249
+
250
+ expect(posthog.getFeatureFlags()).toEqual({
251
+ 'feature-1': true,
252
+ 'feature-2': true,
253
+ 'json-payload': true,
254
+ 'feature-variant': 'variant',
255
+ })
256
+
257
+ // now second call to feature flags
258
+ await posthog.reloadFeatureFlagsAsync()
259
+
260
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
261
+ body: JSON.stringify({
262
+ token: 'TEST_API_KEY',
263
+ distinct_id: posthog.getDistinctId(),
264
+ groups: {},
265
+ person_properties: {},
266
+ group_properties: {},
267
+ $anon_distinct_id: posthog.getAnonymousId(),
268
+ }),
269
+ method: 'POST',
270
+ headers: {
271
+ 'Content-Type': 'application/json',
272
+ 'User-Agent': 'posthog-core-tests',
273
+ },
274
+ signal: expect.anything(),
275
+ })
276
+
277
+ expect(posthog.getFeatureFlags()).toEqual({
278
+ 'feature-1': false,
279
+ 'feature-2': true,
280
+ 'json-payload': true,
281
+ 'feature-variant': 'variant',
282
+ 'x-flag': 'x-value',
283
+ })
284
+
285
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(false)
286
+ expect(posthog.getFeatureFlag('feature-variant')).toEqual('variant')
287
+ expect(posthog.getFeatureFlag('feature-missing')).toEqual(false)
288
+ expect(posthog.getFeatureFlag('x-flag')).toEqual('x-value')
289
+
290
+ expect(posthog.isFeatureEnabled('feature-1')).toEqual(false)
291
+ expect(posthog.isFeatureEnabled('feature-variant')).toEqual(true)
292
+ expect(posthog.isFeatureEnabled('feature-missing')).toEqual(false)
293
+ expect(posthog.isFeatureEnabled('x-flag')).toEqual(true)
294
+ })
295
+ })
296
+
297
+ describe('when subsequent flags calls return results without errors', () => {
298
+ beforeEach(() => {
299
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 }, (_mocks) => {
300
+ _mocks.fetch
301
+ .mockImplementationOnce((url) => {
302
+ if (url.includes('/flags/?v=2&config=true')) {
303
+ return Promise.resolve({
304
+ status: 200,
305
+ text: () => Promise.resolve('ok'),
306
+ json: () =>
307
+ Promise.resolve({
308
+ featureFlags: createMockFeatureFlags(),
309
+ }),
310
+ })
311
+ }
312
+ return errorAPIResponse
313
+ })
314
+ .mockImplementationOnce((url) => {
315
+ if (url.includes('/flags/?v=2&config=true')) {
316
+ return Promise.resolve({
317
+ status: 200,
318
+ text: () => Promise.resolve('ok'),
319
+ json: () =>
320
+ Promise.resolve({
321
+ featureFlags: { 'x-flag': 'x-value', 'feature-1': false },
322
+ errorsWhileComputingFlags: false,
323
+ }),
324
+ })
325
+ }
326
+
327
+ return errorAPIResponse
328
+ })
329
+ .mockImplementation(() => {
330
+ return errorAPIResponse
331
+ })
332
+ })
333
+
334
+ posthog.reloadFeatureFlags()
335
+ })
336
+
337
+ it('should return only latest results', async () => {
338
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
339
+ body: JSON.stringify({
340
+ token: 'TEST_API_KEY',
341
+ distinct_id: posthog.getDistinctId(),
342
+ groups: {},
343
+ person_properties: {},
344
+ group_properties: {},
345
+ $anon_distinct_id: posthog.getAnonymousId(),
346
+ }),
347
+ method: 'POST',
348
+ headers: {
349
+ 'Content-Type': 'application/json',
350
+ 'User-Agent': 'posthog-core-tests',
351
+ },
352
+ signal: expect.anything(),
353
+ })
354
+
355
+ expect(posthog.getFeatureFlags()).toEqual({
356
+ 'feature-1': true,
357
+ 'feature-2': true,
358
+ 'json-payload': true,
359
+ 'feature-variant': 'variant',
360
+ })
361
+
362
+ // now second call to feature flags
363
+ await posthog.reloadFeatureFlagsAsync()
364
+
365
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
366
+ body: JSON.stringify({
367
+ token: 'TEST_API_KEY',
368
+ distinct_id: posthog.getDistinctId(),
369
+ groups: {},
370
+ person_properties: {},
371
+ group_properties: {},
372
+ $anon_distinct_id: posthog.getAnonymousId(),
373
+ }),
374
+ method: 'POST',
375
+ headers: {
376
+ 'Content-Type': 'application/json',
377
+ 'User-Agent': 'posthog-core-tests',
378
+ },
379
+ signal: expect.anything(),
380
+ })
381
+
382
+ expect(posthog.getFeatureFlags()).toEqual({
383
+ 'feature-1': false,
384
+ 'x-flag': 'x-value',
385
+ })
386
+
387
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(false)
388
+ expect(posthog.getFeatureFlag('feature-variant')).toEqual(false)
389
+ expect(posthog.getFeatureFlag('feature-missing')).toEqual(false)
390
+ expect(posthog.getFeatureFlag('x-flag')).toEqual('x-value')
391
+
392
+ expect(posthog.isFeatureEnabled('feature-1')).toEqual(false)
393
+ expect(posthog.isFeatureEnabled('feature-variant')).toEqual(false)
394
+ expect(posthog.isFeatureEnabled('feature-missing')).toEqual(false)
395
+ expect(posthog.isFeatureEnabled('x-flag')).toEqual(true)
396
+ })
397
+ })
398
+
399
+ it('should return the boolean value of a flag', async () => {
400
+ expect(posthog.isFeatureEnabled('feature-1')).toEqual(true)
401
+ expect(posthog.isFeatureEnabled('feature-variant')).toEqual(true)
402
+ expect(posthog.isFeatureEnabled('feature-missing')).toEqual(false)
403
+ })
404
+
405
+ it('should reload if groups are set', async () => {
406
+ posthog.group('my-group', 'is-great')
407
+ await waitForPromises()
408
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
409
+ expect(JSON.parse((mocks.fetch.mock.calls[1][1].body as string) || '')).toMatchObject({
410
+ groups: { 'my-group': 'is-great' },
411
+ })
412
+ })
413
+
414
+ it('should capture $feature_flag_called when called', async () => {
415
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
416
+ await waitForPromises()
417
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
418
+
419
+ expect(parseBody(mocks.fetch.mock.calls[1])).toMatchObject({
420
+ batch: [
421
+ {
422
+ event: '$feature_flag_called',
423
+ distinct_id: posthog.getDistinctId(),
424
+ properties: {
425
+ $feature_flag: 'feature-1',
426
+ $feature_flag_response: true,
427
+ '$feature/feature-1': true,
428
+ $used_bootstrap_value: false,
429
+ },
430
+ type: 'capture',
431
+ },
432
+ ],
433
+ })
434
+
435
+ // Only tracked once
436
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
437
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
438
+ })
439
+
440
+ it('should capture $feature_flag_called again if new flags', async () => {
441
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
442
+ await waitForPromises()
443
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
444
+
445
+ expect(parseBody(mocks.fetch.mock.calls[1])).toMatchObject({
446
+ batch: [
447
+ {
448
+ event: '$feature_flag_called',
449
+ distinct_id: posthog.getDistinctId(),
450
+ properties: {
451
+ $feature_flag: 'feature-1',
452
+ $feature_flag_response: true,
453
+ '$feature/feature-1': true,
454
+ $used_bootstrap_value: false,
455
+ },
456
+ type: 'capture',
457
+ },
458
+ ],
459
+ })
460
+
461
+ await posthog.reloadFeatureFlagsAsync()
462
+ posthog.getFeatureFlag('feature-1')
463
+
464
+ await waitForPromises()
465
+ expect(mocks.fetch).toHaveBeenCalledTimes(4)
466
+
467
+ expect(parseBody(mocks.fetch.mock.calls[3])).toMatchObject({
468
+ batch: [
469
+ {
470
+ event: '$feature_flag_called',
471
+ distinct_id: posthog.getDistinctId(),
472
+ properties: {
473
+ $feature_flag: 'feature-1',
474
+ $feature_flag_response: true,
475
+ '$feature/feature-1': true,
476
+ $used_bootstrap_value: false,
477
+ },
478
+ type: 'capture',
479
+ },
480
+ ],
481
+ })
482
+ })
483
+
484
+ it('should capture $feature_flag_called when called, but not add all cached flags', async () => {
485
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
486
+ await waitForPromises()
487
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
488
+
489
+ expect(parseBody(mocks.fetch.mock.calls[1])).toMatchObject({
490
+ batch: [
491
+ {
492
+ event: '$feature_flag_called',
493
+ distinct_id: posthog.getDistinctId(),
494
+ properties: {
495
+ $feature_flag: 'feature-1',
496
+ $feature_flag_response: true,
497
+ '$feature/feature-1': true,
498
+ $used_bootstrap_value: false,
499
+ },
500
+ type: 'capture',
501
+ },
502
+ ],
503
+ })
504
+
505
+ // Only tracked once
506
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
507
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
508
+ })
509
+
510
+ it('should persist feature flags', () => {
511
+ const expectedFeatureFlags = {
512
+ featureFlags: createMockFeatureFlags(),
513
+ featureFlagPayloads: createMockFeatureFlagPayloads(),
514
+ }
515
+ const normalizedFeatureFlags = normalizeFlagsResponse(expectedFeatureFlags as PostHogV1FlagsResponse)
516
+ expect(posthog.getPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails)).toEqual(
517
+ normalizedFeatureFlags
518
+ )
519
+ })
520
+
521
+ it('should include feature flags in subsequent captures', async () => {
522
+ posthog.capture('test-event', { foo: 'bar' })
523
+
524
+ await waitForPromises()
525
+
526
+ expect(parseBody(mocks.fetch.mock.calls[1])).toMatchObject({
527
+ batch: [
528
+ {
529
+ event: 'test-event',
530
+ distinct_id: posthog.getDistinctId(),
531
+ properties: {
532
+ $active_feature_flags: ['feature-1', 'feature-2', 'feature-variant', 'json-payload'],
533
+ '$feature/feature-1': true,
534
+ '$feature/feature-2': true,
535
+ '$feature/json-payload': true,
536
+ '$feature/feature-variant': 'variant',
537
+ },
538
+ type: 'capture',
539
+ },
540
+ ],
541
+ })
542
+ })
543
+
544
+ it('should override flags', () => {
545
+ posthog.overrideFeatureFlag({
546
+ 'feature-2': false,
547
+ 'feature-variant': 'control',
548
+ })
549
+ expect(posthog.getFeatureFlags()).toEqual({
550
+ 'json-payload': true,
551
+ 'feature-1': true,
552
+ 'feature-variant': 'control',
553
+ })
554
+ })
555
+ })
556
+
557
+ describe('when quota limited', () => {
558
+ beforeEach(() => {
559
+ ;[posthog, mocks] = createTestClient('TEST_API_KEY', { flushAt: 1 }, (_mocks) => {
560
+ _mocks.fetch.mockImplementation((url) => {
561
+ if (url.includes('/flags/')) {
562
+ return Promise.resolve({
563
+ status: 200,
564
+ text: () => Promise.resolve('ok'),
565
+ json: () =>
566
+ Promise.resolve({
567
+ quotaLimited: ['feature_flags'],
568
+ featureFlags: {},
569
+ featureFlagPayloads: {},
570
+ }),
571
+ })
572
+ }
573
+ return errorAPIResponse
574
+ })
575
+ })
576
+
577
+ posthog.reloadFeatureFlags()
578
+ })
579
+
580
+ it('should unset all flags when feature_flags is quota limited', async () => {
581
+ // First verify the fetch was called correctly
582
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
583
+ body: JSON.stringify({
584
+ token: 'TEST_API_KEY',
585
+ distinct_id: posthog.getDistinctId(),
586
+ groups: {},
587
+ person_properties: {},
588
+ group_properties: {},
589
+ $anon_distinct_id: posthog.getAnonymousId(),
590
+ }),
591
+ method: 'POST',
592
+ headers: {
593
+ 'Content-Type': 'application/json',
594
+ 'User-Agent': 'posthog-core-tests',
595
+ },
596
+ signal: expect.anything(),
597
+ })
598
+
599
+ // Verify all flag methods return undefined when quota limited
600
+ expect(posthog.getFeatureFlags()).toEqual(undefined)
601
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(undefined)
602
+ expect(posthog.getFeatureFlagPayloads()).toEqual(undefined)
603
+ expect(posthog.getFeatureFlagPayload('feature-1')).toEqual(undefined)
604
+ })
605
+
606
+ it('should emit debug message when quota limited', async () => {
607
+ const warnSpy = jest.spyOn(console, 'warn')
608
+ posthog.debug(true)
609
+ await posthog.reloadFeatureFlagsAsync()
610
+
611
+ expect(warnSpy).toHaveBeenCalledWith(
612
+ '[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts'
613
+ )
614
+ })
615
+ })
616
+ })
617
+
618
+ describe('bootstrapped feature flags', () => {
619
+ beforeEach(() => {
620
+ ;[posthog, mocks] = createTestClient(
621
+ 'TEST_API_KEY',
622
+ {
623
+ flushAt: 1,
624
+ bootstrap: {
625
+ distinctId: 'tomato',
626
+ featureFlags: {
627
+ 'bootstrap-1': 'variant-1',
628
+ 'feature-1': 'feature-1-bootstrap-value',
629
+ enabled: true,
630
+ disabled: false,
631
+ },
632
+ featureFlagPayloads: {
633
+ 'bootstrap-1': {
634
+ some: 'key',
635
+ },
636
+ 'feature-1': {
637
+ color: 'feature-1-bootstrap-color',
638
+ },
639
+ 'not-in-featureFlags': {
640
+ color: { foo: 'bar' },
641
+ },
642
+ enabled: 200,
643
+ },
644
+ },
645
+ },
646
+ (_mocks) => {
647
+ _mocks.fetch.mockImplementation((url) => {
648
+ if (url.includes('/flags/')) {
649
+ return Promise.reject(new Error('Not responding to emulate use of bootstrapped values'))
650
+ }
651
+
652
+ return Promise.resolve({
653
+ status: 200,
654
+ text: () => Promise.resolve('ok'),
655
+ json: () =>
656
+ Promise.resolve({
657
+ status: 'ok',
658
+ }),
659
+ })
660
+ })
661
+ }
662
+ )
663
+ })
664
+
665
+ it('getFeatureFlags should return bootstrapped flags', async () => {
666
+ expect(posthog.getFeatureFlags()).toEqual({
667
+ 'bootstrap-1': 'variant-1',
668
+ enabled: true,
669
+ 'feature-1': 'feature-1-bootstrap-value',
670
+ 'not-in-featureFlags': true,
671
+ })
672
+ expect(posthog.getDistinctId()).toEqual('tomato')
673
+ expect(posthog.getAnonymousId()).toEqual('tomato')
674
+ })
675
+
676
+ it('getFeatureFlag should return bootstrapped flags', async () => {
677
+ expect(posthog.getFeatureFlag('my-flag')).toEqual(false)
678
+ expect(posthog.getFeatureFlag('bootstrap-1')).toEqual('variant-1')
679
+ expect(posthog.getFeatureFlag('enabled')).toEqual(true)
680
+ expect(posthog.getFeatureFlag('disabled')).toEqual(false)
681
+ // If a bootstrapped payload is not in the feature flags, we treat it as true
682
+ expect(posthog.getFeatureFlag('not-in-featureFlags')).toEqual(true)
683
+ })
684
+
685
+ it('getFeatureFlag should capture $feature_flag_called with bootstrapped values', async () => {
686
+ expect(posthog.getFeatureFlag('bootstrap-1')).toEqual('variant-1')
687
+
688
+ await waitForPromises()
689
+ expect(mocks.fetch).toHaveBeenCalledTimes(1)
690
+
691
+ expect(parseBody(mocks.fetch.mock.calls[0])).toMatchObject({
692
+ batch: [
693
+ {
694
+ event: '$feature_flag_called',
695
+ distinct_id: posthog.getDistinctId(),
696
+ properties: {
697
+ $feature_flag: 'bootstrap-1',
698
+ $feature_flag_response: 'variant-1',
699
+ '$feature/bootstrap-1': 'variant-1',
700
+ $feature_flag_bootstrapped_response: 'variant-1',
701
+ $feature_flag_bootstrapped_payload: { some: 'key' },
702
+ $used_bootstrap_value: true,
703
+ },
704
+ type: 'capture',
705
+ },
706
+ ],
707
+ })
708
+ })
709
+
710
+ it('isFeatureEnabled should return true/false for bootstrapped flags', () => {
711
+ expect(posthog.isFeatureEnabled('my-flag')).toEqual(false)
712
+ expect(posthog.isFeatureEnabled('bootstrap-1')).toEqual(true)
713
+ expect(posthog.isFeatureEnabled('enabled')).toEqual(true)
714
+ expect(posthog.isFeatureEnabled('disabled')).toEqual(false)
715
+ expect(posthog.isFeatureEnabled('not-in-featureFlags')).toEqual(true)
716
+ })
717
+
718
+ it('getFeatureFlagPayload should return bootstrapped payloads', () => {
719
+ expect(posthog.getFeatureFlagPayload('my-flag')).toEqual(null)
720
+ expect(posthog.getFeatureFlagPayload('bootstrap-1')).toEqual({
721
+ some: 'key',
722
+ })
723
+ expect(posthog.getFeatureFlagPayload('enabled')).toEqual(200)
724
+ expect(posthog.getFeatureFlagPayload('not-in-featureFlags')).toEqual({
725
+ color: { foo: 'bar' },
726
+ })
727
+ })
728
+
729
+ describe('when loaded', () => {
730
+ beforeEach(() => {
731
+ ;[posthog, mocks] = createTestClient(
732
+ 'TEST_API_KEY',
733
+ {
734
+ flushAt: 1,
735
+ bootstrap: {
736
+ distinctId: 'tomato',
737
+ featureFlags: {
738
+ 'bootstrap-1': 'variant-1',
739
+ 'feature-1': 'feature-1-bootstrap-value',
740
+ enabled: true,
741
+ disabled: false,
742
+ },
743
+ featureFlagPayloads: {
744
+ 'bootstrap-1': {
745
+ some: 'key',
746
+ },
747
+ 'feature-1': {
748
+ color: 'feature-1-bootstrap-color',
749
+ },
750
+ enabled: 200,
751
+ },
752
+ },
753
+ },
754
+ (_mocks) => {
755
+ _mocks.fetch.mockImplementation((url) => {
756
+ if (url.includes('/flags/')) {
757
+ return Promise.resolve({
758
+ status: 200,
759
+ text: () => Promise.resolve('ok'),
760
+ json: () =>
761
+ Promise.resolve({
762
+ featureFlags: createMockFeatureFlags(),
763
+ featureFlagPayloads: createMockFeatureFlagPayloads(),
764
+ }),
765
+ })
766
+ }
767
+
768
+ return Promise.resolve({
769
+ status: 200,
770
+ text: () => Promise.resolve('ok'),
771
+ json: () =>
772
+ Promise.resolve({
773
+ status: 'ok',
774
+ }),
775
+ })
776
+ })
777
+ }
778
+ )
779
+
780
+ posthog.reloadFeatureFlags()
781
+ })
782
+
783
+ it('should load new feature flags', async () => {
784
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
785
+ body: JSON.stringify({
786
+ token: 'TEST_API_KEY',
787
+ distinct_id: posthog.getDistinctId(),
788
+ groups: {},
789
+ person_properties: {},
790
+ group_properties: {},
791
+ $anon_distinct_id: 'tomato',
792
+ }),
793
+ method: 'POST',
794
+ headers: {
795
+ 'Content-Type': 'application/json',
796
+ 'User-Agent': 'posthog-core-tests',
797
+ },
798
+ signal: expect.anything(),
799
+ })
800
+
801
+ expect(posthog.getFeatureFlags()).toEqual({
802
+ 'feature-1': true,
803
+ 'feature-2': true,
804
+ 'json-payload': true,
805
+ 'feature-variant': 'variant',
806
+ })
807
+ })
808
+
809
+ it('should load new feature flag payloads', async () => {
810
+ expect(mocks.fetch).toHaveBeenCalledWith('https://us.i.posthog.com/flags/?v=2&config=true', {
811
+ body: JSON.stringify({
812
+ token: 'TEST_API_KEY',
813
+ distinct_id: posthog.getDistinctId(),
814
+ groups: {},
815
+ person_properties: {},
816
+ group_properties: {},
817
+ $anon_distinct_id: 'tomato',
818
+ }),
819
+ method: 'POST',
820
+ headers: {
821
+ 'Content-Type': 'application/json',
822
+ 'User-Agent': 'posthog-core-tests',
823
+ },
824
+ signal: expect.anything(),
825
+ })
826
+ expect(posthog.getFeatureFlagPayload('feature-1')).toEqual({
827
+ color: 'blue',
828
+ })
829
+ expect(posthog.getFeatureFlagPayload('feature-variant')).toEqual([5])
830
+ })
831
+
832
+ it('should capture $feature_flag_called with bootstrapped values', async () => {
833
+ expect(posthog.getFeatureFlag('feature-1')).toEqual(true)
834
+
835
+ await waitForPromises()
836
+ expect(mocks.fetch).toHaveBeenCalledTimes(2)
837
+
838
+ expect(parseBody(mocks.fetch.mock.calls[1])).toMatchObject({
839
+ batch: [
840
+ {
841
+ event: '$feature_flag_called',
842
+ distinct_id: posthog.getDistinctId(),
843
+ properties: {
844
+ $feature_flag: 'feature-1',
845
+ $feature_flag_response: true,
846
+ '$feature/feature-1': true,
847
+ $feature_flag_bootstrapped_response: 'feature-1-bootstrap-value',
848
+ $feature_flag_bootstrapped_payload: { color: 'feature-1-bootstrap-color' },
849
+ $used_bootstrap_value: false,
850
+ },
851
+ type: 'capture',
852
+ },
853
+ ],
854
+ })
855
+ })
856
+ })
857
+ })
858
+
859
+ describe('bootstapped do not overwrite values', () => {
860
+ beforeEach(() => {
861
+ ;[posthog, mocks] = createTestClient(
862
+ 'TEST_API_KEY',
863
+ {
864
+ flushAt: 1,
865
+ bootstrap: {
866
+ distinctId: 'tomato',
867
+ featureFlags: { 'bootstrap-1': 'variant-1', enabled: true, disabled: false },
868
+ featureFlagPayloads: {
869
+ 'bootstrap-1': {
870
+ some: 'key',
871
+ },
872
+ enabled: 200,
873
+ },
874
+ },
875
+ },
876
+ (_mocks) => {
877
+ _mocks.fetch.mockImplementation((url) => {
878
+ if (url.includes('/flags/')) {
879
+ return Promise.resolve({
880
+ status: 200,
881
+ text: () => Promise.resolve('ok'),
882
+ json: () =>
883
+ Promise.resolve({
884
+ featureFlags: createMockFeatureFlags(),
885
+ featureFlagPayloads: createMockFeatureFlagPayloads(),
886
+ }),
887
+ })
888
+ }
889
+
890
+ return Promise.resolve({
891
+ status: 200,
892
+ text: () => Promise.resolve('ok'),
893
+ json: () =>
894
+ Promise.resolve({
895
+ status: 'ok',
896
+ }),
897
+ })
898
+ })
899
+ },
900
+ {
901
+ distinct_id: '123',
902
+ feature_flags: { 'bootstrap-1': 'variant-2' },
903
+ feature_flag_payloads: { 'bootstrap-1': { some: 'other-key' } },
904
+ }
905
+ )
906
+ })
907
+
908
+ it('distinct id should not be overwritten if already there', () => {
909
+ expect(posthog.getDistinctId()).toEqual('123')
910
+ })
911
+
912
+ it('flags should not be overwritten if already there', () => {
913
+ expect(posthog.getFeatureFlag('bootstrap-1')).toEqual('variant-2')
914
+ })
915
+
916
+ it('flag payloads should not be overwritten if already there', () => {
917
+ expect(posthog.getFeatureFlagPayload('bootstrap-1')).toEqual({
918
+ some: 'other-key',
919
+ })
920
+ })
921
+ })
922
+ })