@capillarytech/creatives-library 9.0.54-0 → 9.0.54-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 +9 -0
- package/utils/common.js +0 -4
- package/v2Components/CommonTestAndPreview/index.js +0 -3
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +96 -58
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +20 -4
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +227 -49
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +16 -0
- package/v2Containers/CommunicationFlow/constants.js +66 -0
- package/v2Containers/CommunicationFlow/index.js +15 -20
- package/v2Containers/CommunicationFlow/messages.js +4 -0
- package/v2Containers/CreativesContainer/index.js +0 -2
- package/v2Containers/Rcs/constants.js +0 -1
- package/v2Containers/Rcs/index.js +1 -9
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -108
- package/v2Containers/Rcs/tests/index.test.js +0 -121
- package/v2Containers/Templates/index.js +0 -20
- package/.vscode/settings.json +0 -3
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
jest.mock('../../../services/api', () => ({
|
|
4
|
-
|
|
5
|
-
getCentralCommsMetaIds: jest.fn(),
|
|
4
|
+
createCommDefinition: jest.fn(),
|
|
6
5
|
}));
|
|
7
6
|
|
|
8
7
|
jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
|
|
@@ -38,7 +37,7 @@ import { IntlProvider } from 'react-intl';
|
|
|
38
37
|
import history from '../../../utils/history';
|
|
39
38
|
import { initialReducer } from '../../../initialReducer';
|
|
40
39
|
import CommunicationFlow from '../CommunicationFlow';
|
|
41
|
-
import {
|
|
40
|
+
import { createCommDefinition } from '../../../services/api';
|
|
42
41
|
import { getEnabledSteps } from '../utils/getEnabledSteps';
|
|
43
42
|
import {
|
|
44
43
|
CHANNELS,
|
|
@@ -359,82 +358,200 @@ describe('isSaveDisabled', () => {
|
|
|
359
358
|
});
|
|
360
359
|
|
|
361
360
|
describe('handleSave — CCS flow', () => {
|
|
361
|
+
const ccsConfig = { ...baseConfig, context: { name: 'Order Placed Notification' }, features: {} };
|
|
362
|
+
|
|
362
363
|
beforeEach(() => {
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
createCommDefinition.mockResolvedValue({
|
|
365
|
+
response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 1 } } },
|
|
366
|
+
});
|
|
365
367
|
});
|
|
366
368
|
|
|
367
369
|
afterEach(() => {
|
|
368
370
|
jest.clearAllMocks();
|
|
369
371
|
});
|
|
370
372
|
|
|
371
|
-
it('calls
|
|
373
|
+
it('calls createCommDefinition with a SINGLE-strategy payload when useCCS is not false', async () => {
|
|
372
374
|
const onSave = jest.fn();
|
|
373
375
|
renderWithFlow({
|
|
374
376
|
features: {},
|
|
377
|
+
config: ccsConfig,
|
|
375
378
|
initialData: {
|
|
376
|
-
contentItems: [{ channel: 'sms', templateData: {
|
|
379
|
+
contentItems: [{ channel: 'sms', templateData: { message: 'Hello' } }],
|
|
377
380
|
},
|
|
378
381
|
onSave,
|
|
379
382
|
});
|
|
380
383
|
|
|
381
384
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
382
385
|
|
|
383
|
-
await waitFor(() => expect(
|
|
384
|
-
expect(
|
|
386
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
387
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
385
388
|
expect.objectContaining({
|
|
386
|
-
|
|
389
|
+
name: 'Order Placed Notification',
|
|
390
|
+
strategyType: 'SINGLE',
|
|
391
|
+
singleChannelStrategy: expect.objectContaining({
|
|
392
|
+
variant: expect.objectContaining({
|
|
393
|
+
channel: 'SMS',
|
|
394
|
+
smsMessageContent: { channel: 'SMS', message: 'Hello' },
|
|
395
|
+
}),
|
|
396
|
+
}),
|
|
387
397
|
}),
|
|
388
398
|
);
|
|
389
399
|
expect(onSave).toHaveBeenCalledTimes(1);
|
|
390
400
|
});
|
|
391
401
|
|
|
392
|
-
it('
|
|
402
|
+
it('transforms EMAIL content from the legacy editor shape (emailSubject) to CCS\'s messageSubject', async () => {
|
|
403
|
+
const onSave = jest.fn();
|
|
393
404
|
renderWithFlow({
|
|
394
405
|
features: {},
|
|
395
|
-
|
|
406
|
+
config: ccsConfig,
|
|
407
|
+
initialData: {
|
|
408
|
+
contentItems: [{
|
|
409
|
+
channel: 'EMAIL',
|
|
410
|
+
templateData: { emailSubject: 'Order Confirmed', messageBody: '<html>Hi</html>' },
|
|
411
|
+
}],
|
|
412
|
+
},
|
|
413
|
+
onSave,
|
|
396
414
|
});
|
|
397
415
|
|
|
398
416
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
399
417
|
|
|
400
|
-
await waitFor(() => expect(
|
|
418
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
419
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
420
|
+
expect.objectContaining({
|
|
421
|
+
singleChannelStrategy: expect.objectContaining({
|
|
422
|
+
variant: expect.objectContaining({
|
|
423
|
+
channel: 'EMAIL',
|
|
424
|
+
emailMessageContent: {
|
|
425
|
+
channel: 'EMAIL',
|
|
426
|
+
messageSubject: 'Order Confirmed',
|
|
427
|
+
messageBody: '<html>Hi</html>',
|
|
428
|
+
},
|
|
429
|
+
}),
|
|
430
|
+
}),
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
401
433
|
});
|
|
402
434
|
|
|
403
|
-
it('
|
|
404
|
-
|
|
435
|
+
it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*)', async () => {
|
|
436
|
+
const onSave = jest.fn();
|
|
405
437
|
renderWithFlow({
|
|
406
438
|
features: {},
|
|
439
|
+
config: ccsConfig,
|
|
440
|
+
initialData: {
|
|
441
|
+
contentItems: [{ channel: 'MOBILEPUSH', templateData: { messageSubject: 'Hi' } }],
|
|
442
|
+
},
|
|
443
|
+
onSave,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
447
|
+
|
|
448
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
449
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
450
|
+
expect.objectContaining({
|
|
451
|
+
singleChannelStrategy: expect.objectContaining({
|
|
452
|
+
variant: expect.objectContaining({
|
|
453
|
+
channel: 'PUSH',
|
|
454
|
+
mpushMessageContent: { channel: 'PUSH', messageSubject: 'Hi' },
|
|
455
|
+
}),
|
|
456
|
+
}),
|
|
457
|
+
}),
|
|
458
|
+
);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('merges ccsCommDefinition into the data passed to onSave when create succeeds', async () => {
|
|
462
|
+
const onSave = jest.fn();
|
|
463
|
+
renderWithFlow({
|
|
464
|
+
features: {},
|
|
465
|
+
config: ccsConfig,
|
|
407
466
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
467
|
+
onSave,
|
|
408
468
|
});
|
|
409
469
|
|
|
410
470
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
411
471
|
|
|
412
|
-
await waitFor(() => expect(
|
|
413
|
-
expect(
|
|
472
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
473
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
474
|
+
expect.objectContaining({
|
|
475
|
+
ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 1, status: 'DRAFT' },
|
|
476
|
+
}),
|
|
477
|
+
);
|
|
414
478
|
});
|
|
415
479
|
|
|
416
|
-
it('
|
|
480
|
+
it('preserves a real version of 0 from CCS (not coerced to 1 by a falsy-zero fallback)', async () => {
|
|
481
|
+
const onSave = jest.fn();
|
|
482
|
+
createCommDefinition.mockResolvedValueOnce({
|
|
483
|
+
response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 0 } } },
|
|
484
|
+
});
|
|
417
485
|
renderWithFlow({
|
|
418
486
|
features: {},
|
|
419
|
-
config:
|
|
487
|
+
config: ccsConfig,
|
|
420
488
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
489
|
+
onSave,
|
|
421
490
|
});
|
|
422
491
|
|
|
423
492
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
424
493
|
|
|
425
|
-
await waitFor(() => expect(
|
|
426
|
-
expect(
|
|
494
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
495
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
427
496
|
expect.objectContaining({
|
|
428
|
-
|
|
497
|
+
ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 0, status: 'DRAFT' },
|
|
429
498
|
}),
|
|
430
499
|
);
|
|
431
500
|
});
|
|
432
501
|
|
|
502
|
+
it('generates a referenceId from name when config.context.referenceId is absent', async () => {
|
|
503
|
+
// moduleMocks.js spies on Date.now globally, but this config's resetMocks:true
|
|
504
|
+
// clears that return value before every test — set it explicitly here.
|
|
505
|
+
jest.spyOn(Date, 'now').mockReturnValue(1612267539410);
|
|
506
|
+
renderWithFlow({
|
|
507
|
+
features: {},
|
|
508
|
+
config: ccsConfig,
|
|
509
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
513
|
+
|
|
514
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
515
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
516
|
+
expect(payload.referenceId).toBe('ORDER_PLACED_NOTIFICATION_1612267539410');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('uses config.context.referenceId and description when provided', async () => {
|
|
520
|
+
renderWithFlow({
|
|
521
|
+
features: {},
|
|
522
|
+
config: { ...ccsConfig, context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc' } },
|
|
523
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
527
|
+
|
|
528
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
529
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
530
|
+
expect.objectContaining({ referenceId: 'ORDER_PLACED', description: 'desc' }),
|
|
531
|
+
);
|
|
532
|
+
});
|
|
533
|
+
|
|
433
534
|
it('skips CCS entirely when useCCS is false', async () => {
|
|
434
535
|
const onSave = jest.fn();
|
|
435
536
|
renderWithFlow({
|
|
436
537
|
features: {},
|
|
437
|
-
config: { ...
|
|
538
|
+
config: { ...ccsConfig, useCCS: false },
|
|
539
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
540
|
+
onSave,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
544
|
+
|
|
545
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
546
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
547
|
+
expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('skips createCommDefinition when config.context.name is absent (mandatory)', async () => {
|
|
551
|
+
const onSave = jest.fn();
|
|
552
|
+
renderWithFlow({
|
|
553
|
+
features: {},
|
|
554
|
+
config: { ...baseConfig, features: {} },
|
|
438
555
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
439
556
|
onSave,
|
|
440
557
|
});
|
|
@@ -442,14 +559,34 @@ describe('handleSave — CCS flow', () => {
|
|
|
442
559
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
443
560
|
|
|
444
561
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
445
|
-
expect(
|
|
562
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
446
563
|
});
|
|
447
564
|
|
|
448
|
-
it('
|
|
449
|
-
createCentralCommsMetaId.mockRejectedValue(new Error('Network error'));
|
|
565
|
+
it('skips createCommDefinition for multi-channel strategies (CHANNEL_PRIORITY/AB_TEST)', async () => {
|
|
450
566
|
const onSave = jest.fn();
|
|
451
567
|
renderWithFlow({
|
|
452
568
|
features: {},
|
|
569
|
+
config: ccsConfig,
|
|
570
|
+
initialData: {
|
|
571
|
+
communicationStrategy: CHANNEL_PRIORITY,
|
|
572
|
+
channels: ['SMS', 'EMAIL'],
|
|
573
|
+
contentItems: [{ channel: 'SMS', templateData: {} }, { channel: 'EMAIL', templateData: {} }],
|
|
574
|
+
},
|
|
575
|
+
onSave,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
579
|
+
|
|
580
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
581
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('still calls onSave (without ccsCommDefinition) when createCommDefinition rejects', async () => {
|
|
585
|
+
createCommDefinition.mockRejectedValue(new Error('Network error'));
|
|
586
|
+
const onSave = jest.fn();
|
|
587
|
+
renderWithFlow({
|
|
588
|
+
features: {},
|
|
589
|
+
config: ccsConfig,
|
|
453
590
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
454
591
|
onSave,
|
|
455
592
|
});
|
|
@@ -457,12 +594,35 @@ describe('handleSave — CCS flow', () => {
|
|
|
457
594
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
458
595
|
|
|
459
596
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
597
|
+
expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('blocks save and shows an error when createCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
|
|
601
|
+
createCommDefinition.mockResolvedValue({
|
|
602
|
+
success: false,
|
|
603
|
+
status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
|
|
604
|
+
message: 'REFERENCE_ID_EXISTS',
|
|
605
|
+
});
|
|
606
|
+
const onSave = jest.fn();
|
|
607
|
+
renderWithFlow({
|
|
608
|
+
features: {},
|
|
609
|
+
config: ccsConfig,
|
|
610
|
+
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
611
|
+
onSave,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
615
|
+
|
|
616
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
617
|
+
expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
|
|
618
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
460
619
|
});
|
|
461
620
|
|
|
462
|
-
it('skips
|
|
621
|
+
it('skips createCommDefinition when contentItems is empty', async () => {
|
|
463
622
|
const onSave = jest.fn();
|
|
464
623
|
renderWithFlow({
|
|
465
624
|
features: {},
|
|
625
|
+
config: ccsConfig,
|
|
466
626
|
initialData: { contentItems: [] },
|
|
467
627
|
onSave,
|
|
468
628
|
});
|
|
@@ -470,12 +630,13 @@ describe('handleSave — CCS flow', () => {
|
|
|
470
630
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
471
631
|
|
|
472
632
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
473
|
-
expect(
|
|
633
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
474
634
|
});
|
|
475
635
|
|
|
476
|
-
it('
|
|
636
|
+
it('places additionalSettings derived from dynamicControls under settings.additionalSettings (not delivery settings)', async () => {
|
|
477
637
|
renderWithFlow({
|
|
478
638
|
features: {},
|
|
639
|
+
config: ccsConfig,
|
|
479
640
|
initialData: {
|
|
480
641
|
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
481
642
|
dynamicControls: {
|
|
@@ -489,14 +650,36 @@ describe('handleSave — CCS flow', () => {
|
|
|
489
650
|
|
|
490
651
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
491
652
|
|
|
492
|
-
await waitFor(() => expect(
|
|
493
|
-
const payload =
|
|
494
|
-
expect(payload.
|
|
653
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
654
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
655
|
+
expect(payload.settings.additionalSettings).toEqual({
|
|
495
656
|
useTinyUrl: true,
|
|
496
657
|
encryptUrl: true,
|
|
497
658
|
linkTrackingEnabled: true,
|
|
498
659
|
userSubscriptionDisabled: true,
|
|
499
660
|
});
|
|
661
|
+
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.additionalSettings).toBeUndefined();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it('includes channelSettings from deliverySetting.channelSetting in the payload', async () => {
|
|
665
|
+
renderWithFlow({
|
|
666
|
+
features: {},
|
|
667
|
+
config: ccsConfig,
|
|
668
|
+
initialData: {
|
|
669
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
670
|
+
deliverySetting: { channelSetting: { SMS: { domainId: 1001, gsmSenderId: 'BRAND1' } } },
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
675
|
+
|
|
676
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
677
|
+
const payload = createCommDefinition.mock.calls[0][0];
|
|
678
|
+
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.channelSettings).toEqual({
|
|
679
|
+
channel: 'SMS',
|
|
680
|
+
domainId: 1001,
|
|
681
|
+
gsmSenderId: 'BRAND1',
|
|
682
|
+
});
|
|
500
683
|
});
|
|
501
684
|
});
|
|
502
685
|
|
|
@@ -552,39 +735,34 @@ describe('optional chaining safety', () => {
|
|
|
552
735
|
jest.clearAllMocks();
|
|
553
736
|
});
|
|
554
737
|
|
|
555
|
-
it('
|
|
556
|
-
|
|
738
|
+
it('does not crash and skips CCS when config.context is entirely absent', async () => {
|
|
739
|
+
const onSave = jest.fn();
|
|
557
740
|
renderWithFlow({
|
|
558
741
|
features: {},
|
|
742
|
+
config: { ...baseConfig, features: {} },
|
|
559
743
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
744
|
+
onSave,
|
|
560
745
|
});
|
|
561
746
|
|
|
562
747
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
563
748
|
|
|
564
|
-
await waitFor(() => expect(
|
|
565
|
-
expect(
|
|
566
|
-
expect.objectContaining({
|
|
567
|
-
centralCommsPayload: expect.objectContaining({ ouId: -1 }),
|
|
568
|
-
}),
|
|
569
|
-
);
|
|
749
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
750
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
570
751
|
});
|
|
571
752
|
|
|
572
|
-
it('
|
|
573
|
-
|
|
753
|
+
it('does not crash when config.context is present but empty', async () => {
|
|
754
|
+
const onSave = jest.fn();
|
|
574
755
|
renderWithFlow({
|
|
575
756
|
features: {},
|
|
576
|
-
config: { ...baseConfig,
|
|
757
|
+
config: { ...baseConfig, context: {}, features: {} },
|
|
577
758
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
759
|
+
onSave,
|
|
578
760
|
});
|
|
579
761
|
|
|
580
762
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
581
763
|
|
|
582
|
-
await waitFor(() => expect(
|
|
583
|
-
expect(
|
|
584
|
-
expect.objectContaining({
|
|
585
|
-
centralCommsPayload: expect.objectContaining({ module: 'LOYALTY' }),
|
|
586
|
-
}),
|
|
587
|
-
);
|
|
764
|
+
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
765
|
+
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
588
766
|
});
|
|
589
767
|
|
|
590
768
|
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)', () => {
|
|
@@ -38,6 +38,72 @@ export const CHANNEL_PRIORITY = 'CHANNEL_PRIORITY';
|
|
|
38
38
|
export const AB_TEST = 'AB_TEST';
|
|
39
39
|
export const SINGLE_TEMPLATE = 'SINGLE_TEMPLATE';
|
|
40
40
|
|
|
41
|
+
// CCS CommDefinition strategyType — distinct from SINGLE_TEMPLATE above, which
|
|
42
|
+
// is this UI's own communicationStrategy value. Only SINGLE is supported this
|
|
43
|
+
// phase; CCS create is skipped entirely for CHANNEL_PRIORITY/AB_TEST.
|
|
44
|
+
export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
|
|
45
|
+
|
|
46
|
+
// Mirrors CCS's SingleChannelStrategy field names exactly (Cap Notify API
|
|
47
|
+
// Design doc sample payloads, verified against real create responses). Kept
|
|
48
|
+
// separate from CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP below, which
|
|
49
|
+
// are the legacy messageMeta payload's keys.
|
|
50
|
+
export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
51
|
+
SMS: 'smsMessageContent',
|
|
52
|
+
EMAIL: 'emailMessageContent',
|
|
53
|
+
WHATSAPP: 'whatsappMessageContent',
|
|
54
|
+
// CCS's channel enum value for mobile push is 'PUSH' (verified against real
|
|
55
|
+
// create responses), even though the content/delivery keys stay 'mpush*'.
|
|
56
|
+
PUSH: 'mpushMessageContent',
|
|
57
|
+
INAPP: 'inAppMessageContent',
|
|
58
|
+
ANDROID: 'androidMessageContent',
|
|
59
|
+
IOS: 'iosMessageContent',
|
|
60
|
+
ZALO: 'zaloMessageContent',
|
|
61
|
+
VIBER: 'viberMessageContent',
|
|
62
|
+
LINE: 'lineMessageContent',
|
|
63
|
+
WEBPUSH: 'webPushMessageContent',
|
|
64
|
+
RCS: 'rcsMessageContent',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// contentItem.templateData is built by the legacy CreativesContainer content
|
|
68
|
+
// editor (messageMeta-oriented field names) — for channels whose legacy field
|
|
69
|
+
// names don't match CCS's own content schema, transform before sending rather
|
|
70
|
+
// than dumping templateData as-is. EMAIL: legacy stores the subject as
|
|
71
|
+
// `emailSubject` (see getContentBody.js), CCS expects `messageSubject`;
|
|
72
|
+
// `messageBody` already matches CCS's name by coincidence. Channels absent
|
|
73
|
+
// here are passed through unchanged (their legacy/CCS field names already
|
|
74
|
+
// align, e.g. SMS's `message`/`messageBody`).
|
|
75
|
+
export const CCS_CONTENT_TRANSFORMS = {
|
|
76
|
+
EMAIL: (templateData = {}) => ({
|
|
77
|
+
messageSubject: templateData.emailSubject,
|
|
78
|
+
messageBody: templateData.messageBody,
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
|
|
83
|
+
SMS: 'smsDeliverySettings',
|
|
84
|
+
EMAIL: 'emailDeliverySettings',
|
|
85
|
+
WHATSAPP: 'whatsappDeliverySettings',
|
|
86
|
+
PUSH: 'mpushDeliverySettings',
|
|
87
|
+
INAPP: 'inAppDeliverySettings',
|
|
88
|
+
ANDROID: 'androidDeliverySettings',
|
|
89
|
+
IOS: 'iosDeliverySettings',
|
|
90
|
+
ZALO: 'zaloDeliverySettings',
|
|
91
|
+
VIBER: 'viberDeliverySettings',
|
|
92
|
+
LINE: 'lineDeliverySettings',
|
|
93
|
+
WEBPUSH: 'webPushDeliverySettings',
|
|
94
|
+
RCS: 'rcsDeliverySettings',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// The UI's own internal channel identifier for mobile push is 'MOBILEPUSH'
|
|
98
|
+
// (see CreativesContainer/constants.js MOBILE_PUSH), but CCS's channel enum
|
|
99
|
+
// value is 'PUSH' (verified against real create responses — the content/
|
|
100
|
+
// delivery KEYS still say 'mpush*', only the enum VALUE is 'PUSH') —
|
|
101
|
+
// normalize before building the CCS payload. Every other channel identifier
|
|
102
|
+
// matches CCS as-is.
|
|
103
|
+
export const CCS_CHANNEL_NAME_MAP = {
|
|
104
|
+
MOBILEPUSH: 'PUSH',
|
|
105
|
+
};
|
|
106
|
+
|
|
41
107
|
// Channel config - single source of truth for CreativesContainer and TemplatesV2
|
|
42
108
|
// paneKey: TemplatesV2 defaultPanes object key (for channelsToHide)
|
|
43
109
|
// channelProp: CreativesContainer channel prop (must match pane.key for tab to be active)
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import PropTypes from 'prop-types';
|
|
11
11
|
import CommunicationFlow from './CommunicationFlow';
|
|
12
|
-
import { hasSupportEngagementModule } from '../../utils/common';
|
|
13
12
|
|
|
14
13
|
const CommunicationFlowContainer = ({
|
|
15
14
|
config,
|
|
@@ -18,22 +17,16 @@ const CommunicationFlowContainer = ({
|
|
|
18
17
|
onCancel,
|
|
19
18
|
onChange,
|
|
20
19
|
...otherProps
|
|
21
|
-
}) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
onCancel={onCancel}
|
|
32
|
-
onChange={onChange}
|
|
33
|
-
{...otherProps}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
20
|
+
}) => (
|
|
21
|
+
<CommunicationFlow
|
|
22
|
+
config={config}
|
|
23
|
+
initialData={initialData}
|
|
24
|
+
onSave={onSave}
|
|
25
|
+
onCancel={onCancel}
|
|
26
|
+
onChange={onChange}
|
|
27
|
+
{...otherProps}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
37
30
|
|
|
38
31
|
CommunicationFlowContainer.propTypes = {
|
|
39
32
|
config: PropTypes.shape({
|
|
@@ -87,11 +80,13 @@ CommunicationFlowContainer.propTypes = {
|
|
|
87
80
|
controls: PropTypes.array,
|
|
88
81
|
}),
|
|
89
82
|
}),
|
|
90
|
-
context: PropTypes.object, // ouId, campaignId, programId,
|
|
91
|
-
|
|
83
|
+
context: PropTypes.object, // ouId, campaignId, programId; name (required for CCS create),
|
|
84
|
+
// referenceId (optional, auto-generated from name if absent), description (optional).
|
|
85
|
+
useCCS: PropTypes.bool, // If false, skips the CCS createCommDefinition call on save. Defaults to true.
|
|
92
86
|
}).isRequired,
|
|
93
87
|
initialData: PropTypes.object, // for edit/preview mode
|
|
94
|
-
onSave: PropTypes.func.isRequired, // (data) => void - called when user saves
|
|
88
|
+
onSave: PropTypes.func.isRequired, // (data) => void - called when user saves; data.ccsCommDefinition
|
|
89
|
+
// ({id, referenceId, version, status}) is present when the CCS create succeeded
|
|
95
90
|
onCancel: PropTypes.func.isRequired, // () => void - called when user cancels
|
|
96
91
|
onChange: PropTypes.func, // (data) => void - optional, called on data changes
|
|
97
92
|
};
|
|
@@ -363,4 +363,8 @@ export default {
|
|
|
363
363
|
id: `${prefix}.senderNotConfiguredError`,
|
|
364
364
|
defaultMessage: 'Selected domain gateway id is not correct. Please change the domain id or contact the gateway team to register them with Capillary.',
|
|
365
365
|
},
|
|
366
|
+
duplicateReferenceIdError: {
|
|
367
|
+
id: `${prefix}.duplicateReferenceIdError`,
|
|
368
|
+
defaultMessage: 'This reference ID is already in use in your organization. Please use a different reference ID.',
|
|
369
|
+
},
|
|
366
370
|
};
|
|
@@ -1441,7 +1441,6 @@ export class Creatives extends React.Component {
|
|
|
1441
1441
|
cardType = "",
|
|
1442
1442
|
cardSettings = {},
|
|
1443
1443
|
accountId = "",
|
|
1444
|
-
vfTemplateId,
|
|
1445
1444
|
} = get(versions, 'base.content.RCS.rcsContent', {});
|
|
1446
1445
|
const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
|
|
1447
1446
|
? cardContentFromSubmit[0]
|
|
@@ -1470,7 +1469,6 @@ export class Creatives extends React.Component {
|
|
|
1470
1469
|
creativeName: name,
|
|
1471
1470
|
rcsContent,
|
|
1472
1471
|
accountId,
|
|
1473
|
-
...(vfTemplateId && { templateId: vfTemplateId }),
|
|
1474
1472
|
...(includeRootRcsCardVarMappedOnConsumerPayload
|
|
1475
1473
|
? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
|
|
1476
1474
|
: {}),
|
|
@@ -358,8 +358,6 @@ export const Rcs = (props) => {
|
|
|
358
358
|
const [accessToken, setAccessToken] = useState('');
|
|
359
359
|
const [hostName, setHostName] = useState('');
|
|
360
360
|
const [accountName, setAccountName] = useState('');
|
|
361
|
-
const [botId, setBotId] = useState('');
|
|
362
|
-
const [vfTemplateId, setVfTemplateId] = useState('');
|
|
363
361
|
const isHostInfoBip = hostName === HOST_INFOBIP;
|
|
364
362
|
const isHostIcs = hostName === HOST_ICS;
|
|
365
363
|
|
|
@@ -383,14 +381,12 @@ export const Rcs = (props) => {
|
|
|
383
381
|
setHostName(accountObj.hostName || '');
|
|
384
382
|
setAccountName(accountObj.name || '');
|
|
385
383
|
setRcsAccount(accountObj.id || '');
|
|
386
|
-
setBotId(accountObj.connectionProperties?.agentid || '');
|
|
387
384
|
} else {
|
|
388
385
|
setAccountId('');
|
|
389
386
|
setWecrmAccountId('');
|
|
390
387
|
setAccessToken('');
|
|
391
388
|
setHostName('');
|
|
392
389
|
setAccountName('');
|
|
393
|
-
setBotId('');
|
|
394
390
|
}
|
|
395
391
|
}, [accountData.selectedRcsAccount]);
|
|
396
392
|
|
|
@@ -1391,8 +1387,6 @@ export const Rcs = (props) => {
|
|
|
1391
1387
|
const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
|
|
1392
1388
|
setRcsAccount(rcsAccountId);
|
|
1393
1389
|
}
|
|
1394
|
-
const storedVfTemplateId = get(details, 'versions.base.content.RCS.rcsContent.vfTemplateId', '');
|
|
1395
|
-
if (storedVfTemplateId) setVfTemplateId(storedVfTemplateId);
|
|
1396
1390
|
|
|
1397
1391
|
applySmsFallbackHydration({
|
|
1398
1392
|
details,
|
|
@@ -2830,8 +2824,7 @@ const onTitleAddVar = () => {
|
|
|
2830
2824
|
}
|
|
2831
2825
|
],
|
|
2832
2826
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2833
|
-
...(isFullMode && {accountId, accessToken, accountName, hostName
|
|
2834
|
-
...(vfTemplateId && { vfTemplateId }),
|
|
2827
|
+
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2835
2828
|
},
|
|
2836
2829
|
...smsFallbackPayloadContent,
|
|
2837
2830
|
},
|
|
@@ -2971,7 +2964,6 @@ const onTitleAddVar = () => {
|
|
|
2971
2964
|
accountName,
|
|
2972
2965
|
hostName,
|
|
2973
2966
|
smsRegister,
|
|
2974
|
-
vfTemplateId,
|
|
2975
2967
|
]);
|
|
2976
2968
|
|
|
2977
2969
|
/**
|