@capillarytech/creatives-library 9.0.54 → 9.0.55-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +0 -1
- package/package.json +1 -1
- package/services/api.js +20 -0
- package/utils/common.js +0 -4
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +20 -0
- package/v2Components/CommonTestAndPreview/constants.js +2 -0
- package/v2Components/CommonTestAndPreview/index.js +1 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +118 -56
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +1 -1
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +22 -6
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +421 -49
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +18 -2
- package/v2Containers/CommunicationFlow/constants.js +90 -1
- package/v2Containers/CommunicationFlow/index.js +15 -20
- package/v2Containers/CommunicationFlow/messages.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +6 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +25 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +24 -5
- package/v2Containers/CreativesContainer/index.js +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
jest.mock('../../../services/api', () => ({
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
createCommDefinition: jest.fn(),
|
|
5
|
+
editCommDefinition: jest.fn(),
|
|
6
6
|
}));
|
|
7
7
|
|
|
8
8
|
jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
|
|
@@ -38,7 +38,7 @@ import { IntlProvider } from 'react-intl';
|
|
|
38
38
|
import history from '../../../utils/history';
|
|
39
39
|
import { initialReducer } from '../../../initialReducer';
|
|
40
40
|
import CommunicationFlow from '../CommunicationFlow';
|
|
41
|
-
import {
|
|
41
|
+
import { createCommDefinition, editCommDefinition } from '../../../services/api';
|
|
42
42
|
import { getEnabledSteps } from '../utils/getEnabledSteps';
|
|
43
43
|
import {
|
|
44
44
|
CHANNELS,
|
|
@@ -359,82 +359,313 @@ describe('isSaveDisabled', () => {
|
|
|
359
359
|
});
|
|
360
360
|
|
|
361
361
|
describe('handleSave — CCS flow', () => {
|
|
362
|
+
const ccsConfig = { ...baseConfig, context: { name: 'Order Placed Notification' }, features: {} };
|
|
363
|
+
|
|
362
364
|
beforeEach(() => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
+
createCommDefinition.mockResolvedValue({
|
|
366
|
+
response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 1 } } },
|
|
367
|
+
});
|
|
365
368
|
});
|
|
366
369
|
|
|
367
370
|
afterEach(() => {
|
|
368
371
|
jest.clearAllMocks();
|
|
369
372
|
});
|
|
370
373
|
|
|
371
|
-
it('calls
|
|
374
|
+
it('calls createCommDefinition with a SINGLE-strategy payload when useCCS is not false', async () => {
|
|
372
375
|
const onSave = jest.fn();
|
|
373
376
|
renderWithFlow({
|
|
374
377
|
features: {},
|
|
378
|
+
config: ccsConfig,
|
|
379
|
+
// messageBody is the legacy editor's field name for the typed SMS text
|
|
380
|
+
// (see CreativesContainer/index.js's getCreativesData SMS case) — the
|
|
381
|
+
// SMS transform maps it to CCS's `message` field.
|
|
375
382
|
initialData: {
|
|
376
|
-
contentItems: [{ channel: 'sms', templateData: {
|
|
383
|
+
contentItems: [{ channel: 'sms', templateData: { messageBody: 'Hello' } }],
|
|
377
384
|
},
|
|
378
385
|
onSave,
|
|
379
386
|
});
|
|
380
387
|
|
|
381
388
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
382
389
|
|
|
383
|
-
await waitFor(() => expect(
|
|
384
|
-
expect(
|
|
390
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
391
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
385
392
|
expect.objectContaining({
|
|
386
|
-
|
|
393
|
+
name: 'Order Placed Notification',
|
|
394
|
+
strategyType: 'SINGLE',
|
|
395
|
+
singleChannelStrategy: expect.objectContaining({
|
|
396
|
+
variant: expect.objectContaining({
|
|
397
|
+
channel: 'SMS',
|
|
398
|
+
smsMessageContent: { channel: 'SMS', message: 'Hello' },
|
|
399
|
+
}),
|
|
400
|
+
}),
|
|
387
401
|
}),
|
|
388
402
|
);
|
|
389
403
|
expect(onSave).toHaveBeenCalledTimes(1);
|
|
390
404
|
});
|
|
391
405
|
|
|
392
|
-
it('
|
|
406
|
+
it('transforms SMS content from the legacy editor shape (messageBody) to CCS\'s message field', async () => {
|
|
407
|
+
const onSave = jest.fn();
|
|
408
|
+
renderWithFlow({
|
|
409
|
+
features: {},
|
|
410
|
+
config: ccsConfig,
|
|
411
|
+
initialData: {
|
|
412
|
+
contentItems: [{
|
|
413
|
+
channel: 'SMS',
|
|
414
|
+
templateData: { messageBody: 'Hello {{name}}', channel: 'SMS' },
|
|
415
|
+
}],
|
|
416
|
+
},
|
|
417
|
+
onSave,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
421
|
+
|
|
422
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
423
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
424
|
+
expect.objectContaining({
|
|
425
|
+
singleChannelStrategy: expect.objectContaining({
|
|
426
|
+
variant: expect.objectContaining({
|
|
427
|
+
channel: 'SMS',
|
|
428
|
+
smsMessageContent: {
|
|
429
|
+
channel: 'SMS',
|
|
430
|
+
message: 'Hello {{name}}',
|
|
431
|
+
},
|
|
432
|
+
}),
|
|
433
|
+
}),
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('does not carry the legacy messageBody key into smsMessageContent (regression guard against reverting to pass-through)', async () => {
|
|
439
|
+
const onSave = jest.fn();
|
|
440
|
+
renderWithFlow({
|
|
441
|
+
features: {},
|
|
442
|
+
config: ccsConfig,
|
|
443
|
+
initialData: {
|
|
444
|
+
contentItems: [{
|
|
445
|
+
channel: 'SMS',
|
|
446
|
+
templateData: { messageBody: 'Hello {{name}}', channel: 'SMS' },
|
|
447
|
+
}],
|
|
448
|
+
},
|
|
449
|
+
onSave,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
453
|
+
|
|
454
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
455
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
456
|
+
expect(payload.singleChannelStrategy.variant.smsMessageContent).not.toHaveProperty('messageBody');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('transforms EMAIL content from the legacy editor shape (emailSubject) to CCS\'s messageSubject', async () => {
|
|
460
|
+
const onSave = jest.fn();
|
|
461
|
+
renderWithFlow({
|
|
462
|
+
features: {},
|
|
463
|
+
config: ccsConfig,
|
|
464
|
+
initialData: {
|
|
465
|
+
contentItems: [{
|
|
466
|
+
channel: 'EMAIL',
|
|
467
|
+
templateData: { emailSubject: 'Order Confirmed', emailBody: '<html>Hi</html>' },
|
|
468
|
+
}],
|
|
469
|
+
},
|
|
470
|
+
onSave,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
474
|
+
|
|
475
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
476
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
477
|
+
expect.objectContaining({
|
|
478
|
+
singleChannelStrategy: expect.objectContaining({
|
|
479
|
+
variant: expect.objectContaining({
|
|
480
|
+
channel: 'EMAIL',
|
|
481
|
+
emailMessageContent: {
|
|
482
|
+
channel: 'EMAIL',
|
|
483
|
+
messageSubject: 'Order Confirmed',
|
|
484
|
+
messageBody: '<html>Hi</html>',
|
|
485
|
+
},
|
|
486
|
+
}),
|
|
487
|
+
}),
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('transforms WEBPUSH content from the legacy editor\'s messageContent.content wrapping to CCS\'s flat shape', async () => {
|
|
493
|
+
const onSave = jest.fn();
|
|
494
|
+
const cta = { type: 'SITE_URL', actionLink: 'https://example.com' };
|
|
495
|
+
const expandableDetails = { media: [{ url: 'https://example.com/banner.png', type: 'IMAGE' }], ctas: [] };
|
|
496
|
+
renderWithFlow({
|
|
497
|
+
features: {},
|
|
498
|
+
config: ccsConfig,
|
|
499
|
+
initialData: {
|
|
500
|
+
contentItems: [{
|
|
501
|
+
channel: 'WEBPUSH',
|
|
502
|
+
templateData: {
|
|
503
|
+
messageContent: {
|
|
504
|
+
content: {
|
|
505
|
+
channel: 'WEBPUSH',
|
|
506
|
+
accountId: 13792,
|
|
507
|
+
content: { title: 'Hi', message: 'Body', iconImageUrl: 'https://example.com/icon.png', cta, expandableDetails },
|
|
508
|
+
messageSubject: 'Subject',
|
|
509
|
+
offers: [],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
}],
|
|
514
|
+
},
|
|
515
|
+
onSave,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
519
|
+
|
|
520
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
521
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
522
|
+
expect.objectContaining({
|
|
523
|
+
singleChannelStrategy: expect.objectContaining({
|
|
524
|
+
variant: expect.objectContaining({
|
|
525
|
+
channel: 'WEBPUSH',
|
|
526
|
+
webPushMessageContent: {
|
|
527
|
+
channel: 'WEBPUSH',
|
|
528
|
+
messageSubject: 'Subject',
|
|
529
|
+
accountId: 13792,
|
|
530
|
+
content: { title: 'Hi', message: 'Body', iconImageUrl: 'https://example.com/icon.png', cta, expandableDetails },
|
|
531
|
+
},
|
|
532
|
+
}),
|
|
533
|
+
}),
|
|
534
|
+
}),
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*), even when templateData/channelSetting carry their own stray channel field', async () => {
|
|
539
|
+
const onSave = jest.fn();
|
|
393
540
|
renderWithFlow({
|
|
394
541
|
features: {},
|
|
395
|
-
|
|
542
|
+
config: ccsConfig,
|
|
543
|
+
initialData: {
|
|
544
|
+
// `channel: 'MOBILEPUSH'` inline on templateData reproduces the real
|
|
545
|
+
// production bug: contentItem.templateData carries the UI's own
|
|
546
|
+
// internal channel identifier, which must not win over the
|
|
547
|
+
// normalized 'PUSH' enum once spread into mpushMessageContent.
|
|
548
|
+
contentItems: [{ channel: 'MOBILEPUSH', templateData: { channel: 'MOBILEPUSH', messageSubject: 'Hi' } }],
|
|
549
|
+
// Likewise, deliverySetting.channelSetting.PUSH carrying its own
|
|
550
|
+
// stray `channel` key must not win over the normalized value once
|
|
551
|
+
// spread into mpushDeliverySettings.channelSettings.
|
|
552
|
+
deliverySetting: { channelSetting: { PUSH: { channel: 'MOBILEPUSH', domainId: 2002 } } },
|
|
553
|
+
},
|
|
554
|
+
onSave,
|
|
396
555
|
});
|
|
397
556
|
|
|
398
557
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
399
558
|
|
|
400
|
-
await waitFor(() => expect(
|
|
559
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
560
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
561
|
+
expect.objectContaining({
|
|
562
|
+
singleChannelStrategy: expect.objectContaining({
|
|
563
|
+
variant: expect.objectContaining({
|
|
564
|
+
channel: 'PUSH',
|
|
565
|
+
mpushMessageContent: { channel: 'PUSH', messageSubject: 'Hi' },
|
|
566
|
+
mpushDeliverySettings: {
|
|
567
|
+
channelSettings: { channel: 'PUSH', domainId: 2002 },
|
|
568
|
+
},
|
|
569
|
+
}),
|
|
570
|
+
}),
|
|
571
|
+
}),
|
|
572
|
+
);
|
|
401
573
|
});
|
|
402
574
|
|
|
403
|
-
it('
|
|
404
|
-
|
|
575
|
+
it('merges ccsCommDefinition into the data passed to onSave when create succeeds', async () => {
|
|
576
|
+
const onSave = jest.fn();
|
|
405
577
|
renderWithFlow({
|
|
406
578
|
features: {},
|
|
579
|
+
config: ccsConfig,
|
|
407
580
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
581
|
+
onSave,
|
|
408
582
|
});
|
|
409
583
|
|
|
410
584
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
411
585
|
|
|
412
|
-
await waitFor(() => expect(
|
|
413
|
-
expect(
|
|
586
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
587
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
588
|
+
expect.objectContaining({
|
|
589
|
+
ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 1, status: 'DRAFT' },
|
|
590
|
+
}),
|
|
591
|
+
);
|
|
414
592
|
});
|
|
415
593
|
|
|
416
|
-
it('
|
|
594
|
+
it('preserves a real version of 0 from CCS (not coerced to 1 by a falsy-zero fallback)', async () => {
|
|
595
|
+
const onSave = jest.fn();
|
|
596
|
+
createCommDefinition.mockResolvedValueOnce({
|
|
597
|
+
response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 0 } } },
|
|
598
|
+
});
|
|
417
599
|
renderWithFlow({
|
|
418
600
|
features: {},
|
|
419
|
-
config:
|
|
601
|
+
config: ccsConfig,
|
|
420
602
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
603
|
+
onSave,
|
|
421
604
|
});
|
|
422
605
|
|
|
423
606
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
424
607
|
|
|
425
|
-
await waitFor(() => expect(
|
|
426
|
-
expect(
|
|
608
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
609
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
427
610
|
expect.objectContaining({
|
|
428
|
-
|
|
611
|
+
ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 0, status: 'DRAFT' },
|
|
429
612
|
}),
|
|
430
613
|
);
|
|
431
614
|
});
|
|
432
615
|
|
|
616
|
+
it('generates a referenceId from name when config.context.referenceId is absent', async () => {
|
|
617
|
+
// moduleMocks.js spies on Date.now globally, but this config's resetMocks:true
|
|
618
|
+
// clears that return value before every test — set it explicitly here.
|
|
619
|
+
jest.spyOn(Date, 'now').mockReturnValue(1612267539410);
|
|
620
|
+
renderWithFlow({
|
|
621
|
+
features: {},
|
|
622
|
+
config: ccsConfig,
|
|
623
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
627
|
+
|
|
628
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
629
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
630
|
+
expect(payload.referenceId).toBe('ORDER_PLACED_NOTIFICATION_1612267539410');
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('uses config.context.referenceId and description when provided', async () => {
|
|
634
|
+
renderWithFlow({
|
|
635
|
+
features: {},
|
|
636
|
+
config: { ...ccsConfig, context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc' } },
|
|
637
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
641
|
+
|
|
642
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
643
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
644
|
+
expect.objectContaining({ referenceId: 'ORDER_PLACED', description: 'desc' }),
|
|
645
|
+
);
|
|
646
|
+
});
|
|
647
|
+
|
|
433
648
|
it('skips CCS entirely when useCCS is false', async () => {
|
|
434
649
|
const onSave = jest.fn();
|
|
435
650
|
renderWithFlow({
|
|
436
651
|
features: {},
|
|
437
|
-
config: { ...
|
|
652
|
+
config: { ...ccsConfig, useCCS: false },
|
|
653
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
654
|
+
onSave,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
658
|
+
|
|
659
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
660
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
661
|
+
expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it('skips createCommDefinition when config.context.name is absent (mandatory)', async () => {
|
|
665
|
+
const onSave = jest.fn();
|
|
666
|
+
renderWithFlow({
|
|
667
|
+
features: {},
|
|
668
|
+
config: { ...baseConfig, features: {} },
|
|
438
669
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
439
670
|
onSave,
|
|
440
671
|
});
|
|
@@ -442,14 +673,34 @@ describe('handleSave — CCS flow', () => {
|
|
|
442
673
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
443
674
|
|
|
444
675
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
445
|
-
expect(
|
|
676
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it('skips createCommDefinition for multi-channel strategies (CHANNEL_PRIORITY/AB_TEST)', async () => {
|
|
680
|
+
const onSave = jest.fn();
|
|
681
|
+
renderWithFlow({
|
|
682
|
+
features: {},
|
|
683
|
+
config: ccsConfig,
|
|
684
|
+
initialData: {
|
|
685
|
+
communicationStrategy: CHANNEL_PRIORITY,
|
|
686
|
+
channels: ['SMS', 'EMAIL'],
|
|
687
|
+
contentItems: [{ channel: 'SMS', templateData: {} }, { channel: 'EMAIL', templateData: {} }],
|
|
688
|
+
},
|
|
689
|
+
onSave,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
693
|
+
|
|
694
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
695
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
446
696
|
});
|
|
447
697
|
|
|
448
|
-
it('still calls onSave when
|
|
449
|
-
|
|
698
|
+
it('still calls onSave (without ccsCommDefinition) when createCommDefinition rejects', async () => {
|
|
699
|
+
createCommDefinition.mockRejectedValue(new Error('Network error'));
|
|
450
700
|
const onSave = jest.fn();
|
|
451
701
|
renderWithFlow({
|
|
452
702
|
features: {},
|
|
703
|
+
config: ccsConfig,
|
|
453
704
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
454
705
|
onSave,
|
|
455
706
|
});
|
|
@@ -457,12 +708,35 @@ describe('handleSave — CCS flow', () => {
|
|
|
457
708
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
458
709
|
|
|
459
710
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
711
|
+
expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
|
|
460
712
|
});
|
|
461
713
|
|
|
462
|
-
it('
|
|
714
|
+
it('blocks save and shows an error when createCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
|
|
715
|
+
createCommDefinition.mockResolvedValue({
|
|
716
|
+
success: false,
|
|
717
|
+
status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
|
|
718
|
+
message: 'REFERENCE_ID_EXISTS',
|
|
719
|
+
});
|
|
463
720
|
const onSave = jest.fn();
|
|
464
721
|
renderWithFlow({
|
|
465
722
|
features: {},
|
|
723
|
+
config: ccsConfig,
|
|
724
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
725
|
+
onSave,
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
729
|
+
|
|
730
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
731
|
+
expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
|
|
732
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('skips createCommDefinition when contentItems is empty', async () => {
|
|
736
|
+
const onSave = jest.fn();
|
|
737
|
+
renderWithFlow({
|
|
738
|
+
features: {},
|
|
739
|
+
config: ccsConfig,
|
|
466
740
|
initialData: { contentItems: [] },
|
|
467
741
|
onSave,
|
|
468
742
|
});
|
|
@@ -470,12 +744,13 @@ describe('handleSave — CCS flow', () => {
|
|
|
470
744
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
471
745
|
|
|
472
746
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
473
|
-
expect(
|
|
747
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
474
748
|
});
|
|
475
749
|
|
|
476
|
-
it('
|
|
750
|
+
it('places additionalSettings derived from dynamicControls under settings.additionalSettings (not delivery settings)', async () => {
|
|
477
751
|
renderWithFlow({
|
|
478
752
|
features: {},
|
|
753
|
+
config: ccsConfig,
|
|
479
754
|
initialData: {
|
|
480
755
|
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
481
756
|
dynamicControls: {
|
|
@@ -489,14 +764,116 @@ describe('handleSave — CCS flow', () => {
|
|
|
489
764
|
|
|
490
765
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
491
766
|
|
|
492
|
-
await waitFor(() => expect(
|
|
493
|
-
const payload =
|
|
494
|
-
expect(payload.
|
|
767
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
768
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
769
|
+
expect(payload.settings.additionalSettings).toEqual({
|
|
495
770
|
useTinyUrl: true,
|
|
496
771
|
encryptUrl: true,
|
|
497
772
|
linkTrackingEnabled: true,
|
|
498
773
|
userSubscriptionDisabled: true,
|
|
499
774
|
});
|
|
775
|
+
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.additionalSettings).toBeUndefined();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('includes channelSettings from deliverySetting.channelSetting in the payload', async () => {
|
|
779
|
+
renderWithFlow({
|
|
780
|
+
features: {},
|
|
781
|
+
config: ccsConfig,
|
|
782
|
+
initialData: {
|
|
783
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
784
|
+
deliverySetting: { channelSetting: { SMS: { domainId: 1001, gsmSenderId: 'BRAND1' } } },
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
789
|
+
|
|
790
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
791
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
792
|
+
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.channelSettings).toEqual({
|
|
793
|
+
channel: 'SMS',
|
|
794
|
+
domainId: 1001,
|
|
795
|
+
gsmSenderId: 'BRAND1',
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('does not call editCommDefinition when config.context.existingCommDefinitionId is absent', async () => {
|
|
800
|
+
const onSave = jest.fn();
|
|
801
|
+
renderWithFlow({
|
|
802
|
+
features: {},
|
|
803
|
+
config: ccsConfig,
|
|
804
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
805
|
+
onSave,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
809
|
+
|
|
810
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
811
|
+
expect(editCommDefinition).not.toHaveBeenCalled();
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it('calls editCommDefinition (not createCommDefinition) and merges the existing id into ccsCommDefinition when config.context.existingCommDefinitionId is set', async () => {
|
|
815
|
+
editCommDefinition.mockResolvedValue({
|
|
816
|
+
response: { data: { version: { version: 2 }, status: 'DRAFT' } },
|
|
817
|
+
});
|
|
818
|
+
const onSave = jest.fn();
|
|
819
|
+
renderWithFlow({
|
|
820
|
+
features: {},
|
|
821
|
+
config: {
|
|
822
|
+
...ccsConfig,
|
|
823
|
+
context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', existingCommDefinitionId: 'cd_existing_1' },
|
|
824
|
+
},
|
|
825
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
826
|
+
onSave,
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
830
|
+
|
|
831
|
+
await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
|
|
832
|
+
expect(editCommDefinition).toHaveBeenCalledWith(
|
|
833
|
+
'cd_existing_1',
|
|
834
|
+
expect.objectContaining({
|
|
835
|
+
strategyType: 'SINGLE',
|
|
836
|
+
settings: expect.anything(),
|
|
837
|
+
singleChannelStrategy: expect.anything(),
|
|
838
|
+
}),
|
|
839
|
+
);
|
|
840
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
841
|
+
|
|
842
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
843
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
844
|
+
expect.objectContaining({
|
|
845
|
+
ccsCommDefinition: {
|
|
846
|
+
id: 'cd_existing_1',
|
|
847
|
+
referenceId: 'ORDER_PLACED',
|
|
848
|
+
version: 2,
|
|
849
|
+
status: 'DRAFT',
|
|
850
|
+
},
|
|
851
|
+
}),
|
|
852
|
+
);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
it('blocks save and shows an error when editCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
|
|
856
|
+
editCommDefinition.mockResolvedValue({
|
|
857
|
+
success: false,
|
|
858
|
+
status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
|
|
859
|
+
message: 'REFERENCE_ID_EXISTS',
|
|
860
|
+
});
|
|
861
|
+
const onSave = jest.fn();
|
|
862
|
+
renderWithFlow({
|
|
863
|
+
features: {},
|
|
864
|
+
config: {
|
|
865
|
+
...ccsConfig,
|
|
866
|
+
context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', existingCommDefinitionId: 'cd_existing_1' },
|
|
867
|
+
},
|
|
868
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
869
|
+
onSave,
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
873
|
+
|
|
874
|
+
await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
|
|
875
|
+
expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
|
|
876
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
500
877
|
});
|
|
501
878
|
});
|
|
502
879
|
|
|
@@ -552,39 +929,34 @@ describe('optional chaining safety', () => {
|
|
|
552
929
|
jest.clearAllMocks();
|
|
553
930
|
});
|
|
554
931
|
|
|
555
|
-
it('
|
|
556
|
-
|
|
932
|
+
it('does not crash and skips CCS when config.context is entirely absent', async () => {
|
|
933
|
+
const onSave = jest.fn();
|
|
557
934
|
renderWithFlow({
|
|
558
935
|
features: {},
|
|
936
|
+
config: { ...baseConfig, features: {} },
|
|
559
937
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
938
|
+
onSave,
|
|
560
939
|
});
|
|
561
940
|
|
|
562
941
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
563
942
|
|
|
564
|
-
await waitFor(() => expect(
|
|
565
|
-
expect(
|
|
566
|
-
expect.objectContaining({
|
|
567
|
-
centralCommsPayload: expect.objectContaining({ ouId: -1 }),
|
|
568
|
-
}),
|
|
569
|
-
);
|
|
943
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
944
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
570
945
|
});
|
|
571
946
|
|
|
572
|
-
it('
|
|
573
|
-
|
|
947
|
+
it('does not crash when config.context is present but empty', async () => {
|
|
948
|
+
const onSave = jest.fn();
|
|
574
949
|
renderWithFlow({
|
|
575
950
|
features: {},
|
|
576
|
-
config: { ...baseConfig,
|
|
951
|
+
config: { ...baseConfig, context: {}, features: {} },
|
|
577
952
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
953
|
+
onSave,
|
|
578
954
|
});
|
|
579
955
|
|
|
580
956
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
581
957
|
|
|
582
|
-
await waitFor(() => expect(
|
|
583
|
-
expect(
|
|
584
|
-
expect.objectContaining({
|
|
585
|
-
centralCommsPayload: expect.objectContaining({ module: 'LOYALTY' }),
|
|
586
|
-
}),
|
|
587
|
-
);
|
|
958
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
959
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
588
960
|
});
|
|
589
961
|
|
|
590
962
|
it('initializes channel from config.channel when initialData.channel is absent', () => {
|
|
@@ -108,6 +108,22 @@ describe('CommunicationFlowCard', () => {
|
|
|
108
108
|
fireEvent.click(screen.getByText('Add creatives'));
|
|
109
109
|
expect(screen.getByTestId('comm-flow-mock')).toHaveAttribute('data-mode', 'create');
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
it('disables the Add creatives button when disabled prop is true', () => {
|
|
113
|
+
renderCard({ disabled: true, disabledTooltip: 'Enter an Alert name to add content' });
|
|
114
|
+
expect(screen.getByText('Add creatives').closest('button')).toBeDisabled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('does not open SlideBox when Add creatives is clicked while disabled', () => {
|
|
118
|
+
renderCard({ disabled: true, disabledTooltip: 'Enter an Alert name to add content' });
|
|
119
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
120
|
+
expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('Add creatives button is enabled by default (disabled prop absent)', () => {
|
|
124
|
+
renderCard();
|
|
125
|
+
expect(screen.getByText('Add creatives').closest('button')).not.toBeDisabled();
|
|
126
|
+
});
|
|
111
127
|
});
|
|
112
128
|
|
|
113
129
|
describe('configured state (initialData provided)', () => {
|
|
@@ -231,11 +247,11 @@ describe('CommunicationFlowCard', () => {
|
|
|
231
247
|
expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
|
|
232
248
|
});
|
|
233
249
|
|
|
234
|
-
it('shows
|
|
250
|
+
it('shows senderId for EMAIL with Sender ID label', () => {
|
|
235
251
|
renderCard({
|
|
236
252
|
initialData: makeSavedData({
|
|
237
253
|
contentItems: [{ channel: 'EMAIL', templateData: {} }],
|
|
238
|
-
deliverySetting: { channelSetting: { EMAIL: {
|
|
254
|
+
deliverySetting: { channelSetting: { EMAIL: { senderId: 'test@example.com' } } },
|
|
239
255
|
}),
|
|
240
256
|
});
|
|
241
257
|
expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
|