@purchasely/cordova-plugin-purchasely 6.0.0-rc.1 → 6.0.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.
@@ -47,6 +47,9 @@ describe('Purchasely', () => {
47
47
  expect(Purchasely.Attribute.APPSFLYER_ID).toBe(5);
48
48
  expect(Purchasely.Attribute.AMPLITUDE_USER_ID).toBe(16);
49
49
  expect(Purchasely.Attribute.BATCH_CUSTOM_USER_ID).toBe(20);
50
+ // ENM-02 / REC-11
51
+ expect(Purchasely.Attribute.ONESIGNAL_USER_ID).toBe(21);
52
+ expect(Purchasely.Attribute.oneSignalPlayerId).toBeUndefined();
50
53
  });
51
54
  });
52
55
 
@@ -96,6 +99,14 @@ describe('Purchasely', () => {
96
99
  });
97
100
  });
98
101
 
102
+ describe('BillingPlanType', () => {
103
+ it('should have correct billing plan type values (iOS 26.4+ commitment, Apple only)', () => {
104
+ expect(Purchasely.BillingPlanType.unspecified).toBe(0);
105
+ expect(Purchasely.BillingPlanType.upFront).toBe(1);
106
+ expect(Purchasely.BillingPlanType.monthly).toBe(2);
107
+ });
108
+ });
109
+
99
110
  describe('RunningMode', () => {
100
111
  it('should expose the Purchasely 6.0 running modes by name', () => {
101
112
  expect(Purchasely.RunningMode.observer).toBe('observer');
@@ -106,15 +117,15 @@ describe('Purchasely', () => {
106
117
  describe('PresentationAction', () => {
107
118
  it('should have correct presentation action values', () => {
108
119
  expect(Purchasely.PresentationAction.close).toBe('close');
109
- expect(Purchasely.PresentationAction.close_all).toBe('close_all');
120
+ expect(Purchasely.PresentationAction.closeAll).toBe('close_all');
110
121
  expect(Purchasely.PresentationAction.login).toBe('login');
111
122
  expect(Purchasely.PresentationAction.navigate).toBe('navigate');
112
123
  expect(Purchasely.PresentationAction.purchase).toBe('purchase');
113
124
  expect(Purchasely.PresentationAction.restore).toBe('restore');
114
- expect(Purchasely.PresentationAction.open_presentation).toBe('open_presentation');
115
- expect(Purchasely.PresentationAction.open_placement).toBe('open_placement');
116
- expect(Purchasely.PresentationAction.promo_code).toBe('promo_code');
117
- expect(Purchasely.PresentationAction.web_checkout).toBe('web_checkout');
125
+ expect(Purchasely.PresentationAction.openPresentation).toBe('open_presentation');
126
+ expect(Purchasely.PresentationAction.openPlacement).toBe('open_placement');
127
+ expect(Purchasely.PresentationAction.promoCode).toBe('promo_code');
128
+ expect(Purchasely.PresentationAction.webCheckout).toBe('web_checkout');
118
129
  });
119
130
  });
120
131
 
@@ -228,9 +239,122 @@ describe('Purchasely', () => {
228
239
  ]
229
240
  );
230
241
  });
242
+
243
+ it('should fall back to the literal default sdkVersion when plugin_list metadata is missing', () => {
244
+ const metadata = global.cordova.define.moduleMap['cordova/plugin_list'].exports.metadata;
245
+ const original = metadata['cordova-plugin-purchasely'];
246
+ delete metadata['cordova-plugin-purchasely'];
247
+
248
+ try {
249
+ Purchasely.start({ apiKey: 'API_KEY' }, jest.fn(), jest.fn());
250
+
251
+ expect(mockExec).toHaveBeenCalledWith(
252
+ expect.any(Function),
253
+ expect.any(Function),
254
+ 'Purchasely',
255
+ 'start',
256
+ [{ apiKey: 'API_KEY', sdkVersion: '6.0.0' }]
257
+ );
258
+ } finally {
259
+ metadata['cordova-plugin-purchasely'] = original;
260
+ }
261
+ });
262
+
263
+ it('should treat a v5-style positional call as broken (no longer supported)', () => {
264
+ // Purchasely 6.0: `start` takes a single options object. A v5-style positional
265
+ // call (apiKey, stores, storeKit1, ...) is NOT supported: `options` becomes the
266
+ // raw apiKey string (sdkVersion silently fails to attach to a string primitive),
267
+ // and `success`/`error` are mis-bound to the 2nd/3rd positional args instead of
268
+ // the real callbacks. This documents the breaking change from MIGRATION-v6.md.
269
+ const legacyStores = ['Google'];
270
+ const legacyStoreKit1 = false;
271
+
272
+ Purchasely.start('API_KEY', legacyStores, legacyStoreKit1, null, 0, 'full', jest.fn(), jest.fn());
273
+
274
+ expect(mockExec).toHaveBeenCalledWith(
275
+ legacyStores,
276
+ legacyStoreKit1,
277
+ 'Purchasely',
278
+ 'start',
279
+ ['API_KEY']
280
+ );
281
+ });
282
+ });
283
+
284
+ describe('builder(apiKey) (fluent alias of start)', () => {
285
+ it('accumulates chained options and delegates to exports.start with (success, error) callbacks', () => {
286
+ const success = jest.fn();
287
+ const error = jest.fn();
288
+
289
+ Purchasely.builder('API_KEY')
290
+ .appUserId('user123')
291
+ .runningMode(Purchasely.RunningMode.full)
292
+ .logLevel(Purchasely.LogLevel.INFO)
293
+ .allowDeeplink(true)
294
+ .allowCampaigns(false)
295
+ .stores([Purchasely.Store.google])
296
+ .storekitVersion(Purchasely.StorekitVersion.storeKit2)
297
+ .storeKit1(false)
298
+ .deeplink('myapp://deeplink')
299
+ .start(success, error);
300
+
301
+ expect(mockExec).toHaveBeenCalledWith(
302
+ success,
303
+ error,
304
+ 'Purchasely',
305
+ 'start',
306
+ [
307
+ {
308
+ apiKey: 'API_KEY',
309
+ appUserId: 'user123',
310
+ runningMode: 'full',
311
+ logLevel: 1,
312
+ allowDeeplink: true,
313
+ allowCampaigns: false,
314
+ stores: ['Google'],
315
+ storekitVersion: 'storeKit2',
316
+ storeKit1: false,
317
+ deeplink: 'myapp://deeplink',
318
+ sdkVersion: '5.6.2'
319
+ }
320
+ ]
321
+ );
322
+ });
323
+
324
+ it('start() with no callbacks returns a Promise resolving the isConfigured value', async () => {
325
+ const startPromise = Purchasely.builder('API_KEY').start();
326
+
327
+ const call = mockExec.mock.calls[mockExec.mock.calls.length - 1];
328
+ expect(call[2]).toBe('Purchasely');
329
+ expect(call[3]).toBe('start');
330
+ expect(call[4]).toEqual([{ apiKey: 'API_KEY', sdkVersion: '5.6.2' }]);
331
+ call[0](true); // native success callback -> isConfigured
332
+
333
+ await expect(startPromise).resolves.toBe(true);
334
+ });
335
+
336
+ it('rejects the Promise on native start failure', async () => {
337
+ const startPromise = Purchasely.builder('API_KEY').start();
338
+
339
+ const call = mockExec.mock.calls[mockExec.mock.calls.length - 1];
340
+ call[1]('Invalid API Key');
341
+
342
+ await expect(startPromise).rejects.toEqual({ message: 'Invalid API Key' });
343
+ });
231
344
  });
232
345
 
233
- describe('addEventsListener', () => {
346
+ describe('addEventListener (canonical, REC-18/PAR-18)', () => {
347
+ it('should call exec with correct parameters', () => {
348
+ const success = jest.fn();
349
+ const error = jest.fn();
350
+
351
+ Purchasely.addEventListener(success, error);
352
+
353
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'addEventsListener', []);
354
+ });
355
+ });
356
+
357
+ describe('addEventsListener (deprecated alias)', () => {
234
358
  it('should call exec with correct parameters', () => {
235
359
  const success = jest.fn();
236
360
  const error = jest.fn();
@@ -252,7 +376,21 @@ describe('Purchasely', () => {
252
376
  });
253
377
  });
254
378
 
255
- describe('removeEventsListener', () => {
379
+ describe('removeEventListener (canonical, REC-18/PAR-18)', () => {
380
+ it('should call exec with correct parameters', () => {
381
+ Purchasely.removeEventListener();
382
+
383
+ expect(mockExec).toHaveBeenCalledWith(
384
+ expect.any(Function),
385
+ expect.any(Function),
386
+ 'Purchasely',
387
+ 'removeEventsListener',
388
+ []
389
+ );
390
+ });
391
+ });
392
+
393
+ describe('removeEventsListener (deprecated alias)', () => {
256
394
  it('should call exec with correct parameters', () => {
257
395
  Purchasely.removeEventsListener();
258
396
 
@@ -291,6 +429,17 @@ describe('Purchasely', () => {
291
429
  });
292
430
  });
293
431
 
432
+ describe('isAnonymous (REC-12 / PAR-04)', () => {
433
+ it('should call exec with correct parameters', () => {
434
+ const success = jest.fn();
435
+ const error = jest.fn();
436
+
437
+ Purchasely.isAnonymous(success, error);
438
+
439
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'isAnonymous', []);
440
+ });
441
+ });
442
+
294
443
  describe('userLogin', () => {
295
444
  it('should call exec with correct parameters', () => {
296
445
  const success = jest.fn();
@@ -308,7 +457,7 @@ describe('Purchasely', () => {
308
457
  });
309
458
 
310
459
  describe('userLogout', () => {
311
- it('should call exec with correct parameters', () => {
460
+ it('defaults clearUserAttributes to true when omitted (PAR-30)', () => {
312
461
  Purchasely.userLogout();
313
462
 
314
463
  expect(mockExec).toHaveBeenCalledWith(
@@ -316,7 +465,19 @@ describe('Purchasely', () => {
316
465
  expect.any(Function),
317
466
  'Purchasely',
318
467
  'userLogout',
319
- []
468
+ [true]
469
+ );
470
+ });
471
+
472
+ it('forwards an explicit clearUserAttributes value', () => {
473
+ Purchasely.userLogout(false);
474
+
475
+ expect(mockExec).toHaveBeenCalledWith(
476
+ expect.any(Function),
477
+ expect.any(Function),
478
+ 'Purchasely',
479
+ 'userLogout',
480
+ [false]
320
481
  );
321
482
  });
322
483
  });
@@ -411,186 +572,425 @@ describe('Purchasely', () => {
411
572
  });
412
573
  });
413
574
 
414
- describe('presentPresentationWithIdentifier', () => {
415
- it('should pass a display-mode string as a transition object', () => {
416
- const success = jest.fn();
417
- const error = jest.fn();
575
+ // Purchasely 6.0: the v5 presentation surface (fetchPresentation*, presentPresentation*,
576
+ // backPresentation) is REMOVED, replaced by the promise-based v6 builder
577
+ // (Purchasely.presentation), which re-wraps the very same native exec actions.
578
+ describe('presentation builder (v6)', () => {
579
+ // Grabs the exec() call for a given native action, most recent first.
580
+ const execCallFor = (action) => {
581
+ const calls = mockExec.mock.calls.filter((c) => c[3] === action);
582
+ return calls[calls.length - 1];
583
+ };
584
+
585
+ // normalizePresentation always returns this exact whitelist of documented fields
586
+ // (defaulting the ones the raw payload didn't carry to null); tests merge in the
587
+ // fields they care about rather than repeating the full shape each time.
588
+ const fullPresentation = (overrides) => ({
589
+ screenId: null,
590
+ placementId: null,
591
+ contentId: null,
592
+ audienceId: null,
593
+ abTestId: null,
594
+ abTestVariantId: null,
595
+ campaignId: null,
596
+ flowId: null,
597
+ language: null,
598
+ type: null,
599
+ plans: null,
600
+ metadata: null,
601
+ height: null,
602
+ ...overrides,
603
+ });
604
+
605
+ describe('sources', () => {
606
+ it('.placement(id).build().display() calls presentPresentationForPlacement', async () => {
607
+ const outcomePromise = Purchasely.presentation.placement('placement1').build().display();
608
+
609
+ const call = execCallFor('presentPresentationForPlacement');
610
+ expect(call[4]).toEqual(['placement1', null, undefined]);
611
+ call[0]({ purchaseResult: null, plan: null, closeReason: 'button', error: null, presentation: null });
612
+
613
+ const outcome = await outcomePromise;
614
+ expect(outcome.closeReason).toBe('button');
615
+ });
418
616
 
419
- Purchasely.presentPresentationWithIdentifier('presentation1', 'content1', Purchasely.TransitionType.drawer, success, error);
617
+ it('.screen(id).build().display() calls presentPresentationWithIdentifier', async () => {
618
+ const outcomePromise = Purchasely.presentation.screen('screen1').build().display();
420
619
 
421
- expect(mockExec).toHaveBeenCalledWith(
422
- expect.any(Function),
423
- error,
424
- 'Purchasely',
425
- 'presentPresentationWithIdentifier',
426
- ['presentation1', 'content1', { type: 'drawer' }]
427
- );
620
+ const call = execCallFor('presentPresentationWithIdentifier');
621
+ expect(call[4]).toEqual(['screen1', null, undefined]);
622
+ call[0]({ purchaseResult: null, plan: null, closeReason: 'programmatic', error: null, presentation: null });
623
+
624
+ await outcomePromise;
625
+ });
626
+
627
+ it('.defaultSource().build().display() calls presentPresentationForDefault', async () => {
628
+ const outcomePromise = Purchasely.presentation.defaultSource().build().display();
629
+
630
+ const call = execCallFor('presentPresentationForDefault');
631
+ expect(call[4]).toEqual([null, undefined]);
632
+ call[0]({ purchaseResult: null, plan: null, closeReason: 'button', error: null, presentation: null });
633
+
634
+ await outcomePromise;
635
+ });
636
+
637
+ it('.default() is an alias of .defaultSource()', async () => {
638
+ const outcomePromise = Purchasely.presentation.default().build().display();
639
+
640
+ const call = execCallFor('presentPresentationForDefault');
641
+ expect(call[4]).toEqual([null, undefined]);
642
+ call[0]({ purchaseResult: null, plan: null, closeReason: 'button', error: null, presentation: null });
643
+
644
+ await outcomePromise;
645
+ });
428
646
  });
429
647
 
430
- it('should normalize a legacy isFullscreen=true to fullScreen', () => {
431
- const success = jest.fn();
432
- const error = jest.fn();
648
+ describe('contentId', () => {
649
+ it('is forwarded to the matching present* action', async () => {
650
+ const outcomePromise = Purchasely.presentation.placement('placement1').contentId('content1').build().display();
433
651
 
434
- Purchasely.presentPresentationWithIdentifier('presentation1', 'content1', true, success, error);
652
+ const call = execCallFor('presentPresentationForPlacement');
653
+ expect(call[4]).toEqual(['placement1', 'content1', undefined]);
654
+ call[0]({ closeReason: 'button' });
435
655
 
436
- expect(mockExec).toHaveBeenCalledWith(
437
- expect.any(Function),
438
- error,
439
- 'Purchasely',
440
- 'presentPresentationWithIdentifier',
441
- ['presentation1', 'content1', { type: 'fullScreen' }]
442
- );
656
+ await outcomePromise;
657
+ });
443
658
  });
444
659
 
445
- it('should pass a full transition object (dimensions/dismissible) through unchanged', () => {
446
- const success = jest.fn();
447
- const error = jest.fn();
448
- const transition = { type: 'drawer', dismissible: false, height: { type: 'percentage', value: 0.8 } };
660
+ describe('display(transition)', () => {
661
+ it('normalizes a display-mode string to a transition object', async () => {
662
+ const outcomePromise = Purchasely.presentation.screen('screen1').build().display(Purchasely.TransitionType.drawer);
449
663
 
450
- Purchasely.presentPresentationWithIdentifier('presentation1', null, transition, success, error);
664
+ const call = execCallFor('presentPresentationWithIdentifier');
665
+ expect(call[4][2]).toEqual({ type: 'drawer' });
666
+ call[0]({ closeReason: 'button' });
451
667
 
452
- expect(mockExec).toHaveBeenCalledWith(
453
- expect.any(Function),
454
- error,
455
- 'Purchasely',
456
- 'presentPresentationWithIdentifier',
457
- ['presentation1', null, transition]
458
- );
668
+ await outcomePromise;
669
+ });
670
+
671
+ it('passes a full transition object through unchanged', async () => {
672
+ const transition = { type: 'drawer', dismissible: false, height: { type: 'percentage', value: 0.8 } };
673
+ const outcomePromise = Purchasely.presentation.placement('placement1').build().display(transition);
674
+
675
+ const call = execCallFor('presentPresentationForPlacement');
676
+ expect(call[4][2]).toEqual(transition);
677
+ call[0]({ closeReason: 'button' });
678
+
679
+ await outcomePromise;
680
+ });
459
681
  });
460
- });
461
682
 
462
- describe('presentPresentationForPlacement', () => {
463
- it('should normalize a legacy isFullscreen=false to modal', () => {
464
- const success = jest.fn();
465
- const error = jest.fn();
683
+ describe('backgroundColor', () => {
684
+ it('merges into the transition object on the direct present* path (no forced type)', async () => {
685
+ const outcomePromise = Purchasely.presentation
686
+ .placement('placement1')
687
+ .backgroundColor('#101010')
688
+ .build()
689
+ .display();
690
+
691
+ const call = execCallFor('presentPresentationForPlacement');
692
+ // No display-mode given: only backgroundColor, no `type`, so the backend
693
+ // transition default is still honored (CDV-W-12).
694
+ expect(call[4][2]).toEqual({ backgroundColor: '#101010' });
695
+ call[0]({ closeReason: 'button' });
696
+ await outcomePromise;
697
+ });
466
698
 
467
- Purchasely.presentPresentationForPlacement('placement1', 'content1', false, success, error);
699
+ it('is forwarded as presentPresentation\'s native backgroundColor arg on the re-display path', async () => {
700
+ const request = Purchasely.presentation.screen('screen1').backgroundColor('#202020').build();
468
701
 
469
- expect(mockExec).toHaveBeenCalledWith(
470
- expect.any(Function),
471
- error,
472
- 'Purchasely',
473
- 'presentPresentationForPlacement',
474
- ['placement1', 'content1', { type: 'modal' }]
475
- );
702
+ const preloadPromise = request.preload();
703
+ const rawFetched = { screenId: 'screen1', fetchId: 'ply_fetch_789' };
704
+ execCallFor('fetchPresentation')[0](rawFetched);
705
+ await preloadPromise;
706
+
707
+ const outcomePromise = request.display();
708
+ const displayCall = execCallFor('presentPresentation');
709
+ expect(displayCall[4][0]).toBe(rawFetched);
710
+ expect(displayCall[4][2]).toBe('#202020');
711
+ displayCall[0]({ closeReason: 'programmatic' });
712
+ await outcomePromise;
713
+ });
714
+
715
+ it('does not expose progressColor / displayCloseButton / displayBackButton (no Cordova native support)', () => {
716
+ const builder = Purchasely.presentation.placement('placement1');
717
+ expect(builder.progressColor).toBeUndefined();
718
+ expect(builder.displayCloseButton).toBeUndefined();
719
+ expect(builder.displayBackButton).toBeUndefined();
720
+ });
476
721
  });
477
- });
478
722
 
479
- describe('presentPresentationForDefault', () => {
480
- it('should present the default presentation with a transition object', () => {
481
- const success = jest.fn();
482
- const error = jest.fn();
723
+ describe('lifecycle callbacks', () => {
724
+ it('routes presented/closeRequested envelopes to the builder callbacks, and the final outcome to onDismissed + the resolved promise', async () => {
725
+ const onPresented = jest.fn();
726
+ const onCloseRequested = jest.fn();
727
+ const onDismissed = jest.fn();
728
+
729
+ const outcomePromise = Purchasely.presentation
730
+ .placement('placement1')
731
+ .onPresented(onPresented)
732
+ .onCloseRequested(onCloseRequested)
733
+ .onDismissed(onDismissed)
734
+ .build()
735
+ .display();
736
+
737
+ const call = execCallFor('presentPresentationForPlacement');
738
+ const dispatch = call[0];
739
+ dispatch({ event: 'presented', presentation: { screenId: 's' } });
740
+ dispatch({ event: 'closeRequested' });
741
+ dispatch({ purchaseResult: 'cancelled', plan: null, closeReason: 'button', error: null, presentation: { screenId: 's' } });
742
+
743
+ const outcome = await outcomePromise;
744
+
745
+ // The 'presented' envelope's presentation is screenId-normalized like everywhere else.
746
+ expect(onPresented).toHaveBeenCalledWith(fullPresentation({ screenId: 's' }), null);
747
+ expect(onCloseRequested).toHaveBeenCalledTimes(1);
748
+ expect(onDismissed).toHaveBeenCalledWith(outcome);
749
+ expect(outcome).toEqual({
750
+ presentation: fullPresentation({ screenId: 's' }),
751
+ purchaseResult: 'cancelled',
752
+ plan: null,
753
+ closeReason: 'button',
754
+ error: null,
755
+ });
756
+ });
757
+ });
483
758
 
484
- Purchasely.presentPresentationForDefault('content1', Purchasely.TransitionType.fullScreen, success, error);
759
+ describe('outcome normalization (5 fields)', () => {
760
+ it('exposes exactly presentation/purchaseResult/plan/closeReason/error, with screenId tolerance', async () => {
761
+ const outcomePromise = Purchasely.presentation.placement('placement1').build().display();
762
+
763
+ const call = execCallFor('presentPresentationForPlacement');
764
+ call[0]({
765
+ result: 1, // legacy field: not part of the v6 builder's outcome contract
766
+ purchaseResult: 'purchased',
767
+ plan: { name: 'Plus' },
768
+ closeReason: null,
769
+ error: null,
770
+ presentation: { id: 'screen-id-only' }, // no screenId key: exercises the fallback
771
+ });
772
+
773
+ const outcome = await outcomePromise;
774
+ expect(outcome).toEqual({
775
+ presentation: fullPresentation({ screenId: 'screen-id-only' }),
776
+ purchaseResult: 'purchased',
777
+ plan: { name: 'Plus' },
778
+ closeReason: null,
779
+ error: null,
780
+ });
781
+ expect(outcome.result).toBeUndefined();
782
+ });
485
783
 
486
- expect(mockExec).toHaveBeenCalledWith(
487
- expect.any(Function),
488
- error,
489
- 'Purchasely',
490
- 'presentPresentationForDefault',
491
- ['content1', { type: 'fullScreen' }]
492
- );
784
+ it('synthesizes an outcome carrying `error` when the native call fails, without rejecting', async () => {
785
+ const outcomePromise = Purchasely.presentation.placement('placement1').build().display();
786
+
787
+ const call = execCallFor('presentPresentationForPlacement');
788
+ call[1]('Presentation not loaded'); // native error callback
789
+
790
+ const outcome = await outcomePromise;
791
+ expect(outcome.error).toBe('Presentation not loaded');
792
+ expect(outcome.presentation).toBeNull();
793
+ expect(outcome.closeReason).toBeNull();
794
+ });
493
795
  });
494
- });
495
796
 
496
- describe('presentation lifecycle callbacks', () => {
497
- it('routes presented/closeRequested envelopes to callbacks and the final outcome to success', () => {
498
- const success = jest.fn();
499
- const onPresented = jest.fn();
500
- const onCloseRequested = jest.fn();
797
+ describe('preload() -> display() re-display', () => {
798
+ it('preload() resolves a screenId-normalized presentation, and the follow-up display() re-displays it via presentPresentation carrying the native handle', async () => {
799
+ const request = Purchasely.presentation.placement('placement1').build();
800
+
801
+ const preloadPromise = request.preload();
802
+ const fetchCall = execCallFor('fetchPresentation');
803
+ expect(fetchCall[4]).toEqual(['placement1', null, null]);
804
+
805
+ const rawFetched = { screenId: 'onboarding-screen', placementId: 'placement1', fetchId: 'ply_fetch_123' };
806
+ fetchCall[0](rawFetched);
807
+
808
+ const presentation = await preloadPromise;
809
+ // The screenId-normalized data is present (methods are added on top, below).
810
+ expect(presentation).toMatchObject(fullPresentation({ screenId: 'onboarding-screen', placementId: 'placement1' }));
811
+ // The native re-display handle is never exposed on the returned object.
812
+ expect(presentation.fetchId).toBeUndefined();
813
+ expect(presentation.id).toBeUndefined();
814
+
815
+ const outcomePromise = request.display();
816
+ const displayCall = execCallFor('presentPresentation');
817
+ // The exact raw fetch payload (with its private handle) is forwarded as-is.
818
+ expect(displayCall[4][0]).toBe(rawFetched);
819
+ displayCall[0]({ closeReason: 'programmatic' });
501
820
 
502
- Purchasely.presentPresentationForPlacement('p', null, 'fullScreen', success, jest.fn(), { onPresented, onCloseRequested });
821
+ const outcome = await outcomePromise;
822
+ expect(outcome.closeReason).toBe('programmatic');
823
+ });
824
+
825
+ it('the loaded presentation exposes display()/close()/back() delegating to its request', async () => {
826
+ const request = Purchasely.presentation.screen('screen1').build();
827
+
828
+ const preloadPromise = request.preload();
829
+ const rawFetched = { screenId: 'screen1', fetchId: 'ply_fetch_456' };
830
+ execCallFor('fetchPresentation')[0](rawFetched);
831
+
832
+ const loaded = await preloadPromise;
833
+ expect(typeof loaded.display).toBe('function');
834
+ expect(typeof loaded.close).toBe('function');
835
+ expect(typeof loaded.back).toBe('function');
836
+
837
+ // display() on the loaded object re-displays via the same request/handle.
838
+ const outcomePromise = loaded.display();
839
+ const displayCall = execCallFor('presentPresentation');
840
+ expect(displayCall[4][0]).toBe(rawFetched);
841
+ displayCall[0]({ closeReason: 'programmatic' });
842
+ await outcomePromise;
843
+
844
+ // close() / back() delegate to the request's native actions.
845
+ loaded.close();
846
+ expect(execCallFor('closeAllScreens')).toBeDefined();
847
+ loaded.back();
848
+ expect(execCallFor('backPresentation')).toBeDefined();
849
+ });
850
+
851
+ it('rejects preload() on native failure', async () => {
852
+ const request = Purchasely.presentation.screen('screen1').build();
853
+ const preloadPromise = request.preload();
503
854
 
504
- const dispatch = mockExec.mock.calls[0][0];
505
- dispatch({ event: 'presented', presentation: { screenId: 's' } });
506
- dispatch({ event: 'closeRequested' });
507
- dispatch({ result: 1, purchaseResult: 'cancelled', closeReason: 'button' });
855
+ const fetchCall = execCallFor('fetchPresentation');
856
+ fetchCall[1]('Screen not found');
508
857
 
509
- expect(onPresented).toHaveBeenCalledWith({ screenId: 's' }, null);
510
- expect(onCloseRequested).toHaveBeenCalledTimes(1);
511
- expect(success).toHaveBeenCalledWith({ result: 1, purchaseResult: 'cancelled', closeReason: 'button' });
858
+ await expect(preloadPromise).rejects.toEqual({ message: 'Screen not found' });
859
+ });
860
+
861
+ // Android asymmetry regression (v6 audit): onPresented/onCloseRequested previously only
862
+ // fired on the direct present* path. The re-display action (presentPresentation, reached
863
+ // via preload() -> display()) must route the very same keep-alive envelopes to the
864
+ // builder's callbacks, resolving the display() Promise only once the final (non-kept)
865
+ // outcome envelope arrives -- envelopes strictly before resolution.
866
+ it('routes presented/closeRequested envelopes on the preload() -> display() path too, resolving only at the final outcome', async () => {
867
+ const onPresented = jest.fn();
868
+ const onCloseRequested = jest.fn();
869
+ const order = [];
870
+ onPresented.mockImplementation(() => order.push('presented'));
871
+ onCloseRequested.mockImplementation(() => order.push('closeRequested'));
872
+
873
+ const request = Purchasely.presentation
874
+ .screen('screen1')
875
+ .onPresented(onPresented)
876
+ .onCloseRequested(onCloseRequested)
877
+ .build();
878
+
879
+ const preloadPromise = request.preload();
880
+ const rawFetched = { screenId: 'screen1', fetchId: 'ply_fetch_789' };
881
+ execCallFor('fetchPresentation')[0](rawFetched);
882
+ await preloadPromise;
883
+
884
+ const outcomePromise = request.display();
885
+ const displayCall = execCallFor('presentPresentation');
886
+ const dispatch = displayCall[0];
887
+
888
+ dispatch({ event: 'presented', presentation: { screenId: 'screen1' } });
889
+ dispatch({ event: 'closeRequested' });
890
+ expect(order).toEqual(['presented', 'closeRequested']);
891
+ // Neither envelope resolves the Promise -- only the final outcome does.
892
+ let settled = false;
893
+ outcomePromise.then(() => { settled = true; });
894
+ await Promise.resolve();
895
+ expect(settled).toBe(false);
896
+ order.push('outcome-dispatched');
897
+
898
+ dispatch({ closeReason: 'button', presentation: { screenId: 'screen1' } });
899
+ const outcome = await outcomePromise;
900
+
901
+ // Envelopes fired strictly before the Promise resolved.
902
+ expect(order).toEqual(['presented', 'closeRequested', 'outcome-dispatched']);
903
+ expect(onPresented).toHaveBeenCalledWith(fullPresentation({ screenId: 'screen1' }), null);
904
+ expect(onCloseRequested).toHaveBeenCalledTimes(1);
905
+ expect(outcome.closeReason).toBe('button');
906
+ });
512
907
  });
513
- });
514
908
 
515
- describe('removeDefaultPresentationDismissHandler', () => {
516
- it('should call exec with correct parameters', () => {
517
- Purchasely.removeDefaultPresentationDismissHandler();
909
+ describe('onLoaded builder callback', () => {
910
+ it('fires onLoaded with the screenId-normalized presentation once preload() loads it', async () => {
911
+ const onLoaded = jest.fn();
912
+ const request = Purchasely.presentation.screen('screen1').onLoaded(onLoaded).build();
518
913
 
519
- expect(mockExec).toHaveBeenCalledWith(
520
- expect.any(Function),
521
- expect.any(Function),
522
- 'Purchasely',
523
- 'removeDefaultPresentationDismissHandler',
524
- []
525
- );
914
+ const preloadPromise = request.preload();
915
+ const rawFetched = { screenId: 'screen1', fetchId: 'ply_fetch_loaded' };
916
+ execCallFor('fetchPresentation')[0](rawFetched);
917
+
918
+ await preloadPromise;
919
+
920
+ expect(onLoaded).toHaveBeenCalledWith(fullPresentation({ screenId: 'screen1' }), null);
921
+ });
922
+
923
+ it('does not fire onLoaded when preload() fails (parity with RN/Flutter)', async () => {
924
+ const onLoaded = jest.fn();
925
+ const request = Purchasely.presentation.screen('screen1').onLoaded(onLoaded).build();
926
+
927
+ const preloadPromise = request.preload();
928
+ execCallFor('fetchPresentation')[1]('Screen not found');
929
+
930
+ await expect(preloadPromise).rejects.toEqual({ message: 'Screen not found' });
931
+ expect(onLoaded).not.toHaveBeenCalled();
932
+ });
526
933
  });
527
- });
528
934
 
529
- describe('fetchPresentation', () => {
530
- it('should call exec with correct parameters', () => {
531
- const success = jest.fn();
532
- const error = jest.fn();
935
+ describe('display() direct (no prior preload())', () => {
936
+ it('calls the matching present* action directly, without ever calling fetchPresentation', async () => {
937
+ mockExec.mockClear();
938
+ const outcomePromise = Purchasely.presentation.screen('screen1').build().display();
533
939
 
534
- Purchasely.fetchPresentation('presentation1', 'content1', success, error);
940
+ expect(mockExec.mock.calls.some((c) => c[3] === 'fetchPresentation')).toBe(false);
941
+ expect(mockExec.mock.calls.some((c) => c[3] === 'presentPresentationWithIdentifier')).toBe(true);
535
942
 
536
- expect(mockExec).toHaveBeenCalledWith(
537
- success,
538
- error,
539
- 'Purchasely',
540
- 'fetchPresentation',
541
- [null, 'presentation1', 'content1']
542
- );
943
+ const call = execCallFor('presentPresentationWithIdentifier');
944
+ call[0]({ closeReason: 'button' });
945
+ await outcomePromise;
946
+ });
543
947
  });
544
- });
545
948
 
546
- describe('fetchPresentationForPlacement', () => {
547
- it('should call exec with correct parameters', () => {
548
- const success = jest.fn();
549
- const error = jest.fn();
949
+ describe('request.close()', () => {
950
+ it('delegates to closeAllScreens (current bridge semantics)', () => {
951
+ mockExec.mockClear();
952
+ const request = Purchasely.presentation.placement('placement1').build();
550
953
 
551
- Purchasely.fetchPresentationForPlacement('placement1', 'content1', success, error);
954
+ request.close();
552
955
 
553
- expect(mockExec).toHaveBeenCalledWith(
554
- success,
555
- error,
556
- 'Purchasely',
557
- 'fetchPresentation',
558
- ['placement1', null, 'content1']
559
- );
956
+ expect(mockExec).toHaveBeenCalledWith(
957
+ expect.any(Function),
958
+ expect.any(Function),
959
+ 'Purchasely',
960
+ 'closeAllScreens',
961
+ []
962
+ );
963
+ });
560
964
  });
561
- });
562
965
 
563
- describe('fetchPresentationForDefault', () => {
564
- it('should call exec with both ids null', () => {
565
- const success = jest.fn();
566
- const error = jest.fn();
966
+ describe('request.back()', () => {
967
+ it('calls exec with the backPresentation native action', () => {
968
+ mockExec.mockClear();
969
+ const request = Purchasely.presentation.placement('placement1').build();
567
970
 
568
- Purchasely.fetchPresentationForDefault('content1', success, error);
971
+ request.back();
569
972
 
570
- expect(mockExec).toHaveBeenCalledWith(
571
- success,
572
- error,
573
- 'Purchasely',
574
- 'fetchPresentation',
575
- [null, null, 'content1']
576
- );
973
+ expect(mockExec).toHaveBeenCalledWith(
974
+ expect.any(Function),
975
+ expect.any(Function),
976
+ 'Purchasely',
977
+ 'backPresentation',
978
+ []
979
+ );
980
+ });
577
981
  });
578
982
  });
579
983
 
580
- describe('presentPresentation', () => {
581
- it('should call exec with a transition object', () => {
582
- const success = jest.fn();
583
- const error = jest.fn();
584
- const presentation = { id: 'test' };
585
-
586
- Purchasely.presentPresentation(presentation, Purchasely.TransitionType.fullScreen, '#FFFFFF', success, error);
984
+ describe('removeDefaultPresentationDismissHandler', () => {
985
+ it('should call exec with correct parameters', () => {
986
+ Purchasely.removeDefaultPresentationDismissHandler();
587
987
 
588
988
  expect(mockExec).toHaveBeenCalledWith(
589
989
  expect.any(Function),
590
- error,
990
+ expect.any(Function),
591
991
  'Purchasely',
592
- 'presentPresentation',
593
- [presentation, { type: 'fullScreen' }, '#FFFFFF']
992
+ 'removeDefaultPresentationDismissHandler',
993
+ []
594
994
  );
595
995
  });
596
996
  });
@@ -760,6 +1160,37 @@ describe('Purchasely', () => {
760
1160
  );
761
1161
  });
762
1162
 
1163
+ // Commitment fields (iOS 26.4+, Apple only) ride on the plan carried by the purchase
1164
+ // payload's `parameters.plan`. The JS layer forwards `parameters` verbatim, so a plan's
1165
+ // `commitmentInfo` must survive untouched — this guards against a future param whitelist
1166
+ // silently dropping it.
1167
+ it('forwards a purchase payload plan carrying commitmentInfo to the handler', async () => {
1168
+ const handler = jest.fn().mockReturnValue(Purchasely.InterceptResult.success);
1169
+ Purchasely.interceptAction('purchase', handler);
1170
+ const nativeSuccess = nativeInterceptorFor('purchase');
1171
+
1172
+ const parameters = {
1173
+ plan: {
1174
+ vendorId: 'plan_monthly_12m',
1175
+ commitmentInfo: [
1176
+ {
1177
+ billingPlanType: Purchasely.BillingPlanType.monthly,
1178
+ billingPrice: 9.99,
1179
+ billingPeriod: 'P1M',
1180
+ totalPrice: 119.88,
1181
+ totalPeriod: 'P1Y',
1182
+ totalDuration: 12,
1183
+ },
1184
+ ],
1185
+ },
1186
+ };
1187
+ nativeSuccess({ action: 'purchase', callbackId: 'purchase#c', info: null, parameters });
1188
+ await flush();
1189
+
1190
+ expect(handler).toHaveBeenCalledWith(null, parameters);
1191
+ expect(handler.mock.calls[0][1].plan.commitmentInfo[0].billingPlanType).toBe(2);
1192
+ });
1193
+
763
1194
  it('ignores events whose action does not match the registered kind', async () => {
764
1195
  const handler = jest.fn();
765
1196
  Purchasely.interceptAction('restore', handler);
@@ -833,7 +1264,7 @@ describe('Purchasely', () => {
833
1264
  });
834
1265
 
835
1266
  describe('userSubscriptions', () => {
836
- it('should call exec with correct parameters', () => {
1267
+ it('defaults invalidateCache to false when omitted (PAR-29)', () => {
837
1268
  const success = jest.fn();
838
1269
  const error = jest.fn();
839
1270
 
@@ -844,13 +1275,28 @@ describe('Purchasely', () => {
844
1275
  expect.any(Function),
845
1276
  'Purchasely',
846
1277
  'userSubscriptions',
847
- []
1278
+ [false]
1279
+ );
1280
+ });
1281
+
1282
+ it('forwards an explicit invalidateCache value', () => {
1283
+ const success = jest.fn();
1284
+ const error = jest.fn();
1285
+
1286
+ Purchasely.userSubscriptions(success, error, true);
1287
+
1288
+ expect(mockExec).toHaveBeenCalledWith(
1289
+ success,
1290
+ expect.any(Function),
1291
+ 'Purchasely',
1292
+ 'userSubscriptions',
1293
+ [true]
848
1294
  );
849
1295
  });
850
1296
  });
851
1297
 
852
1298
  describe('userSubscriptionsHistory', () => {
853
- it('should call exec with correct parameters', () => {
1299
+ it('defaults invalidateCache to false when omitted (PAR-29)', () => {
854
1300
  const success = jest.fn();
855
1301
  const error = jest.fn();
856
1302
 
@@ -861,7 +1307,22 @@ describe('Purchasely', () => {
861
1307
  expect.any(Function),
862
1308
  'Purchasely',
863
1309
  'userSubscriptionsHistory',
864
- []
1310
+ [false]
1311
+ );
1312
+ });
1313
+
1314
+ it('forwards an explicit invalidateCache value', () => {
1315
+ const success = jest.fn();
1316
+ const error = jest.fn();
1317
+
1318
+ Purchasely.userSubscriptionsHistory(success, error, true);
1319
+
1320
+ expect(mockExec).toHaveBeenCalledWith(
1321
+ success,
1322
+ expect.any(Function),
1323
+ 'Purchasely',
1324
+ 'userSubscriptionsHistory',
1325
+ [true]
865
1326
  );
866
1327
  });
867
1328
  });
@@ -880,29 +1341,38 @@ describe('Purchasely', () => {
880
1341
  });
881
1342
  });
882
1343
 
883
- describe('closePresentation', () => {
1344
+ describe('closeAllScreens', () => {
884
1345
  it('should call exec with correct parameters', () => {
885
- Purchasely.closePresentation();
1346
+ const success = jest.fn();
1347
+ const error = jest.fn();
1348
+
1349
+ Purchasely.closeAllScreens(success, error);
1350
+
1351
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'closeAllScreens', []);
1352
+ });
1353
+
1354
+ it('should default callbacks when none provided', () => {
1355
+ Purchasely.closeAllScreens();
886
1356
 
887
1357
  expect(mockExec).toHaveBeenCalledWith(
888
1358
  expect.any(Function),
889
1359
  expect.any(Function),
890
1360
  'Purchasely',
891
- 'closePresentation',
1361
+ 'closeAllScreens',
892
1362
  []
893
1363
  );
894
1364
  });
895
1365
  });
896
1366
 
897
- describe('backPresentation', () => {
898
- it('should call exec with correct parameters', () => {
899
- Purchasely.backPresentation();
1367
+ describe('closePresentation (deprecated alias of closeAllScreens)', () => {
1368
+ it('should delegate to closeAllScreens', () => {
1369
+ Purchasely.closePresentation();
900
1370
 
901
1371
  expect(mockExec).toHaveBeenCalledWith(
902
1372
  expect.any(Function),
903
1373
  expect.any(Function),
904
1374
  'Purchasely',
905
- 'backPresentation',
1375
+ 'closeAllScreens',
906
1376
  []
907
1377
  );
908
1378
  });
@@ -1087,6 +1557,79 @@ describe('Purchasely', () => {
1087
1557
  );
1088
1558
  });
1089
1559
  });
1560
+
1561
+ describe('getBuiltInAttributes (PAR-07)', () => {
1562
+ it('should call exec with correct parameters', () => {
1563
+ const success = jest.fn();
1564
+ const error = jest.fn();
1565
+
1566
+ Purchasely.getBuiltInAttributes(success, error);
1567
+
1568
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'getBuiltInAttributes', []);
1569
+ });
1570
+ });
1571
+
1572
+ describe('getBuiltInAttribute (PAR-07)', () => {
1573
+ it('should call exec with correct parameters', () => {
1574
+ const success = jest.fn();
1575
+ const error = jest.fn();
1576
+
1577
+ Purchasely.getBuiltInAttribute('ply_session_count', success, error);
1578
+
1579
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'getBuiltInAttribute', ['ply_session_count']);
1580
+ });
1581
+ });
1582
+
1583
+ describe('userAttributes (REC-12 / PAR-03)', () => {
1584
+ it('should call exec with correct parameters', () => {
1585
+ const success = jest.fn();
1586
+ const error = jest.fn();
1587
+
1588
+ Purchasely.userAttributes(success, error);
1589
+
1590
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'userAttributes', []);
1591
+ });
1592
+ });
1593
+
1594
+ describe('incrementUserAttribute (REC-12 / PAR-02)', () => {
1595
+ it('should call exec with correct parameters', () => {
1596
+ Purchasely.incrementUserAttribute('counter', 5);
1597
+
1598
+ expect(mockExec).toHaveBeenCalledWith(
1599
+ expect.any(Function),
1600
+ expect.any(Function),
1601
+ 'Purchasely',
1602
+ 'incrementUserAttribute',
1603
+ ['counter', 5]
1604
+ );
1605
+ });
1606
+
1607
+ it('should forward an omitted value as-is (native defaults it to 1)', () => {
1608
+ Purchasely.incrementUserAttribute('counter');
1609
+
1610
+ expect(mockExec).toHaveBeenCalledWith(
1611
+ expect.any(Function),
1612
+ expect.any(Function),
1613
+ 'Purchasely',
1614
+ 'incrementUserAttribute',
1615
+ ['counter', undefined]
1616
+ );
1617
+ });
1618
+ });
1619
+
1620
+ describe('decrementUserAttribute (REC-12 / PAR-02)', () => {
1621
+ it('should call exec with correct parameters', () => {
1622
+ Purchasely.decrementUserAttribute('counter', 3);
1623
+
1624
+ expect(mockExec).toHaveBeenCalledWith(
1625
+ expect.any(Function),
1626
+ expect.any(Function),
1627
+ 'Purchasely',
1628
+ 'decrementUserAttribute',
1629
+ ['counter', 3]
1630
+ );
1631
+ });
1632
+ });
1090
1633
  });
1091
1634
 
1092
1635
  describe('isEligibleForIntroOffer', () => {
@@ -1158,4 +1701,126 @@ describe('Purchasely', () => {
1158
1701
  );
1159
1702
  });
1160
1703
  });
1704
+
1705
+ describe('Dynamic Offerings (PAR-05)', () => {
1706
+ describe('setDynamicOffering', () => {
1707
+ it('should call exec with correct parameters, forwarding billingPlanType', () => {
1708
+ const success = jest.fn();
1709
+ const error = jest.fn();
1710
+
1711
+ Purchasely.setDynamicOffering('ref_1', 'plan_vendor_id', 'offer_vendor_id', Purchasely.BillingPlanType.monthly, success, error);
1712
+
1713
+ expect(mockExec).toHaveBeenCalledWith(
1714
+ success,
1715
+ error,
1716
+ 'Purchasely',
1717
+ 'setDynamicOffering',
1718
+ ['ref_1', 'plan_vendor_id', 'offer_vendor_id', 2]
1719
+ );
1720
+ });
1721
+
1722
+ it('should forward a null offerVendorId and default billingPlanType to unspecified when omitted', () => {
1723
+ Purchasely.setDynamicOffering('ref_1', 'plan_vendor_id', null, undefined, jest.fn(), jest.fn());
1724
+
1725
+ expect(mockExec).toHaveBeenCalledWith(
1726
+ expect.any(Function),
1727
+ expect.any(Function),
1728
+ 'Purchasely',
1729
+ 'setDynamicOffering',
1730
+ ['ref_1', 'plan_vendor_id', null, 0]
1731
+ );
1732
+ });
1733
+ });
1734
+
1735
+ describe('getDynamicOfferings', () => {
1736
+ it('should call exec with correct parameters', () => {
1737
+ const success = jest.fn();
1738
+ const error = jest.fn();
1739
+
1740
+ Purchasely.getDynamicOfferings(success, error);
1741
+
1742
+ expect(mockExec).toHaveBeenCalledWith(success, error, 'Purchasely', 'getDynamicOfferings', []);
1743
+ });
1744
+ });
1745
+
1746
+ describe('removeDynamicOffering', () => {
1747
+ it('should call exec with correct parameters', () => {
1748
+ Purchasely.removeDynamicOffering('ref_1');
1749
+
1750
+ expect(mockExec).toHaveBeenCalledWith(
1751
+ expect.any(Function),
1752
+ expect.any(Function),
1753
+ 'Purchasely',
1754
+ 'removeDynamicOffering',
1755
+ ['ref_1']
1756
+ );
1757
+ });
1758
+ });
1759
+
1760
+ describe('clearDynamicOfferings', () => {
1761
+ it('should call exec with correct parameters', () => {
1762
+ Purchasely.clearDynamicOfferings();
1763
+
1764
+ expect(mockExec).toHaveBeenCalledWith(
1765
+ expect.any(Function),
1766
+ expect.any(Function),
1767
+ 'Purchasely',
1768
+ 'clearDynamicOfferings',
1769
+ []
1770
+ );
1771
+ });
1772
+ });
1773
+ });
1774
+
1775
+ // Purchasely 6.0: these v5 APIs were deliberately removed (see MIGRATION-v6.md).
1776
+ // Asserting their absence guards against accidental resurrection/typos reintroducing
1777
+ // a v5-shaped surface alongside the v6 replacements.
1778
+ describe('Removed v5 APIs (should not exist in v6)', () => {
1779
+ it('removes the presentation methods with no v6 native screen equivalent', () => {
1780
+ expect(Purchasely.presentSubscriptions).toBeUndefined();
1781
+ expect(Purchasely.presentProductWithIdentifier).toBeUndefined();
1782
+ expect(Purchasely.presentPlanWithIdentifier).toBeUndefined();
1783
+ expect(Purchasely.showPresentation).toBeUndefined();
1784
+ expect(Purchasely.hidePresentation).toBeUndefined();
1785
+ });
1786
+
1787
+ // The imperative presentation surface is replaced by the Purchasely.presentation
1788
+ // builder (see the 'presentation builder (v6)' suite above).
1789
+ it('removes the imperative presentation surface in favor of Purchasely.presentation', () => {
1790
+ expect(Purchasely.fetchPresentation).toBeUndefined();
1791
+ expect(Purchasely.fetchPresentationForPlacement).toBeUndefined();
1792
+ expect(Purchasely.fetchPresentationForDefault).toBeUndefined();
1793
+ expect(Purchasely.presentPresentationWithIdentifier).toBeUndefined();
1794
+ expect(Purchasely.presentPresentationForPlacement).toBeUndefined();
1795
+ expect(Purchasely.presentPresentationForDefault).toBeUndefined();
1796
+ expect(Purchasely.presentPresentation).toBeUndefined();
1797
+ expect(Purchasely.backPresentation).toBeUndefined();
1798
+
1799
+ expect(typeof Purchasely.presentation.placement).toBe('function');
1800
+ expect(typeof Purchasely.presentation.screen).toBe('function');
1801
+ expect(typeof Purchasely.presentation.defaultSource).toBe('function');
1802
+ expect(typeof Purchasely.presentation.default).toBe('function');
1803
+ });
1804
+
1805
+ it('removes the single global action interceptor in favor of interceptAction(kind, handler)', () => {
1806
+ expect(Purchasely.setPaywallActionInterceptor).toBeUndefined();
1807
+ expect(Purchasely.onProcessAction).toBeUndefined();
1808
+ expect(Purchasely.PaywallAction).toBeUndefined();
1809
+ });
1810
+
1811
+ it('removes the renamed deeplink methods', () => {
1812
+ expect(Purchasely.readyToOpenDeeplink).toBeUndefined();
1813
+ expect(Purchasely.isDeeplinkHandled).toBeUndefined();
1814
+ });
1815
+
1816
+ it('removes the renamed default dismiss handler', () => {
1817
+ expect(Purchasely.setDefaultPresentationResultHandler).toBeUndefined();
1818
+ });
1819
+
1820
+ it('removes the RunningMode values dropped by native 6.0', () => {
1821
+ expect(Purchasely.RunningMode.paywallObserver).toBeUndefined();
1822
+ expect(Purchasely.RunningMode.transactionOnly).toBeUndefined();
1823
+ expect(Object.keys(Purchasely.RunningMode).sort()).toEqual(['full', 'observer']);
1824
+ });
1825
+ });
1161
1826
  });