@capillarytech/creatives-library 9.0.56-alpha.6 → 9.0.56-betaa.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.
- package/constants/unified.js +4 -2
- package/package.json +1 -1
- package/services/api.js +0 -20
- package/utils/common.js +12 -0
- package/utils/templateVarUtils.js +0 -32
- package/utils/tests/templateVarUtils.test.js +0 -44
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +0 -14
- package/v2Components/CommonTestAndPreview/actions.js +0 -13
- package/v2Components/CommonTestAndPreview/constants.js +0 -8
- package/v2Components/CommonTestAndPreview/index.js +2 -57
- package/v2Components/CommonTestAndPreview/reducer.js +0 -20
- package/v2Components/CommonTestAndPreview/sagas.js +0 -28
- package/v2Components/CommonTestAndPreview/selectors.js +0 -6
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +0 -106
- package/v2Components/CommonTestAndPreview/tests/actions.test.js +0 -15
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -9
- package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -101
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -41
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +0 -84
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -12
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +320 -46
- package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +10 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +82 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +74 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +28 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +19 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +45 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +162 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +37 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +70 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +274 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +251 -0
- package/v2Components/FormBuilder/Functional/channels/registry.js +2 -0
- package/v2Components/FormBuilder/Functional/constants.js +43 -8
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +175 -28
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +75 -6
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +10 -20
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +9 -1
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +20 -3
- package/v2Components/FormBuilder/Functional/layout/Section.js +6 -1
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +89 -0
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +440 -0
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +9 -2
- package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +430 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +282 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +141 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +306 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +151 -0
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +342 -0
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +17 -0
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +110 -0
- package/v2Components/FormBuilder/index.js +34 -12
- package/v2Components/FormBuilder/tests/entryGate.test.js +94 -7
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +459 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +0 -9
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +75 -128
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +2 -30
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +27 -58
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +51 -471
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +4 -40
- package/v2Containers/CommunicationFlow/constants.js +4 -67
- package/v2Containers/CommunicationFlow/index.js +23 -16
- package/v2Containers/CommunicationFlow/messages.js +3 -7
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +10 -22
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +0 -4
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +4 -112
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +0 -3
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +0 -6
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +0 -25
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +4 -11
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +5 -19
- package/v2Containers/CreativesContainer/index.js +3 -2
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -5
- package/v2Containers/CreativesContainer/tests/index.test.js +0 -12
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +19 -115
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +3 -27
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +12 -17
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -5
- package/v2Containers/Whatsapp/index.js +3 -8
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -306
- package/v2Containers/mockdata.js +0 -25
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +0 -85
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
jest.mock('../../../services/api', () => ({
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
createCentralCommsMetaId: jest.fn(),
|
|
5
|
+
getCentralCommsMetaIds: 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 { createCentralCommsMetaId, getCentralCommsMetaIds } from '../../../services/api';
|
|
42
42
|
import { getEnabledSteps } from '../utils/getEnabledSteps';
|
|
43
43
|
import {
|
|
44
44
|
CHANNELS,
|
|
@@ -192,13 +192,13 @@ describe('CommunicationFlow', () => {
|
|
|
192
192
|
it('hides channel selection and dynamic controls until a communication strategy is set', async () => {
|
|
193
193
|
renderWithFlow({ features: FEATURES.strategyContentDynamic });
|
|
194
194
|
|
|
195
|
-
expect(screen.queryByText(/
|
|
195
|
+
expect(screen.queryByText(/content template/i)).not.toBeInTheDocument();
|
|
196
196
|
expect(screen.queryByText(/other controls/i)).not.toBeInTheDocument();
|
|
197
197
|
|
|
198
198
|
await selectCommunicationStrategy('Single template');
|
|
199
199
|
|
|
200
200
|
await waitFor(() => {
|
|
201
|
-
expect(screen.getAllByText(/
|
|
201
|
+
expect(screen.getAllByText(/content template/i).length).toBeGreaterThan(0);
|
|
202
202
|
});
|
|
203
203
|
expect(screen.getByText(/other controls/i)).toBeInTheDocument();
|
|
204
204
|
});
|
|
@@ -359,295 +359,82 @@ describe('isSaveDisabled', () => {
|
|
|
359
359
|
});
|
|
360
360
|
|
|
361
361
|
describe('handleSave — CCS flow', () => {
|
|
362
|
-
const ccsConfig = { ...baseConfig, context: { name: 'Order Placed Notification' }, features: {} };
|
|
363
|
-
|
|
364
362
|
beforeEach(() => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
});
|
|
363
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'meta-123' } } });
|
|
364
|
+
getCentralCommsMetaIds.mockResolvedValue({ response: { data: {} } });
|
|
368
365
|
});
|
|
369
366
|
|
|
370
367
|
afterEach(() => {
|
|
371
368
|
jest.clearAllMocks();
|
|
372
369
|
});
|
|
373
370
|
|
|
374
|
-
it('calls
|
|
371
|
+
it('calls createCentralCommsMetaId for each content item when useCCS is not false', async () => {
|
|
375
372
|
const onSave = jest.fn();
|
|
376
373
|
renderWithFlow({
|
|
377
374
|
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.
|
|
382
375
|
initialData: {
|
|
383
|
-
contentItems: [{ channel: 'sms', templateData: {
|
|
376
|
+
contentItems: [{ channel: 'sms', templateData: { smsBody: 'Hello' } }],
|
|
384
377
|
},
|
|
385
378
|
onSave,
|
|
386
379
|
});
|
|
387
380
|
|
|
388
381
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
389
382
|
|
|
390
|
-
await waitFor(() => expect(
|
|
391
|
-
expect(
|
|
383
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalledTimes(1));
|
|
384
|
+
expect(createCentralCommsMetaId).toHaveBeenCalledWith(
|
|
392
385
|
expect.objectContaining({
|
|
393
|
-
|
|
394
|
-
strategyType: 'SINGLE',
|
|
395
|
-
singleChannelStrategy: expect.objectContaining({
|
|
396
|
-
variant: expect.objectContaining({
|
|
397
|
-
channel: 'SMS',
|
|
398
|
-
smsMessageContent: { channel: 'SMS', message: 'Hello' },
|
|
399
|
-
}),
|
|
400
|
-
}),
|
|
386
|
+
centralCommsPayload: expect.objectContaining({ channel: 'SMS', module: 'CAMPAIGNS' }),
|
|
401
387
|
}),
|
|
402
388
|
);
|
|
403
389
|
expect(onSave).toHaveBeenCalledTimes(1);
|
|
404
390
|
});
|
|
405
391
|
|
|
406
|
-
it('
|
|
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();
|
|
392
|
+
it('calls getCentralCommsMetaIds when metaIds are returned from createCentralCommsMetaId', async () => {
|
|
540
393
|
renderWithFlow({
|
|
541
394
|
features: {},
|
|
542
|
-
|
|
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,
|
|
395
|
+
initialData: { contentItems: [{ channel: 'EMAIL', templateData: {} }] },
|
|
555
396
|
});
|
|
556
397
|
|
|
557
398
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
558
399
|
|
|
559
|
-
await waitFor(() => expect(
|
|
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
|
-
);
|
|
400
|
+
await waitFor(() => expect(getCentralCommsMetaIds).toHaveBeenCalledWith('meta-123'));
|
|
573
401
|
});
|
|
574
402
|
|
|
575
|
-
it('
|
|
576
|
-
|
|
403
|
+
it('skips getCentralCommsMetaIds when response contains no id', async () => {
|
|
404
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: {} } });
|
|
577
405
|
renderWithFlow({
|
|
578
406
|
features: {},
|
|
579
|
-
config: ccsConfig,
|
|
580
407
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
581
|
-
onSave,
|
|
582
408
|
});
|
|
583
409
|
|
|
584
410
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
585
411
|
|
|
586
|
-
await waitFor(() => expect(
|
|
587
|
-
expect(
|
|
588
|
-
expect.objectContaining({
|
|
589
|
-
ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 1, status: 'DRAFT' },
|
|
590
|
-
}),
|
|
591
|
-
);
|
|
412
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
413
|
+
expect(getCentralCommsMetaIds).not.toHaveBeenCalled();
|
|
592
414
|
});
|
|
593
415
|
|
|
594
|
-
it('
|
|
595
|
-
const onSave = jest.fn();
|
|
596
|
-
createCommDefinition.mockResolvedValueOnce({
|
|
597
|
-
response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 0 } } },
|
|
598
|
-
});
|
|
416
|
+
it('uses ouId and module from config.context when provided', async () => {
|
|
599
417
|
renderWithFlow({
|
|
600
418
|
features: {},
|
|
601
|
-
config:
|
|
419
|
+
config: { ...baseConfig, context: { ouId: 42, module: 'LOYALTY' }, features: {} },
|
|
602
420
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
603
|
-
onSave,
|
|
604
421
|
});
|
|
605
422
|
|
|
606
423
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
607
424
|
|
|
608
|
-
await waitFor(() => expect(
|
|
609
|
-
expect(
|
|
425
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
426
|
+
expect(createCentralCommsMetaId).toHaveBeenCalledWith(
|
|
610
427
|
expect.objectContaining({
|
|
611
|
-
|
|
428
|
+
centralCommsPayload: expect.objectContaining({ ouId: 42, module: 'LOYALTY' }),
|
|
612
429
|
}),
|
|
613
430
|
);
|
|
614
431
|
});
|
|
615
432
|
|
|
616
|
-
it('sends an empty referenceId/description when config.context does not supply them', async () => {
|
|
617
|
-
renderWithFlow({
|
|
618
|
-
features: {},
|
|
619
|
-
config: ccsConfig,
|
|
620
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
624
|
-
|
|
625
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
626
|
-
const payload = createCommDefinition.mock.calls[0][0];
|
|
627
|
-
expect(payload.referenceId).toBe('');
|
|
628
|
-
expect(payload.description).toBe('');
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it('uses config.context.referenceId and description when provided', async () => {
|
|
632
|
-
renderWithFlow({
|
|
633
|
-
features: {},
|
|
634
|
-
config: { ...ccsConfig, context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc' } },
|
|
635
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
639
|
-
|
|
640
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
641
|
-
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
642
|
-
expect.objectContaining({ referenceId: 'ORDER_PLACED', description: 'desc' }),
|
|
643
|
-
);
|
|
644
|
-
});
|
|
645
|
-
|
|
646
433
|
it('skips CCS entirely when useCCS is false', async () => {
|
|
647
434
|
const onSave = jest.fn();
|
|
648
435
|
renderWithFlow({
|
|
649
436
|
features: {},
|
|
650
|
-
config: { ...
|
|
437
|
+
config: { ...baseConfig, useCCS: false, features: {} },
|
|
651
438
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
652
439
|
onSave,
|
|
653
440
|
});
|
|
@@ -655,50 +442,14 @@ describe('handleSave — CCS flow', () => {
|
|
|
655
442
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
656
443
|
|
|
657
444
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
658
|
-
expect(
|
|
659
|
-
expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
it('skips createCommDefinition when config.context.name is absent (mandatory)', async () => {
|
|
663
|
-
const onSave = jest.fn();
|
|
664
|
-
renderWithFlow({
|
|
665
|
-
features: {},
|
|
666
|
-
config: { ...baseConfig, features: {} },
|
|
667
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
668
|
-
onSave,
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
672
|
-
|
|
673
|
-
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
674
|
-
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
it('skips createCommDefinition for multi-channel strategies (CHANNEL_PRIORITY/AB_TEST)', async () => {
|
|
678
|
-
const onSave = jest.fn();
|
|
679
|
-
renderWithFlow({
|
|
680
|
-
features: {},
|
|
681
|
-
config: ccsConfig,
|
|
682
|
-
initialData: {
|
|
683
|
-
communicationStrategy: CHANNEL_PRIORITY,
|
|
684
|
-
channels: ['SMS', 'EMAIL'],
|
|
685
|
-
contentItems: [{ channel: 'SMS', templateData: {} }, { channel: 'EMAIL', templateData: {} }],
|
|
686
|
-
},
|
|
687
|
-
onSave,
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
691
|
-
|
|
692
|
-
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
693
|
-
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
445
|
+
expect(createCentralCommsMetaId).not.toHaveBeenCalled();
|
|
694
446
|
});
|
|
695
447
|
|
|
696
|
-
it('still calls onSave
|
|
697
|
-
|
|
448
|
+
it('still calls onSave when createCentralCommsMetaId rejects', async () => {
|
|
449
|
+
createCentralCommsMetaId.mockRejectedValue(new Error('Network error'));
|
|
698
450
|
const onSave = jest.fn();
|
|
699
451
|
renderWithFlow({
|
|
700
452
|
features: {},
|
|
701
|
-
config: ccsConfig,
|
|
702
453
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
703
454
|
onSave,
|
|
704
455
|
});
|
|
@@ -706,35 +457,12 @@ describe('handleSave — CCS flow', () => {
|
|
|
706
457
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
707
458
|
|
|
708
459
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
709
|
-
expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
it('blocks save and shows an error when createCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
|
|
713
|
-
createCommDefinition.mockResolvedValue({
|
|
714
|
-
success: false,
|
|
715
|
-
status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
|
|
716
|
-
message: 'REFERENCE_ID_EXISTS',
|
|
717
|
-
});
|
|
718
|
-
const onSave = jest.fn();
|
|
719
|
-
renderWithFlow({
|
|
720
|
-
features: {},
|
|
721
|
-
config: ccsConfig,
|
|
722
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
723
|
-
onSave,
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
727
|
-
|
|
728
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
729
|
-
expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
|
|
730
|
-
expect(onSave).not.toHaveBeenCalled();
|
|
731
460
|
});
|
|
732
461
|
|
|
733
|
-
it('skips
|
|
462
|
+
it('skips createCentralCommsMetaId when contentItems is empty', async () => {
|
|
734
463
|
const onSave = jest.fn();
|
|
735
464
|
renderWithFlow({
|
|
736
465
|
features: {},
|
|
737
|
-
config: ccsConfig,
|
|
738
466
|
initialData: { contentItems: [] },
|
|
739
467
|
onSave,
|
|
740
468
|
});
|
|
@@ -742,13 +470,12 @@ describe('handleSave — CCS flow', () => {
|
|
|
742
470
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
743
471
|
|
|
744
472
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
745
|
-
expect(
|
|
473
|
+
expect(createCentralCommsMetaId).not.toHaveBeenCalled();
|
|
746
474
|
});
|
|
747
475
|
|
|
748
|
-
it('
|
|
476
|
+
it('includes additionalSettings derived from dynamicControls in the payload', async () => {
|
|
749
477
|
renderWithFlow({
|
|
750
478
|
features: {},
|
|
751
|
-
config: ccsConfig,
|
|
752
479
|
initialData: {
|
|
753
480
|
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
754
481
|
dynamicControls: {
|
|
@@ -762,118 +489,14 @@ describe('handleSave — CCS flow', () => {
|
|
|
762
489
|
|
|
763
490
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
764
491
|
|
|
765
|
-
await waitFor(() => expect(
|
|
766
|
-
const payload =
|
|
767
|
-
expect(payload.
|
|
492
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
493
|
+
const payload = createCentralCommsMetaId.mock.calls[0][0];
|
|
494
|
+
expect(payload.centralCommsPayload.smsDeliverySettings.additionalSettings).toEqual({
|
|
768
495
|
useTinyUrl: true,
|
|
769
496
|
encryptUrl: true,
|
|
770
497
|
linkTrackingEnabled: true,
|
|
771
498
|
userSubscriptionDisabled: true,
|
|
772
499
|
});
|
|
773
|
-
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.additionalSettings).toBeUndefined();
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
it('includes channelSettings from deliverySetting.channelSetting in the payload', async () => {
|
|
777
|
-
renderWithFlow({
|
|
778
|
-
features: {},
|
|
779
|
-
config: ccsConfig,
|
|
780
|
-
initialData: {
|
|
781
|
-
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
782
|
-
deliverySetting: { channelSetting: { SMS: { domainId: 1001, gsmSenderId: 'BRAND1' } } },
|
|
783
|
-
},
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
787
|
-
|
|
788
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
789
|
-
const payload = createCommDefinition.mock.calls[0][0];
|
|
790
|
-
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.channelSettings).toEqual({
|
|
791
|
-
channel: 'SMS',
|
|
792
|
-
domainId: 1001,
|
|
793
|
-
gsmSenderId: 'BRAND1',
|
|
794
|
-
});
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
it('does not call editCommDefinition when config.context.existingCommDefinitionId is absent', async () => {
|
|
798
|
-
const onSave = jest.fn();
|
|
799
|
-
renderWithFlow({
|
|
800
|
-
features: {},
|
|
801
|
-
config: ccsConfig,
|
|
802
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
803
|
-
onSave,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
807
|
-
|
|
808
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
809
|
-
expect(editCommDefinition).not.toHaveBeenCalled();
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
it('calls editCommDefinition (not createCommDefinition) and merges the existing id into ccsCommDefinition when config.context.existingCommDefinitionId is set', async () => {
|
|
813
|
-
editCommDefinition.mockResolvedValue({
|
|
814
|
-
response: { data: { version: { version: 2 }, status: 'DRAFT' } },
|
|
815
|
-
});
|
|
816
|
-
const onSave = jest.fn();
|
|
817
|
-
renderWithFlow({
|
|
818
|
-
features: {},
|
|
819
|
-
config: {
|
|
820
|
-
...ccsConfig,
|
|
821
|
-
context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc', existingCommDefinitionId: 'cd_existing_1' },
|
|
822
|
-
},
|
|
823
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
824
|
-
onSave,
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
828
|
-
|
|
829
|
-
await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
|
|
830
|
-
expect(editCommDefinition).toHaveBeenCalledWith(
|
|
831
|
-
'cd_existing_1',
|
|
832
|
-
expect.objectContaining({
|
|
833
|
-
referenceId: 'ORDER_PLACED',
|
|
834
|
-
description: 'desc',
|
|
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();
|
|
877
500
|
});
|
|
878
501
|
});
|
|
879
502
|
|
|
@@ -929,34 +552,39 @@ describe('optional chaining safety', () => {
|
|
|
929
552
|
jest.clearAllMocks();
|
|
930
553
|
});
|
|
931
554
|
|
|
932
|
-
it('
|
|
933
|
-
|
|
555
|
+
it('defaults ouId to -1 when config.context is absent', async () => {
|
|
556
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
|
|
934
557
|
renderWithFlow({
|
|
935
558
|
features: {},
|
|
936
|
-
config: { ...baseConfig, features: {} },
|
|
937
559
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
938
|
-
onSave,
|
|
939
560
|
});
|
|
940
561
|
|
|
941
562
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
942
563
|
|
|
943
|
-
await waitFor(() => expect(
|
|
944
|
-
expect(
|
|
564
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
565
|
+
expect(createCentralCommsMetaId).toHaveBeenCalledWith(
|
|
566
|
+
expect.objectContaining({
|
|
567
|
+
centralCommsPayload: expect.objectContaining({ ouId: -1 }),
|
|
568
|
+
}),
|
|
569
|
+
);
|
|
945
570
|
});
|
|
946
571
|
|
|
947
|
-
it('
|
|
948
|
-
|
|
572
|
+
it('defaults module to consumer.toUpperCase() when config.context.module absent', async () => {
|
|
573
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
|
|
949
574
|
renderWithFlow({
|
|
950
575
|
features: {},
|
|
951
|
-
config: { ...baseConfig,
|
|
576
|
+
config: { ...baseConfig, consumer: 'loyalty', features: {} },
|
|
952
577
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
953
|
-
onSave,
|
|
954
578
|
});
|
|
955
579
|
|
|
956
580
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
957
581
|
|
|
958
|
-
await waitFor(() => expect(
|
|
959
|
-
expect(
|
|
582
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
583
|
+
expect(createCentralCommsMetaId).toHaveBeenCalledWith(
|
|
584
|
+
expect.objectContaining({
|
|
585
|
+
centralCommsPayload: expect.objectContaining({ module: 'LOYALTY' }),
|
|
586
|
+
}),
|
|
587
|
+
);
|
|
960
588
|
});
|
|
961
589
|
|
|
962
590
|
it('initializes channel from config.channel when initialData.channel is absent', () => {
|
|
@@ -1000,51 +628,3 @@ describe('optional chaining safety', () => {
|
|
|
1000
628
|
expect(screen.getByRole('button', { name: /^save$/i })).toBeDisabled();
|
|
1001
629
|
});
|
|
1002
630
|
});
|
|
1003
|
-
|
|
1004
|
-
describe('step dividers', () => {
|
|
1005
|
-
it('renders a divider after CommunicationStrategyStep and after ChannelSelectionStep when both render together', () => {
|
|
1006
|
-
const { container } = renderWithFlow({
|
|
1007
|
-
features: {
|
|
1008
|
-
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS },
|
|
1009
|
-
contentTemplateData: { required: true, channels: CHANNELS },
|
|
1010
|
-
},
|
|
1011
|
-
initialData: {
|
|
1012
|
-
communicationStrategy: SINGLE_TEMPLATE,
|
|
1013
|
-
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
1014
|
-
},
|
|
1015
|
-
});
|
|
1016
|
-
// Each rendered step (CommunicationStrategyStep, ChannelSelectionStep) emits its
|
|
1017
|
-
// own trailing divider.
|
|
1018
|
-
expect(container.querySelectorAll('.ant-divider')).toHaveLength(2);
|
|
1019
|
-
});
|
|
1020
|
-
|
|
1021
|
-
it('omits the divider after ChannelSelectionStep when no content has been added yet', () => {
|
|
1022
|
-
const { container } = renderWithFlow({
|
|
1023
|
-
features: {
|
|
1024
|
-
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS },
|
|
1025
|
-
contentTemplateData: { required: true, channels: CHANNELS },
|
|
1026
|
-
},
|
|
1027
|
-
initialData: {
|
|
1028
|
-
communicationStrategy: SINGLE_TEMPLATE,
|
|
1029
|
-
contentItems: [],
|
|
1030
|
-
},
|
|
1031
|
-
});
|
|
1032
|
-
// Neither divider renders — both CommunicationStrategyStep's and ChannelSelectionStep's
|
|
1033
|
-
// own trailing dividers are conditional on content existing, to avoid a dangling line
|
|
1034
|
-
// around an empty "Add creative" state.
|
|
1035
|
-
expect(container.querySelectorAll('.ant-divider')).toHaveLength(0);
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
it('renders the divider after CommunicationStrategyStep unconditionally when no CHANNEL_SELECTION step follows', () => {
|
|
1039
|
-
const { container } = renderWithFlow({
|
|
1040
|
-
features: {
|
|
1041
|
-
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS },
|
|
1042
|
-
// contentTemplateData.required omitted — no CHANNEL_SELECTION step in this flow.
|
|
1043
|
-
},
|
|
1044
|
-
initialData: {
|
|
1045
|
-
communicationStrategy: SINGLE_TEMPLATE,
|
|
1046
|
-
},
|
|
1047
|
-
});
|
|
1048
|
-
expect(container.querySelectorAll('.ant-divider')).toHaveLength(1);
|
|
1049
|
-
});
|
|
1050
|
-
});
|