@segment/analytics-browser-actions-facebook-conversions-api-web 1.9.1-staging-99a2d468f.1 → 1.11.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 (55) hide show
  1. package/dist/cjs/functions.d.ts +0 -1
  2. package/dist/cjs/functions.js +2 -11
  3. package/dist/cjs/functions.js.map +1 -1
  4. package/dist/cjs/generated-types.d.ts +1 -0
  5. package/dist/cjs/index.d.ts +6 -2
  6. package/dist/cjs/index.js +17 -5
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/send/depends-on.js +11 -40
  9. package/dist/cjs/send/depends-on.js.map +1 -1
  10. package/dist/cjs/send/fields.js +18 -5
  11. package/dist/cjs/send/fields.js.map +1 -1
  12. package/dist/cjs/send/functions.d.ts +2 -2
  13. package/dist/cjs/send/functions.js +40 -29
  14. package/dist/cjs/send/functions.js.map +1 -1
  15. package/dist/cjs/send/generated-types.d.ts +2 -0
  16. package/dist/cjs/send/index.d.ts +5 -2
  17. package/dist/cjs/send/index.js +1 -1
  18. package/dist/cjs/send/index.js.map +1 -1
  19. package/dist/cjs/types.d.ts +10 -0
  20. package/dist/cjs/types.js.map +1 -1
  21. package/dist/esm/functions.d.ts +0 -1
  22. package/dist/esm/functions.js +2 -10
  23. package/dist/esm/functions.js.map +1 -1
  24. package/dist/esm/generated-types.d.ts +1 -0
  25. package/dist/esm/index.d.ts +6 -2
  26. package/dist/esm/index.js +17 -5
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/send/depends-on.js +11 -40
  29. package/dist/esm/send/depends-on.js.map +1 -1
  30. package/dist/esm/send/fields.js +18 -5
  31. package/dist/esm/send/fields.js.map +1 -1
  32. package/dist/esm/send/functions.d.ts +2 -2
  33. package/dist/esm/send/functions.js +40 -29
  34. package/dist/esm/send/functions.js.map +1 -1
  35. package/dist/esm/send/generated-types.d.ts +2 -0
  36. package/dist/esm/send/index.d.ts +5 -2
  37. package/dist/esm/send/index.js +1 -1
  38. package/dist/esm/send/index.js.map +1 -1
  39. package/dist/esm/types.d.ts +10 -0
  40. package/dist/esm/types.js.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +3 -3
  43. package/src/__tests__/functions.test.ts +159 -0
  44. package/src/constants.ts +1 -1
  45. package/src/functions.ts +66 -65
  46. package/src/generated-types.ts +4 -0
  47. package/src/index.ts +43 -26
  48. package/src/send/__tests__/depends-on.test.ts +28 -0
  49. package/src/send/__tests__/functions.test.ts +1178 -0
  50. package/src/send/depends-on.ts +41 -69
  51. package/src/send/fields.ts +304 -299
  52. package/src/send/functions.ts +213 -153
  53. package/src/send/generated-types.ts +10 -2
  54. package/src/send/index.ts +4 -4
  55. package/src/types.ts +33 -21
