@capillarytech/creatives-library 9.0.56-alpha.7 → 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 -16
- package/utils/tests/templateVarUtils.test.js +0 -44
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +0 -13
- package/v2Components/CommonTestAndPreview/actions.js +0 -13
- package/v2Components/CommonTestAndPreview/constants.js +0 -8
- package/v2Components/CommonTestAndPreview/index.js +3 -55
- 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 -140
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +2 -18
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +27 -58
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +51 -503
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +4 -40
- package/v2Containers/CommunicationFlow/constants.js +4 -65
- package/v2Containers/CommunicationFlow/index.js +23 -16
- package/v2Containers/CommunicationFlow/messages.js +3 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +11 -24
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +0 -4
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +4 -176
- 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 -6
- 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();
|
|
392
|
+
it('calls getCentralCommsMetaIds when metaIds are returned from createCentralCommsMetaId', async () => {
|
|
408
393
|
renderWithFlow({
|
|
409
394
|
features: {},
|
|
410
|
-
|
|
411
|
-
initialData: {
|
|
412
|
-
contentItems: [{
|
|
413
|
-
channel: 'SMS',
|
|
414
|
-
templateData: { messageBody: 'Hello {{name}}', channel: 'SMS' },
|
|
415
|
-
}],
|
|
416
|
-
},
|
|
417
|
-
onSave,
|
|
395
|
+
initialData: { contentItems: [{ channel: 'EMAIL', templateData: {} }] },
|
|
418
396
|
});
|
|
419
397
|
|
|
420
398
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
421
399
|
|
|
422
|
-
await waitFor(() => expect(
|
|
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
|
-
);
|
|
400
|
+
await waitFor(() => expect(getCentralCommsMetaIds).toHaveBeenCalledWith('meta-123'));
|
|
436
401
|
});
|
|
437
402
|
|
|
438
|
-
it('
|
|
439
|
-
|
|
403
|
+
it('skips getCentralCommsMetaIds when response contains no id', async () => {
|
|
404
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: {} } });
|
|
440
405
|
renderWithFlow({
|
|
441
406
|
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();
|
|
540
|
-
renderWithFlow({
|
|
541
|
-
features: {},
|
|
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,
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
558
|
-
|
|
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
|
-
);
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it('merges ccsCommDefinition into the data passed to onSave when create succeeds', async () => {
|
|
576
|
-
const onSave = jest.fn();
|
|
577
|
-
renderWithFlow({
|
|
578
|
-
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,15 +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() }));
|
|
445
|
+
expect(createCentralCommsMetaId).not.toHaveBeenCalled();
|
|
660
446
|
});
|
|
661
447
|
|
|
662
|
-
it('
|
|
448
|
+
it('still calls onSave when createCentralCommsMetaId rejects', async () => {
|
|
449
|
+
createCentralCommsMetaId.mockRejectedValue(new Error('Network error'));
|
|
663
450
|
const onSave = jest.fn();
|
|
664
451
|
renderWithFlow({
|
|
665
452
|
features: {},
|
|
666
|
-
config: { ...baseConfig, features: {} },
|
|
667
453
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
668
454
|
onSave,
|
|
669
455
|
});
|
|
@@ -671,102 +457,12 @@ describe('handleSave — CCS flow', () => {
|
|
|
671
457
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
672
458
|
|
|
673
459
|
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();
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
it('blocks save and shows a generic error when createCommDefinition rejects (regression: this used to silently call onSave without ccsCommDefinition and no user-facing feedback — Send for approval would then just be disabled with zero explanation why)', async () => {
|
|
697
|
-
createCommDefinition.mockRejectedValue(new Error('Network error'));
|
|
698
|
-
const onSave = jest.fn();
|
|
699
|
-
renderWithFlow({
|
|
700
|
-
features: {},
|
|
701
|
-
config: ccsConfig,
|
|
702
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
703
|
-
onSave,
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
707
|
-
|
|
708
|
-
await waitFor(() => expect(screen.getByText('Something went wrong while saving your content. Please try again.')).toBeInTheDocument());
|
|
709
|
-
expect(onSave).not.toHaveBeenCalled();
|
|
710
460
|
});
|
|
711
461
|
|
|
712
|
-
it('
|
|
713
|
-
createCommDefinition.mockResolvedValue({ isError: true, status: 401 });
|
|
462
|
+
it('skips createCentralCommsMetaId when contentItems is empty', async () => {
|
|
714
463
|
const onSave = jest.fn();
|
|
715
464
|
renderWithFlow({
|
|
716
465
|
features: {},
|
|
717
|
-
config: ccsConfig,
|
|
718
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
719
|
-
onSave,
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
723
|
-
|
|
724
|
-
await waitFor(() => expect(screen.getByText('Something went wrong while saving your content. Please try again.')).toBeInTheDocument());
|
|
725
|
-
expect(onSave).not.toHaveBeenCalled();
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
it('blocks save and shows a generic error when createCommDefinition resolves with no usable data (e.g. missing id)', async () => {
|
|
729
|
-
createCommDefinition.mockResolvedValue({ response: { data: {} } });
|
|
730
|
-
const onSave = jest.fn();
|
|
731
|
-
renderWithFlow({
|
|
732
|
-
features: {},
|
|
733
|
-
config: ccsConfig,
|
|
734
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
735
|
-
onSave,
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
739
|
-
|
|
740
|
-
await waitFor(() => expect(screen.getByText('Something went wrong while saving your content. Please try again.')).toBeInTheDocument());
|
|
741
|
-
expect(onSave).not.toHaveBeenCalled();
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
it('blocks save and shows an error when createCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
|
|
745
|
-
createCommDefinition.mockResolvedValue({
|
|
746
|
-
success: false,
|
|
747
|
-
status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
|
|
748
|
-
message: 'REFERENCE_ID_EXISTS',
|
|
749
|
-
});
|
|
750
|
-
const onSave = jest.fn();
|
|
751
|
-
renderWithFlow({
|
|
752
|
-
features: {},
|
|
753
|
-
config: ccsConfig,
|
|
754
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
755
|
-
onSave,
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
759
|
-
|
|
760
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
761
|
-
expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
|
|
762
|
-
expect(onSave).not.toHaveBeenCalled();
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
it('skips createCommDefinition when contentItems is empty', async () => {
|
|
766
|
-
const onSave = jest.fn();
|
|
767
|
-
renderWithFlow({
|
|
768
|
-
features: {},
|
|
769
|
-
config: ccsConfig,
|
|
770
466
|
initialData: { contentItems: [] },
|
|
771
467
|
onSave,
|
|
772
468
|
});
|
|
@@ -774,13 +470,12 @@ describe('handleSave — CCS flow', () => {
|
|
|
774
470
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
775
471
|
|
|
776
472
|
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
777
|
-
expect(
|
|
473
|
+
expect(createCentralCommsMetaId).not.toHaveBeenCalled();
|
|
778
474
|
});
|
|
779
475
|
|
|
780
|
-
it('
|
|
476
|
+
it('includes additionalSettings derived from dynamicControls in the payload', async () => {
|
|
781
477
|
renderWithFlow({
|
|
782
478
|
features: {},
|
|
783
|
-
config: ccsConfig,
|
|
784
479
|
initialData: {
|
|
785
480
|
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
786
481
|
dynamicControls: {
|
|
@@ -794,118 +489,14 @@ describe('handleSave — CCS flow', () => {
|
|
|
794
489
|
|
|
795
490
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
796
491
|
|
|
797
|
-
await waitFor(() => expect(
|
|
798
|
-
const payload =
|
|
799
|
-
expect(payload.
|
|
492
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
493
|
+
const payload = createCentralCommsMetaId.mock.calls[0][0];
|
|
494
|
+
expect(payload.centralCommsPayload.smsDeliverySettings.additionalSettings).toEqual({
|
|
800
495
|
useTinyUrl: true,
|
|
801
496
|
encryptUrl: true,
|
|
802
497
|
linkTrackingEnabled: true,
|
|
803
498
|
userSubscriptionDisabled: true,
|
|
804
499
|
});
|
|
805
|
-
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.additionalSettings).toBeUndefined();
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
it('includes channelSettings from deliverySetting.channelSetting in the payload', async () => {
|
|
809
|
-
renderWithFlow({
|
|
810
|
-
features: {},
|
|
811
|
-
config: ccsConfig,
|
|
812
|
-
initialData: {
|
|
813
|
-
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
814
|
-
deliverySetting: { channelSetting: { SMS: { domainId: 1001, gsmSenderId: 'BRAND1' } } },
|
|
815
|
-
},
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
819
|
-
|
|
820
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
|
|
821
|
-
const payload = createCommDefinition.mock.calls[0][0];
|
|
822
|
-
expect(payload.singleChannelStrategy.variant.smsDeliverySettings.channelSettings).toEqual({
|
|
823
|
-
channel: 'SMS',
|
|
824
|
-
domainId: 1001,
|
|
825
|
-
gsmSenderId: 'BRAND1',
|
|
826
|
-
});
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
it('does not call editCommDefinition when config.context.existingCommDefinitionId is absent', async () => {
|
|
830
|
-
const onSave = jest.fn();
|
|
831
|
-
renderWithFlow({
|
|
832
|
-
features: {},
|
|
833
|
-
config: ccsConfig,
|
|
834
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
835
|
-
onSave,
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
839
|
-
|
|
840
|
-
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
841
|
-
expect(editCommDefinition).not.toHaveBeenCalled();
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
it('calls editCommDefinition (not createCommDefinition) and merges the existing id into ccsCommDefinition when config.context.existingCommDefinitionId is set', async () => {
|
|
845
|
-
editCommDefinition.mockResolvedValue({
|
|
846
|
-
response: { data: { version: { version: 2 }, status: 'DRAFT' } },
|
|
847
|
-
});
|
|
848
|
-
const onSave = jest.fn();
|
|
849
|
-
renderWithFlow({
|
|
850
|
-
features: {},
|
|
851
|
-
config: {
|
|
852
|
-
...ccsConfig,
|
|
853
|
-
context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc', existingCommDefinitionId: 'cd_existing_1' },
|
|
854
|
-
},
|
|
855
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
856
|
-
onSave,
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
860
|
-
|
|
861
|
-
await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
|
|
862
|
-
expect(editCommDefinition).toHaveBeenCalledWith(
|
|
863
|
-
'cd_existing_1',
|
|
864
|
-
expect.objectContaining({
|
|
865
|
-
referenceId: 'ORDER_PLACED',
|
|
866
|
-
description: 'desc',
|
|
867
|
-
strategyType: 'SINGLE',
|
|
868
|
-
settings: expect.anything(),
|
|
869
|
-
singleChannelStrategy: expect.anything(),
|
|
870
|
-
}),
|
|
871
|
-
);
|
|
872
|
-
expect(createCommDefinition).not.toHaveBeenCalled();
|
|
873
|
-
|
|
874
|
-
await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
|
|
875
|
-
expect(onSave).toHaveBeenCalledWith(
|
|
876
|
-
expect.objectContaining({
|
|
877
|
-
ccsCommDefinition: {
|
|
878
|
-
id: 'cd_existing_1',
|
|
879
|
-
referenceId: 'ORDER_PLACED',
|
|
880
|
-
version: 2,
|
|
881
|
-
status: 'DRAFT',
|
|
882
|
-
},
|
|
883
|
-
}),
|
|
884
|
-
);
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
it('blocks save and shows an error when editCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
|
|
888
|
-
editCommDefinition.mockResolvedValue({
|
|
889
|
-
success: false,
|
|
890
|
-
status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
|
|
891
|
-
message: 'REFERENCE_ID_EXISTS',
|
|
892
|
-
});
|
|
893
|
-
const onSave = jest.fn();
|
|
894
|
-
renderWithFlow({
|
|
895
|
-
features: {},
|
|
896
|
-
config: {
|
|
897
|
-
...ccsConfig,
|
|
898
|
-
context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', existingCommDefinitionId: 'cd_existing_1' },
|
|
899
|
-
},
|
|
900
|
-
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
901
|
-
onSave,
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
905
|
-
|
|
906
|
-
await waitFor(() => expect(editCommDefinition).toHaveBeenCalledTimes(1));
|
|
907
|
-
expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
|
|
908
|
-
expect(onSave).not.toHaveBeenCalled();
|
|
909
500
|
});
|
|
910
501
|
});
|
|
911
502
|
|
|
@@ -961,34 +552,39 @@ describe('optional chaining safety', () => {
|
|
|
961
552
|
jest.clearAllMocks();
|
|
962
553
|
});
|
|
963
554
|
|
|
964
|
-
it('
|
|
965
|
-
|
|
555
|
+
it('defaults ouId to -1 when config.context is absent', async () => {
|
|
556
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
|
|
966
557
|
renderWithFlow({
|
|
967
558
|
features: {},
|
|
968
|
-
config: { ...baseConfig, features: {} },
|
|
969
559
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
970
|
-
onSave,
|
|
971
560
|
});
|
|
972
561
|
|
|
973
562
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
974
563
|
|
|
975
|
-
await waitFor(() => expect(
|
|
976
|
-
expect(
|
|
564
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
565
|
+
expect(createCentralCommsMetaId).toHaveBeenCalledWith(
|
|
566
|
+
expect.objectContaining({
|
|
567
|
+
centralCommsPayload: expect.objectContaining({ ouId: -1 }),
|
|
568
|
+
}),
|
|
569
|
+
);
|
|
977
570
|
});
|
|
978
571
|
|
|
979
|
-
it('
|
|
980
|
-
|
|
572
|
+
it('defaults module to consumer.toUpperCase() when config.context.module absent', async () => {
|
|
573
|
+
createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
|
|
981
574
|
renderWithFlow({
|
|
982
575
|
features: {},
|
|
983
|
-
config: { ...baseConfig,
|
|
576
|
+
config: { ...baseConfig, consumer: 'loyalty', features: {} },
|
|
984
577
|
initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
|
|
985
|
-
onSave,
|
|
986
578
|
});
|
|
987
579
|
|
|
988
580
|
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
989
581
|
|
|
990
|
-
await waitFor(() => expect(
|
|
991
|
-
expect(
|
|
582
|
+
await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
|
|
583
|
+
expect(createCentralCommsMetaId).toHaveBeenCalledWith(
|
|
584
|
+
expect.objectContaining({
|
|
585
|
+
centralCommsPayload: expect.objectContaining({ module: 'LOYALTY' }),
|
|
586
|
+
}),
|
|
587
|
+
);
|
|
992
588
|
});
|
|
993
589
|
|
|
994
590
|
it('initializes channel from config.channel when initialData.channel is absent', () => {
|
|
@@ -1032,51 +628,3 @@ describe('optional chaining safety', () => {
|
|
|
1032
628
|
expect(screen.getByRole('button', { name: /^save$/i })).toBeDisabled();
|
|
1033
629
|
});
|
|
1034
630
|
});
|
|
1035
|
-
|
|
1036
|
-
describe('step dividers', () => {
|
|
1037
|
-
it('renders a divider after CommunicationStrategyStep and after ChannelSelectionStep when both render together', () => {
|
|
1038
|
-
const { container } = renderWithFlow({
|
|
1039
|
-
features: {
|
|
1040
|
-
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS },
|
|
1041
|
-
contentTemplateData: { required: true, channels: CHANNELS },
|
|
1042
|
-
},
|
|
1043
|
-
initialData: {
|
|
1044
|
-
communicationStrategy: SINGLE_TEMPLATE,
|
|
1045
|
-
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
1046
|
-
},
|
|
1047
|
-
});
|
|
1048
|
-
// Each rendered step (CommunicationStrategyStep, ChannelSelectionStep) emits its
|
|
1049
|
-
// own trailing divider.
|
|
1050
|
-
expect(container.querySelectorAll('.ant-divider')).toHaveLength(2);
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
it('omits the divider after ChannelSelectionStep when no content has been added yet', () => {
|
|
1054
|
-
const { container } = renderWithFlow({
|
|
1055
|
-
features: {
|
|
1056
|
-
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS },
|
|
1057
|
-
contentTemplateData: { required: true, channels: CHANNELS },
|
|
1058
|
-
},
|
|
1059
|
-
initialData: {
|
|
1060
|
-
communicationStrategy: SINGLE_TEMPLATE,
|
|
1061
|
-
contentItems: [],
|
|
1062
|
-
},
|
|
1063
|
-
});
|
|
1064
|
-
// Neither divider renders — both CommunicationStrategyStep's and ChannelSelectionStep's
|
|
1065
|
-
// own trailing dividers are conditional on content existing, to avoid a dangling line
|
|
1066
|
-
// around an empty "Add creative" state.
|
|
1067
|
-
expect(container.querySelectorAll('.ant-divider')).toHaveLength(0);
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
it('renders the divider after CommunicationStrategyStep unconditionally when no CHANNEL_SELECTION step follows', () => {
|
|
1071
|
-
const { container } = renderWithFlow({
|
|
1072
|
-
features: {
|
|
1073
|
-
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS },
|
|
1074
|
-
// contentTemplateData.required omitted — no CHANNEL_SELECTION step in this flow.
|
|
1075
|
-
},
|
|
1076
|
-
initialData: {
|
|
1077
|
-
communicationStrategy: SINGLE_TEMPLATE,
|
|
1078
|
-
},
|
|
1079
|
-
});
|
|
1080
|
-
expect(container.querySelectorAll('.ant-divider')).toHaveLength(1);
|
|
1081
|
-
});
|
|
1082
|
-
});
|