@@ -0,0 +1,1178 @@
1
+ import { send } from '../functions'
2
+
3
+ describe('Facebook Conversions API Web - Send Functions', () => {
4
+ let mockFbq
5
+ let mockAnalytics
6
+ let mockClientParamBuilder
7
+ let consoleWarnSpy
8
+
9
+ beforeEach(() => {
10
+ mockFbq = jest.fn()
11
+ mockAnalytics = {
12
+ storage: {
13
+ get: jest.fn(),
14
+ set: jest.fn()
15
+ }
16
+ }
17
+
18
+ mockClientParamBuilder = undefined
19
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()
20
+ })
21
+
22
+ afterEach(() => {
23
+ jest.restoreAllMocks()
24
+ })
25
+
26
+ const defaultSettings = {
27
+ pixelId: 'test-pixel-123',
28
+ ldu: 'Disabled'
29
+ }
30
+
31
+ describe('send - Standard Events', () => {
32
+ it('should send Purchase event with required fields', () => {
33
+ const payload = {
34
+ event_config: {
35
+ event_name: 'Purchase',
36
+ show_fields: false
37
+ },
38
+ content_ids: ['product-123'],
39
+ value: 99.99,
40
+ currency: 'USD'
41
+ }
42
+
43
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
44
+
45
+ expect(mockFbq).toHaveBeenCalledWith(
46
+ 'trackSingle',
47
+ 'test-pixel-123',
48
+ 'Purchase',
49
+ expect.objectContaining({
50
+ content_ids: ['product-123'],
51
+ value: 99.99,
52
+ currency: 'USD'
53
+ }),
54
+ undefined
55
+ )
56
+ })
57
+
58
+ it('should send AddToCart event with contents', () => {
59
+ const payload = {
60
+ event_config: {
61
+ event_name: 'AddToCart',
62
+ show_fields: false
63
+ },
64
+ contents: [
65
+ {
66
+ id: 'product-123',
67
+ quantity: 2,
68
+ item_price: 49.99
69
+ }
70
+ ],
71
+ value: 99.98,
72
+ currency: 'USD'
73
+ }
74
+
75
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
76
+
77
+ expect(mockFbq).toHaveBeenCalledWith(
78
+ 'trackSingle',
79
+ 'test-pixel-123',
80
+ 'AddToCart',
81
+ expect.objectContaining({
82
+ contents: [{ id: 'product-123', quantity: 2, item_price: 49.99 }],
83
+ value: 99.98,
84
+ currency: 'USD'
85
+ }),
86
+ undefined
87
+ )
88
+ })
89
+
90
+ it('should send ViewContent event', () => {
91
+ const payload = {
92
+ event_config: {
93
+ event_name: 'ViewContent',
94
+ show_fields: false
95
+ },
96
+ content_ids: ['product-456'],
97
+ content_name: 'Test Product',
98
+ content_category: 'Electronics',
99
+ value: 199.99
100
+ }
101
+
102
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
103
+
104
+ expect(mockFbq).toHaveBeenCalledWith(
105
+ 'trackSingle',
106
+ 'test-pixel-123',
107
+ 'ViewContent',
108
+ expect.objectContaining({
109
+ content_ids: ['product-456'],
110
+ content_name: 'Test Product',
111
+ content_category: 'Electronics',
112
+ value: 199.99
113
+ }),
114
+ undefined
115
+ )
116
+ })
117
+
118
+ it('should send PageView event', () => {
119
+ const payload = {
120
+ event_config: {
121
+ event_name: 'PageView',
122
+ show_fields: false
123
+ }
124
+ }
125
+
126
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
127
+
128
+ expect(mockFbq).toHaveBeenCalledWith('trackSingle', 'test-pixel-123', 'PageView', {}, undefined)
129
+ })
130
+ })
131
+
132
+ describe('send - Custom Events', () => {
133
+ it('should send custom event with custom event name', () => {
134
+ const payload = {
135
+ event_config: {
136
+ event_name: 'CustomEvent',
137
+ custom_event_name: 'MyCustomEvent',
138
+ show_fields: true
139
+ },
140
+ value: 50.0,
141
+ custom_data: {
142
+ custom_field_1: 'value1',
143
+ custom_field_2: 'value2'
144
+ }
145
+ }
146
+
147
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
148
+
149
+ expect(mockFbq).toHaveBeenCalledWith(
150
+ 'trackSingleCustom',
151
+ 'test-pixel-123',
152
+ 'MyCustomEvent',
153
+ expect.objectContaining({
154
+ value: 50.0,
155
+ custom_data: {
156
+ custom_field_1: 'value1',
157
+ custom_field_2: 'value2'
158
+ }
159
+ }),
160
+ undefined
161
+ )
162
+ })
163
+ })
164
+
165
+ describe('send - Validation', () => {
166
+ it('should warn if AddToCart is missing both content_ids and contents', () => {
167
+ const payload = {
168
+ event_config: {
169
+ event_name: 'AddToCart',
170
+ show_fields: false
171
+ },
172
+ value: 99.99
173
+ }
174
+
175
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
176
+
177
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
178
+ expect.stringContaining('At least one of content_ids or contents is required for the AddToCart event')
179
+ )
180
+ expect(mockFbq).not.toHaveBeenCalled()
181
+ })
182
+
183
+ it('should warn if Purchase is missing both content_ids and contents', () => {
184
+ const payload = {
185
+ event_config: {
186
+ event_name: 'Purchase',
187
+ show_fields: false
188
+ },
189
+ value: 199.99,
190
+ currency: 'USD'
191
+ }
192
+
193
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
194
+
195
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
196
+ expect.stringContaining('At least one of content_ids or contents is required for the Purchase event')
197
+ )
198
+ expect(mockFbq).not.toHaveBeenCalled()
199
+ })
200
+
201
+ it('should warn if ViewContent is missing both content_ids and contents', () => {
202
+ const payload = {
203
+ event_config: {
204
+ event_name: 'ViewContent',
205
+ show_fields: false
206
+ }
207
+ }
208
+
209
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
210
+
211
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
212
+ expect.stringContaining('At least one of content_ids or contents is required for the ViewContent event')
213
+ )
214
+ expect(mockFbq).not.toHaveBeenCalled()
215
+ })
216
+
217
+ it('should not warn if AddToCart has content_ids', () => {
218
+ const payload = {
219
+ event_config: {
220
+ event_name: 'AddToCart',
221
+ show_fields: false
222
+ },
223
+ content_ids: ['product-123'],
224
+ value: 99.99
225
+ }
226
+
227
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
228
+
229
+ expect(consoleWarnSpy).not.toHaveBeenCalled()
230
+ expect(mockFbq).toHaveBeenCalled()
231
+ })
232
+
233
+ it('should not warn if AddToCart has contents', () => {
234
+ const payload = {
235
+ event_config: {
236
+ event_name: 'AddToCart',
237
+ show_fields: false
238
+ },
239
+ contents: [{ id: 'product-123', quantity: 1 }],
240
+ value: 99.99
241
+ }
242
+
243
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
244
+
245
+ expect(consoleWarnSpy).not.toHaveBeenCalled()
246
+ expect(mockFbq).toHaveBeenCalled()
247
+ })
248
+ })
249
+
250
+ describe('send - Event Options', () => {
251
+ it('should include eventID when provided', () => {
252
+ const payload = {
253
+ event_config: {
254
+ event_name: 'Purchase',
255
+ show_fields: false
256
+ },
257
+ content_ids: ['product-123'],
258
+ value: 99.99,
259
+ eventID: 'unique-event-id-123'
260
+ }
261
+
262
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
263
+
264
+ expect(mockFbq).toHaveBeenCalledWith(
265
+ 'trackSingle',
266
+ 'test-pixel-123',
267
+ 'Purchase',
268
+ expect.any(Object),
269
+ expect.objectContaining({
270
+ eventID: 'unique-event-id-123'
271
+ })
272
+ )
273
+ })
274
+
275
+ it('should include eventSourceUrl when provided', () => {
276
+ const payload = {
277
+ event_config: {
278
+ event_name: 'Purchase',
279
+ show_fields: false
280
+ },
281
+ content_ids: ['product-123'],
282
+ value: 99.99,
283
+ eventSourceUrl: 'https://example.com/checkout'
284
+ }
285
+
286
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
287
+
288
+ expect(mockFbq).toHaveBeenCalledWith(
289
+ 'trackSingle',
290
+ 'test-pixel-123',
291
+ 'Purchase',
292
+ expect.any(Object),
293
+ expect.objectContaining({
294
+ eventSourceUrl: 'https://example.com/checkout'
295
+ })
296
+ )
297
+ })
298
+
299
+ it('should include both eventID and eventSourceUrl when provided', () => {
300
+ const payload = {
301
+ event_config: {
302
+ event_name: 'Purchase',
303
+ show_fields: false
304
+ },
305
+ content_ids: ['product-123'],
306
+ value: 99.99,
307
+ eventID: 'unique-event-id-123',
308
+ eventSourceUrl: 'https://example.com/checkout'
309
+ }
310
+
311
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
312
+
313
+ expect(mockFbq).toHaveBeenCalledWith(
314
+ 'trackSingle',
315
+ 'test-pixel-123',
316
+ 'Purchase',
317
+ expect.any(Object),
318
+ expect.objectContaining({
319
+ eventID: 'unique-event-id-123',
320
+ eventSourceUrl: 'https://example.com/checkout'
321
+ })
322
+ )
323
+ })
324
+ })
325
+
326
+ describe('send - User Data Formatting', () => {
327
+ it('should format userData with email', () => {
328
+ mockAnalytics.storage.get.mockReturnValue('0')
329
+
330
+ const payload = {
331
+ event_config: {
332
+ event_name: 'Purchase',
333
+ show_fields: false
334
+ },
335
+ content_ids: ['product-123'],
336
+ value: 99.99,
337
+ userData: {
338
+ em: 'TEST@EXAMPLE.COM'
339
+ }
340
+ }
341
+
342
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
343
+
344
+ // Should have called init with formatted user data
345
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ em: 'test@example.com' }))
346
+ // Should have stored user data
347
+ expect(mockAnalytics.storage.set).toHaveBeenCalledWith(
348
+ 'fb_user_data',
349
+ expect.stringContaining('test@example.com')
350
+ )
351
+ })
352
+
353
+ it('should format userData with phone number', () => {
354
+ mockAnalytics.storage.get.mockReturnValue('0')
355
+
356
+ const payload = {
357
+ event_config: {
358
+ event_name: 'Purchase',
359
+ show_fields: false
360
+ },
361
+ content_ids: ['product-123'],
362
+ value: 99.99,
363
+ userData: {
364
+ ph: '(555) 123-4567'
365
+ }
366
+ }
367
+
368
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
369
+
370
+ // Phone should be cleaned of non-numeric characters
371
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ ph: '5551234567' }))
372
+ })
373
+
374
+ it('should format userData with first and last name', () => {
375
+ mockAnalytics.storage.get.mockReturnValue('0')
376
+
377
+ const payload = {
378
+ event_config: {
379
+ event_name: 'Purchase',
380
+ show_fields: false
381
+ },
382
+ content_ids: ['product-123'],
383
+ value: 99.99,
384
+ userData: {
385
+ fn: ' JOHN ',
386
+ ln: ' DOE '
387
+ }
388
+ }
389
+
390
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
391
+
392
+ // Names should be lowercased and trimmed
393
+ expect(mockFbq).toHaveBeenCalledWith(
394
+ 'init',
395
+ 'test-pixel-123',
396
+ expect.objectContaining({ fn: 'john', ln: 'doe' })
397
+ )
398
+ })
399
+
400
+ it('should format userData with gender', () => {
401
+ mockAnalytics.storage.get.mockReturnValue('0')
402
+
403
+ const payload = {
404
+ event_config: {
405
+ event_name: 'Purchase',
406
+ show_fields: false
407
+ },
408
+ content_ids: ['product-123'],
409
+ value: 99.99,
410
+ userData: {
411
+ ge: 'm'
412
+ }
413
+ }
414
+
415
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
416
+
417
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ ge: 'm' }))
418
+ })
419
+
420
+ it('should format userData with date of birth', () => {
421
+ mockAnalytics.storage.get.mockReturnValue('0')
422
+
423
+ const payload = {
424
+ event_config: {
425
+ event_name: 'Purchase',
426
+ show_fields: false
427
+ },
428
+ content_ids: ['product-123'],
429
+ value: 99.99,
430
+ userData: {
431
+ db: '1990-05-15T00:00:00.000Z'
432
+ }
433
+ }
434
+
435
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
436
+
437
+ // Date should be formatted as YYYYMMDD
438
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ db: '19900515' }))
439
+ })
440
+
441
+ it('should format userData with city', () => {
442
+ mockAnalytics.storage.get.mockReturnValue('0')
443
+
444
+ const payload = {
445
+ event_config: {
446
+ event_name: 'Purchase',
447
+ show_fields: false
448
+ },
449
+ content_ids: ['product-123'],
450
+ value: 99.99,
451
+ userData: {
452
+ ct: ' New York '
453
+ }
454
+ }
455
+
456
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
457
+
458
+ // City should be lowercased with spaces removed
459
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ ct: 'newyork' }))
460
+ })
461
+
462
+ it('should format userData with US state - full name to code', () => {
463
+ mockAnalytics.storage.get.mockReturnValue('0')
464
+
465
+ const payload = {
466
+ event_config: {
467
+ event_name: 'Purchase',
468
+ show_fields: false
469
+ },
470
+ content_ids: ['product-123'],
471
+ value: 99.99,
472
+ userData: {
473
+ st: 'California'
474
+ }
475
+ }
476
+
477
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
478
+
479
+ // State should be converted to 2-letter code
480
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ st: 'ca' }))
481
+ })
482
+
483
+ it('should format userData with US state - already 2-letter code', () => {
484
+ mockAnalytics.storage.get.mockReturnValue('0')
485
+
486
+ const payload = {
487
+ event_config: {
488
+ event_name: 'Purchase',
489
+ show_fields: false
490
+ },
491
+ content_ids: ['product-123'],
492
+ value: 99.99,
493
+ userData: {
494
+ st: 'NY'
495
+ }
496
+ }
497
+
498
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
499
+
500
+ // State code should be lowercased
501
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ st: 'ny' }))
502
+ })
503
+
504
+ it('should format userData with country - full name to code', () => {
505
+ mockAnalytics.storage.get.mockReturnValue('0')
506
+
507
+ const payload = {
508
+ event_config: {
509
+ event_name: 'Purchase',
510
+ show_fields: false
511
+ },
512
+ content_ids: ['product-123'],
513
+ value: 99.99,
514
+ userData: {
515
+ country: 'United States'
516
+ }
517
+ }
518
+
519
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
520
+
521
+ // Country should be converted to 2-letter code
522
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ country: 'us' }))
523
+ })
524
+
525
+ it('should format userData with country - already 2-letter code', () => {
526
+ mockAnalytics.storage.get.mockReturnValue('0')
527
+
528
+ const payload = {
529
+ event_config: {
530
+ event_name: 'Purchase',
531
+ show_fields: false
532
+ },
533
+ content_ids: ['product-123'],
534
+ value: 99.99,
535
+ userData: {
536
+ country: 'GB'
537
+ }
538
+ }
539
+
540
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
541
+
542
+ // Country code should be lowercased
543
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ country: 'gb' }))
544
+ })
545
+
546
+ it('should format userData with zip code', () => {
547
+ mockAnalytics.storage.get.mockReturnValue('0')
548
+
549
+ const payload = {
550
+ event_config: {
551
+ event_name: 'Purchase',
552
+ show_fields: false
553
+ },
554
+ content_ids: ['product-123'],
555
+ value: 99.99,
556
+ userData: {
557
+ zp: ' 94102 '
558
+ }
559
+ }
560
+
561
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
562
+
563
+ // Zip should be trimmed
564
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ zp: '94102' }))
565
+ })
566
+
567
+ it('should format userData with external_id', () => {
568
+ mockAnalytics.storage.get.mockReturnValue('0')
569
+
570
+ const payload = {
571
+ event_config: {
572
+ event_name: 'Purchase',
573
+ show_fields: false
574
+ },
575
+ content_ids: ['product-123'],
576
+ value: 99.99,
577
+ userData: {
578
+ external_id: ' user-123 '
579
+ }
580
+ }
581
+
582
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
583
+
584
+ // External ID should be trimmed
585
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ external_id: 'user-123' }))
586
+ })
587
+
588
+ it('should format userData with fbp and fbc cookies', () => {
589
+ mockAnalytics.storage.get.mockReturnValue('0')
590
+
591
+ const payload = {
592
+ event_config: {
593
+ event_name: 'Purchase',
594
+ show_fields: false
595
+ },
596
+ content_ids: ['product-123'],
597
+ value: 99.99,
598
+ userData: {
599
+ fbp: ' fb.1.1234567890.1234567890 ',
600
+ fbc: ' fb.1.1234567890.AbCdEf123 '
601
+ }
602
+ }
603
+
604
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
605
+
606
+ // FBP and FBC should be trimmed
607
+ expect(mockFbq).toHaveBeenCalledWith(
608
+ 'init',
609
+ 'test-pixel-123',
610
+ expect.objectContaining({
611
+ fbp: 'fb.1.1234567890.1234567890',
612
+ fbc: 'fb.1.1234567890.AbCdEf123'
613
+ })
614
+ )
615
+ })
616
+
617
+ it('should format userData with all fields combined', () => {
618
+ mockAnalytics.storage.get.mockReturnValue('0')
619
+
620
+ const payload = {
621
+ event_config: {
622
+ event_name: 'Purchase',
623
+ show_fields: false
624
+ },
625
+ content_ids: ['product-123'],
626
+ value: 99.99,
627
+ userData: {
628
+ external_id: 'user-123',
629
+ em: 'test@example.com',
630
+ ph: '5551234567',
631
+ fn: 'John',
632
+ ln: 'Doe',
633
+ ge: 'm',
634
+ db: '1990-05-15T00:00:00.000Z',
635
+ ct: 'San Francisco',
636
+ st: 'California',
637
+ zp: '94102',
638
+ country: 'United States',
639
+ fbp: 'fb.1.1234567890.1234567890',
640
+ fbc: 'fb.1.1234567890.AbCdEf123'
641
+ }
642
+ }
643
+
644
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
645
+
646
+ expect(mockFbq).toHaveBeenCalledWith(
647
+ 'init',
648
+ 'test-pixel-123',
649
+ expect.objectContaining({
650
+ external_id: 'user-123',
651
+ em: 'test@example.com',
652
+ ph: '5551234567',
653
+ fn: 'john',
654
+ ln: 'doe',
655
+ ge: 'm',
656
+ db: '19900515',
657
+ ct: 'sanfrancisco',
658
+ st: 'ca',
659
+ zp: '94102',
660
+ country: 'us',
661
+ fbp: 'fb.1.1234567890.1234567890',
662
+ fbc: 'fb.1.1234567890.AbCdEf123'
663
+ })
664
+ )
665
+ })
666
+
667
+ it('should not send userData init when init count is at max', () => {
668
+ mockAnalytics.storage.get.mockReturnValue('2')
669
+
670
+ const payload = {
671
+ event_config: {
672
+ event_name: 'Purchase',
673
+ show_fields: false
674
+ },
675
+ content_ids: ['product-123'],
676
+ value: 99.99,
677
+ userData: {
678
+ em: 'test@example.com'
679
+ }
680
+ }
681
+
682
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
683
+
684
+ // Should not call init with user data when count is at max
685
+ const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init')
686
+ expect(initCalls.length).toBe(0)
687
+ })
688
+
689
+ it('should skip invalid gender values', () => {
690
+ mockAnalytics.storage.get.mockReturnValue('0')
691
+
692
+ const payload = {
693
+ event_config: {
694
+ event_name: 'Purchase',
695
+ show_fields: false
696
+ },
697
+ content_ids: ['product-123'],
698
+ value: 99.99,
699
+ userData: {
700
+ ge: 'invalid'
701
+ }
702
+ }
703
+
704
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
705
+
706
+ // Should not call init if only invalid gender is provided
707
+ const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init')
708
+ expect(initCalls.length).toBe(0)
709
+ })
710
+
711
+ it('should skip invalid date of birth', () => {
712
+ mockAnalytics.storage.get.mockReturnValue('0')
713
+
714
+ const payload = {
715
+ event_config: {
716
+ event_name: 'Purchase',
717
+ show_fields: false
718
+ },
719
+ content_ids: ['product-123'],
720
+ value: 99.99,
721
+ userData: {
722
+ db: 'invalid-date'
723
+ }
724
+ }
725
+
726
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
727
+
728
+ // Should not call init if only invalid date is provided
729
+ const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init')
730
+ expect(initCalls.length).toBe(0)
731
+ })
732
+ })
733
+
734
+ describe('send - Event Data Fields', () => {
735
+ it('should include all event fields when show_fields is true', () => {
736
+ const payload = {
737
+ event_config: {
738
+ event_name: 'Purchase',
739
+ show_fields: true
740
+ },
741
+ content_ids: ['product-123'],
742
+ content_name: 'Test Product',
743
+ content_category: 'Electronics',
744
+ content_type: 'product',
745
+ value: 99.99,
746
+ currency: 'USD',
747
+ num_items: 1,
748
+ delivery_category: 'home_delivery'
749
+ }
750
+
751
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
752
+
753
+ expect(mockFbq).toHaveBeenCalledWith(
754
+ 'trackSingle',
755
+ 'test-pixel-123',
756
+ 'Purchase',
757
+ expect.objectContaining({
758
+ content_ids: ['product-123'],
759
+ content_name: 'Test Product',
760
+ content_category: 'Electronics',
761
+ content_type: 'product',
762
+ value: 99.99,
763
+ currency: 'USD',
764
+ num_items: 1,
765
+ delivery_category: 'home_delivery'
766
+ }),
767
+ undefined
768
+ )
769
+ })
770
+
771
+ it('should include predicted_ltv for Subscribe event', () => {
772
+ const payload = {
773
+ event_config: {
774
+ event_name: 'Subscribe',
775
+ show_fields: false
776
+ },
777
+ predicted_ltv: 500.0,
778
+ value: 50.0
779
+ }
780
+
781
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
782
+
783
+ expect(mockFbq).toHaveBeenCalledWith(
784
+ 'trackSingle',
785
+ 'test-pixel-123',
786
+ 'Subscribe',
787
+ expect.objectContaining({
788
+ predicted_ltv: 500.0,
789
+ value: 50.0
790
+ }),
791
+ undefined
792
+ )
793
+ })
794
+
795
+ it('should include net_revenue for Purchase event', () => {
796
+ const payload = {
797
+ event_config: {
798
+ event_name: 'Purchase',
799
+ show_fields: false
800
+ },
801
+ content_ids: ['product-123'],
802
+ net_revenue: 450.0,
803
+ value: 50.0
804
+ }
805
+
806
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
807
+
808
+ expect(mockFbq).toHaveBeenCalledWith(
809
+ 'trackSingle',
810
+ 'test-pixel-123',
811
+ 'Purchase',
812
+ expect.objectContaining({
813
+ net_revenue: 450.0,
814
+ value: 50.0
815
+ }),
816
+ undefined
817
+ )
818
+ })
819
+
820
+ it('should include custom_data', () => {
821
+ const payload = {
822
+ event_config: {
823
+ event_name: 'Purchase',
824
+ show_fields: false
825
+ },
826
+ content_ids: ['product-123'],
827
+ value: 99.99,
828
+ custom_data: {
829
+ order_id: 'order-789',
830
+ campaign_id: 'summer-sale',
831
+ user_tier: 'premium'
832
+ }
833
+ }
834
+
835
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
836
+
837
+ expect(mockFbq).toHaveBeenCalledWith(
838
+ 'trackSingle',
839
+ 'test-pixel-123',
840
+ 'Purchase',
841
+ expect.objectContaining({
842
+ custom_data: {
843
+ order_id: 'order-789',
844
+ campaign_id: 'summer-sale',
845
+ user_tier: 'premium'
846
+ }
847
+ }),
848
+ undefined
849
+ )
850
+ })
851
+
852
+ it('should not include empty arrays or objects', () => {
853
+ const payload = {
854
+ event_config: {
855
+ event_name: 'PageView',
856
+ show_fields: false
857
+ },
858
+ content_ids: [],
859
+ contents: [],
860
+ custom_data: {}
861
+ }
862
+
863
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
864
+
865
+ const eventData = (mockFbq).mock.calls[0][3]
866
+ expect(eventData).toEqual({})
867
+ })
868
+
869
+ it('should handle numeric values correctly including zero', () => {
870
+ const payload = {
871
+ event_config: {
872
+ event_name: 'InitiateCheckout',
873
+ show_fields: false
874
+ },
875
+ content_ids: ['product-123'],
876
+ value: 0,
877
+ num_items: 0
878
+ }
879
+
880
+ send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics)
881
+
882
+ expect(mockFbq).toHaveBeenCalledWith(
883
+ 'trackSingle',
884
+ 'test-pixel-123',
885
+ 'InitiateCheckout',
886
+ expect.objectContaining({
887
+ value: 0,
888
+ num_items: 0
889
+ }),
890
+ undefined
891
+ )
892
+ })
893
+ })
894
+
895
+ describe('send - Client Param Builder (formatUserDataWithParamBuilder)', () => {
896
+ let mockClientParamBuilderInstance
897
+
898
+ beforeEach(() => {
899
+ mockClientParamBuilderInstance = {
900
+ getNormalizedAndHashedPII: jest.fn(),
901
+ processAndCollectAllParams: jest.fn(),
902
+ getFbc: jest.fn(),
903
+ getFbp: jest.fn()
904
+ }
905
+ })
906
+
907
+ it('should use clientParamBuilder to format email when available', () => {
908
+ mockAnalytics.storage.get.mockReturnValue('0')
909
+ mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_email_value')
910
+
911
+ const payload = {
912
+ event_config: {
913
+ event_name: 'Purchase',
914
+ show_fields: false
915
+ },
916
+ content_ids: ['product-123'],
917
+ value: 99.99,
918
+ userData: {
919
+ em: 'TEST@EXAMPLE.COM'
920
+ }
921
+ }
922
+
923
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
924
+
925
+ expect(mockClientParamBuilderInstance.processAndCollectAllParams).toHaveBeenCalled()
926
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('TEST@EXAMPLE.COM', 'email')
927
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ em: 'hashed_email_value' }))
928
+ })
929
+
930
+ it('should use clientParamBuilder to format phone number when available', () => {
931
+ mockAnalytics.storage.get.mockReturnValue('0')
932
+ mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_phone_value')
933
+
934
+ const payload = {
935
+ event_config: {
936
+ event_name: 'Purchase',
937
+ show_fields: false
938
+ },
939
+ content_ids: ['product-123'],
940
+ value: 99.99,
941
+ userData: {
942
+ ph: '+1 (555) 123-4567'
943
+ }
944
+ }
945
+
946
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
947
+
948
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith(
949
+ '+1 (555) 123-4567',
950
+ 'phone'
951
+ )
952
+ expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ ph: 'hashed_phone_value' }))
953
+ })
954
+
955
+ it('should use clientParamBuilder to format all PII fields', () => {
956
+ mockAnalytics.storage.get.mockReturnValue('0')
957
+ mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockImplementation((_, type) => {
958
+ return `hashed_${type}_value`
959
+ })
960
+
961
+ const payload = {
962
+ event_config: {
963
+ event_name: 'Purchase',
964
+ show_fields: false
965
+ },
966
+ content_ids: ['product-123'],
967
+ value: 99.99,
968
+ userData: {
969
+ em: 'test@example.com',
970
+ ph: '5551234567',
971
+ fn: 'John',
972
+ ln: 'Doe',
973
+ ge: 'm',
974
+ db: '1990-05-15',
975
+ ct: 'San Francisco',
976
+ st: 'CA',
977
+ zp: '94102',
978
+ country: 'US',
979
+ external_id: 'user-123'
980
+ }
981
+ }
982
+
983
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
984
+
985
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('test@example.com', 'email')
986
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('5551234567', 'phone')
987
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('John', 'first_name')
988
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('Doe', 'last_name')
989
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('m', 'gender')
990
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('1990-05-15', 'date_of_birth')
991
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('San Francisco', 'city')
992
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('CA', 'state')
993
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('94102', 'zip_code')
994
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('US', 'country')
995
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('user-123', 'external_id')
996
+
997
+ expect(mockFbq).toHaveBeenCalledWith(
998
+ 'init',
999
+ 'test-pixel-123',
1000
+ expect.objectContaining({
1001
+ em: 'hashed_email_value',
1002
+ ph: 'hashed_phone_value',
1003
+ fn: 'hashed_first_name_value',
1004
+ ln: 'hashed_last_name_value',
1005
+ ge: 'hashed_gender_value',
1006
+ db: 'hashed_date_of_birth_value',
1007
+ ct: 'hashed_city_value',
1008
+ st: 'hashed_state_value',
1009
+ zp: 'hashed_zip_code_value',
1010
+ country: 'hashed_country_value',
1011
+ external_id: 'hashed_external_id_value'
1012
+ })
1013
+ )
1014
+ })
1015
+
1016
+ it('should use clientParamBuilder getFbc and getFbp methods', () => {
1017
+ mockAnalytics.storage.get.mockReturnValue('0')
1018
+ mockClientParamBuilderInstance.getFbc.mockReturnValue('fb.1.1234567890.ClientParamBuilderFbc')
1019
+ mockClientParamBuilderInstance.getFbp.mockReturnValue('fb.1.1234567890.ClientParamBuilderFbp')
1020
+
1021
+ const payload = {
1022
+ event_config: {
1023
+ event_name: 'Purchase',
1024
+ show_fields: false
1025
+ },
1026
+ content_ids: ['product-123'],
1027
+ value: 99.99,
1028
+ userData: {
1029
+ em: 'test@example.com',
1030
+ fbc: 'fb.1.1234567890.PayloadFbc',
1031
+ fbp: 'fb.1.1234567890.PayloadFbp'
1032
+ }
1033
+ }
1034
+
1035
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
1036
+
1037
+ expect(mockClientParamBuilderInstance.processAndCollectAllParams).toHaveBeenCalled()
1038
+ expect(mockClientParamBuilderInstance.getFbc).toHaveBeenCalled()
1039
+ expect(mockClientParamBuilderInstance.getFbp).toHaveBeenCalled()
1040
+
1041
+ // ClientParamBuilder values should override payload values
1042
+ expect(mockFbq).toHaveBeenCalledWith(
1043
+ 'init',
1044
+ 'test-pixel-123',
1045
+ expect.objectContaining({
1046
+ fbc: 'fb.1.1234567890.ClientParamBuilderFbc',
1047
+ fbp: 'fb.1.1234567890.ClientParamBuilderFbp'
1048
+ })
1049
+ )
1050
+ })
1051
+
1052
+ it('should use payload fbc/fbp when clientParamBuilder methods return null', () => {
1053
+ mockAnalytics.storage.get.mockReturnValue('0')
1054
+ mockClientParamBuilderInstance.getFbc.mockReturnValue(null)
1055
+ mockClientParamBuilderInstance.getFbp.mockReturnValue(null)
1056
+
1057
+ const payload = {
1058
+ event_config: {
1059
+ event_name: 'Purchase',
1060
+ show_fields: false
1061
+ },
1062
+ content_ids: ['product-123'],
1063
+ value: 99.99,
1064
+ userData: {
1065
+ em: 'test@example.com',
1066
+ fbc: 'fb.1.1234567890.PayloadFbc',
1067
+ fbp: 'fb.1.1234567890.PayloadFbp'
1068
+ }
1069
+ }
1070
+
1071
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
1072
+
1073
+ expect(mockClientParamBuilderInstance.getFbc).toHaveBeenCalled()
1074
+ expect(mockClientParamBuilderInstance.getFbp).toHaveBeenCalled()
1075
+
1076
+ // Should fall back to payload values when clientParamBuilder returns null
1077
+ expect(mockFbq).toHaveBeenCalledWith(
1078
+ 'init',
1079
+ 'test-pixel-123',
1080
+ expect.objectContaining({
1081
+ fbc: 'fb.1.1234567890.PayloadFbc',
1082
+ fbp: 'fb.1.1234567890.PayloadFbp'
1083
+ })
1084
+ )
1085
+ })
1086
+
1087
+ it('should fall back to default formatting when clientParamBuilder returns undefined', () => {
1088
+ mockAnalytics.storage.get.mockReturnValue('0')
1089
+ mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue(undefined)
1090
+
1091
+ const payload = {
1092
+ event_config: {
1093
+ event_name: 'Purchase',
1094
+ show_fields: false
1095
+ },
1096
+ content_ids: ['product-123'],
1097
+ value: 99.99,
1098
+ userData: {
1099
+ em: 'TEST@EXAMPLE.COM',
1100
+ ph: '(555) 123-4567'
1101
+ }
1102
+ }
1103
+
1104
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
1105
+
1106
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('TEST@EXAMPLE.COM', 'email')
1107
+ expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('(555) 123-4567', 'phone')
1108
+
1109
+ // When clientParamBuilder returns undefined, should fall back to default formatting
1110
+ expect(mockFbq).toHaveBeenCalledWith(
1111
+ 'init',
1112
+ 'test-pixel-123',
1113
+ expect.objectContaining({
1114
+ em: 'test@example.com',
1115
+ ph: '5551234567'
1116
+ })
1117
+ )
1118
+ })
1119
+
1120
+ it('should call processAndCollectAllParams before getting cookie values', () => {
1121
+ mockAnalytics.storage.get.mockReturnValue('0')
1122
+ mockClientParamBuilderInstance.getFbc.mockReturnValue('fb.1.fbc')
1123
+ mockClientParamBuilderInstance.getFbp.mockReturnValue('fb.1.fbp')
1124
+
1125
+ const payload = {
1126
+ event_config: {
1127
+ event_name: 'Purchase',
1128
+ show_fields: false
1129
+ },
1130
+ content_ids: ['product-123'],
1131
+ value: 99.99,
1132
+ userData: {
1133
+ em: 'test@example.com'
1134
+ }
1135
+ }
1136
+
1137
+ send(mockFbq, mockClientParamBuilderInstance, payload, defaultSettings, mockAnalytics)
1138
+
1139
+ const calls = mockClientParamBuilderInstance.processAndCollectAllParams.mock.invocationCallOrder
1140
+ const fbcCalls = mockClientParamBuilderInstance.getFbc.mock.invocationCallOrder
1141
+ const fbpCalls = mockClientParamBuilderInstance.getFbp.mock.invocationCallOrder
1142
+
1143
+ // processAndCollectAllParams should be called before getFbc/getFbp
1144
+ expect(calls[0]).toBeLessThan(fbcCalls[0])
1145
+ expect(calls[0]).toBeLessThan(fbpCalls[0])
1146
+ })
1147
+
1148
+ it('should work correctly when clientParamBuilder is undefined', () => {
1149
+ mockAnalytics.storage.get.mockReturnValue('0')
1150
+
1151
+ const payload = {
1152
+ event_config: {
1153
+ event_name: 'Purchase',
1154
+ show_fields: false
1155
+ },
1156
+ content_ids: ['product-123'],
1157
+ value: 99.99,
1158
+ userData: {
1159
+ em: 'TEST@EXAMPLE.COM',
1160
+ ph: '(555) 123-4567'
1161
+ }
1162
+ }
1163
+
1164
+ // Pass undefined for clientParamBuilder
1165
+ send(mockFbq, undefined, payload, defaultSettings, mockAnalytics)
1166
+
1167
+ // Should use default formatting (lowercase and trim for email, digits only for phone)
1168
+ expect(mockFbq).toHaveBeenCalledWith(
1169
+ 'init',
1170
+ 'test-pixel-123',
1171
+ expect.objectContaining({
1172
+ em: 'test@example.com',
1173
+ ph: '5551234567'
1174
+ })
1175
+ )
1176
+ })
1177
+ })
1178
+ })