@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.57-alpha.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.
Files changed (91) hide show
  1. package/constants/unified.js +7 -1
  2. package/package.json +1 -1
  3. package/services/api.js +25 -4
  4. package/utils/common.js +0 -4
  5. package/utils/templateVarUtils.js +16 -0
  6. package/utils/tests/templateVarUtils.test.js +44 -0
  7. package/v2Components/CapActionButton/index.js +73 -60
  8. package/v2Components/CapActionButton/index.scss +44 -28
  9. package/v2Components/CapActionButton/messages.js +7 -3
  10. package/v2Components/CapActionButton/tests/index.test.js +17 -1
  11. package/v2Components/CapWhatsappCTA/messages.js +4 -0
  12. package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
  13. package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
  14. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
  15. package/v2Components/CommonTestAndPreview/actions.js +13 -0
  16. package/v2Components/CommonTestAndPreview/constants.js +8 -0
  17. package/v2Components/CommonTestAndPreview/index.js +55 -3
  18. package/v2Components/CommonTestAndPreview/reducer.js +20 -0
  19. package/v2Components/CommonTestAndPreview/sagas.js +28 -0
  20. package/v2Components/CommonTestAndPreview/selectors.js +6 -0
  21. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
  22. package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
  23. package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
  24. package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
  25. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
  26. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
  27. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
  28. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +36 -22
  29. package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +5 -13
  30. package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +6 -4
  31. package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +13 -14
  32. package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +15 -8
  33. package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +31 -0
  34. package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +13 -0
  35. package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +26 -0
  36. package/v2Components/FormBuilder/_formBuilder.scss +8 -0
  37. package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
  38. package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
  39. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
  40. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
  41. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
  42. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
  43. package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
  44. package/v2Containers/CommunicationFlow/constants.js +65 -4
  45. package/v2Containers/CommunicationFlow/index.js +16 -23
  46. package/v2Containers/CommunicationFlow/messages.js +11 -3
  47. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
  48. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
  49. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
  50. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
  51. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
  52. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
  53. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
  54. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
  55. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
  56. package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
  57. package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
  58. package/v2Containers/CreativesContainer/index.js +29 -3
  59. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
  60. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
  61. package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
  62. package/v2Containers/MobilePush/Create/index.js +18 -1
  63. package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
  64. package/v2Containers/MobilePush/Edit/index.js +18 -1
  65. package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
  66. package/v2Containers/MobilePush/commonMethods.js +49 -1
  67. package/v2Containers/MobilePushNew/index.js +29 -4
  68. package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
  69. package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
  70. package/v2Containers/MobilePushNew/utils.js +34 -1
  71. package/v2Containers/MobilepushWrapper/index.js +3 -1
  72. package/v2Containers/Rcs/index.js +34 -0
  73. package/v2Containers/Rcs/index.scss +15 -0
  74. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
  75. package/v2Containers/Rcs/tests/index.test.js +67 -0
  76. package/v2Containers/Sms/Create/index.js +3 -1
  77. package/v2Containers/Sms/Edit/index.js +25 -0
  78. package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
  79. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
  80. package/v2Containers/Viber/index.js +24 -1
  81. package/v2Containers/Viber/tests/index.test.js +103 -0
  82. package/v2Containers/WebPush/Create/index.js +19 -0
  83. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
  84. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
  85. package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
  86. package/v2Containers/Whatsapp/index.js +25 -4
  87. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
  88. package/v2Containers/Whatsapp/tests/index.test.js +115 -0
  89. package/v2Containers/Zalo/index.js +28 -1
  90. package/v2Containers/Zalo/tests/index.test.js +99 -0
  91. package/v2Containers/mockdata.js +25 -0
@@ -1538,3 +1538,118 @@ describe('WhatsApp edit Done button whitespace validation', () => {
1538
1538
  expect(getDoneButton().props().disabled).toBe(true);
1539
1539
  });
1540
1540
  });
1541
+
1542
+ describe('Whatsapp onContentValidityChange reporting', () => {
1543
+ let renderedComponent;
1544
+
1545
+ const createWhatsappTemplate = jest.fn();
1546
+ const getFormData = jest.fn();
1547
+ const clearCreateResponse = jest.fn();
1548
+ const getTemplateDetails = jest.fn();
1549
+ const resetEditTemplate = jest.fn();
1550
+ const fetchSchemaForEntity = jest.fn();
1551
+ const handleClose = jest.fn();
1552
+ const onCreateComplete = jest.fn();
1553
+ const formatMessage = jest.fn();
1554
+ const getMetaTags = jest.fn();
1555
+ const resetMetaTags = jest.fn();
1556
+
1557
+ const mountCreateFlow = (onContentValidityChange) => {
1558
+ renderedComponent = mountWithIntl(
1559
+ <Provider store={store}>
1560
+ <Whatsapp
1561
+ actions={{
1562
+ createWhatsappTemplate,
1563
+ clearCreateResponse,
1564
+ getTemplateDetails,
1565
+ resetEditTemplate,
1566
+ getMetaTags,
1567
+ resetMetaTags,
1568
+ }}
1569
+ globalActions={{ fetchSchemaForEntity }}
1570
+ onCreateComplete={onCreateComplete}
1571
+ handleClose={handleClose}
1572
+ getFormData={getFormData}
1573
+ params={{}}
1574
+ editData={{}}
1575
+ templateData={{}}
1576
+ accountData={mockData.accountData1}
1577
+ location={mockData.location}
1578
+ intl={{ formatMessage }}
1579
+ isFullMode={true}
1580
+ loadingTags={false}
1581
+ metaEntities={[]}
1582
+ getDefaultTags={true}
1583
+ onContentValidityChange={onContentValidityChange}
1584
+ Templates={{
1585
+ senderDetails: {
1586
+ status: 'SUCCESS',
1587
+ domainProperties: [
1588
+ {
1589
+ domainProperties: {
1590
+ connectionProperties: {
1591
+ sourceAccountIdentifier: '2000222347',
1592
+ baseUrl: 'https://api.gupshup.io/whatsapp/v1',
1593
+ },
1594
+ },
1595
+ },
1596
+ ],
1597
+ },
1598
+ }}
1599
+ />
1600
+ </Provider>,
1601
+ );
1602
+ };
1603
+
1604
+ const changeTemplateMessage = (val) => {
1605
+ renderedComponent
1606
+ .find('textarea#whatsapp-create-template-message-input')
1607
+ .at(0)
1608
+ .simulate('change', { target: { value: val } });
1609
+ renderedComponent.update();
1610
+ };
1611
+
1612
+ it('reports isContentEmpty: true on mount when the message body is empty', () => {
1613
+ const onContentValidityChange = jest.fn();
1614
+ mountCreateFlow(onContentValidityChange);
1615
+
1616
+ expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
1617
+ });
1618
+
1619
+ it('reports isContentEmpty: false once the message body has content', () => {
1620
+ const onContentValidityChange = jest.fn();
1621
+ mountCreateFlow(onContentValidityChange);
1622
+ onContentValidityChange.mockClear();
1623
+
1624
+ changeTemplateMessage('Hi there');
1625
+
1626
+ expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: false });
1627
+ });
1628
+
1629
+ it('re-reports empty when the message body is cleared out', () => {
1630
+ const onContentValidityChange = jest.fn();
1631
+ mountCreateFlow(onContentValidityChange);
1632
+ changeTemplateMessage('Hi there');
1633
+ onContentValidityChange.mockClear();
1634
+
1635
+ changeTemplateMessage('');
1636
+
1637
+ expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
1638
+ });
1639
+
1640
+ it('does not throw and does not report when onContentValidityChange is not provided', () => {
1641
+ expect(() => mountCreateFlow(undefined)).not.toThrow();
1642
+ });
1643
+
1644
+ it('does not report again on a re-render that does not change validity (dedup guard)', () => {
1645
+ const onContentValidityChange = jest.fn();
1646
+ mountCreateFlow(onContentValidityChange);
1647
+ changeTemplateMessage('Hi there');
1648
+ onContentValidityChange.mockClear();
1649
+
1650
+ // Same non-empty value re-set — validity boolean is unchanged.
1651
+ changeTemplateMessage('Hi there again');
1652
+
1653
+ expect(onContentValidityChange).not.toHaveBeenCalled();
1654
+ });
1655
+ });
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable camelcase */
2
2
  /* eslint-disable react/jsx-indent */
3
- import React, { useState, useEffect } from 'react';
3
+ import React, { useState, useEffect, useRef } from 'react';
4
4
  import { bindActionCreators } from 'redux';
5
5
  import { createStructuredSelector } from 'reselect';
6
6
  import { FormattedMessage } from 'react-intl';
@@ -82,6 +82,7 @@ export const Zalo = (props) => {
82
82
  showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
83
83
  handleTestAndPreview: propsHandleTestAndPreview,
84
84
  handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
85
+ onContentValidityChange,
85
86
  } = props || {};
86
87
  // Since zalo supports now multiple accounts, we need to get the hostname from the selected account or passed props.
87
88
  const hostName = selectedZaloAccount?.hostName || zaloHostName;
@@ -368,6 +369,32 @@ export const Zalo = (props) => {
368
369
  });
369
370
  return disableCheck;
370
371
  };
372
+
373
+ // Content is considered empty when every variable field's value is blank
374
+ // (i.e. the Zalo message body carries no text and no injected tags).
375
+ const isZaloContentEmpty = (listParams) => {
376
+ if (!Array.isArray(listParams) || listParams.length === 0) {
377
+ return true;
378
+ }
379
+ return listParams.every((listParam) => !(listParam?.value || '').trim());
380
+ };
381
+
382
+ // Tracks the last validity value reported to the parent so this effect does not
383
+ // dispatch on every render. Intentionally undefined (not true) so the first render
384
+ // always reports the real validity rather than assuming the form starts invalid.
385
+ const lastReportedZaloContentEmptyRef = useRef(undefined);
386
+ useEffect(() => {
387
+ if (typeof onContentValidityChange !== 'function') {
388
+ return;
389
+ }
390
+ const isContentEmpty = isZaloContentEmpty(templateListParams);
391
+ if (lastReportedZaloContentEmptyRef.current === isContentEmpty) {
392
+ return;
393
+ }
394
+ lastReportedZaloContentEmptyRef.current = isContentEmpty;
395
+ onContentValidityChange({ isContentEmpty });
396
+ }, [templateListParams, onContentValidityChange]);
397
+
371
398
  const createPayload = () => {
372
399
  const varMap = {};
373
400
  templateListParams?.forEach((listParam) => {
@@ -150,4 +150,103 @@ describe('Test activity zalo container', () => {
150
150
  ),
151
151
  ).toBeInTheDocument();
152
152
  });
153
+
154
+ // Fully self-contained fixtures for the tests below: the "set template data" test above
155
+ // mutates the shared `editData`/`templateData` fixtures in place (handleSetValues mutates
156
+ // listParam objects directly, and that test also reassigns templateData.templateConfigs),
157
+ // so building independent literals here — rather than reusing or cloning the shared
158
+ // fixtures at test time (which would just clone the already-mutated state) — keeps these
159
+ // tests correct regardless of suite execution order.
160
+ const buildFreshEditData = () => ({
161
+ zaloTemplateInfoStatus: 'success',
162
+ zaloTemplateInfoValue: {
163
+ name: 'Fresh Template',
164
+ versions: {
165
+ base: {
166
+ content: {
167
+ zalo: {
168
+ templateId: '1',
169
+ templateName: 'Fresh Template',
170
+ listParams: [
171
+ {
172
+ name: 'danh_xung',
173
+ require: true,
174
+ type: 'STRING',
175
+ maxLength: 30,
176
+ minLength: 0,
177
+ acceptNull: false,
178
+ error: '',
179
+ value: '',
180
+ },
181
+ ],
182
+ previewUrl: 'https://account.zalo.cloud/preview',
183
+ status: 'ENABLE',
184
+ },
185
+ },
186
+ },
187
+ },
188
+ type: 'ZALO',
189
+ },
190
+ zaloTemplateInfoError: null,
191
+ });
192
+ const buildFreshTemplateData = () => ({
193
+ _id: 999001,
194
+ templateConfigs: { id: '', varMapped: {} },
195
+ });
196
+
197
+ it('reports content emptiness to the parent via onContentValidityChange', async () => {
198
+ const onContentValidityChange = jest.fn();
199
+ renderComponent({
200
+ actions: mockActions,
201
+ globalActions: mockGlobalActions,
202
+ editData: buildFreshEditData(),
203
+ metaEntities,
204
+ location,
205
+ getDefaultTags,
206
+ injectedTags,
207
+ accountData,
208
+ templateData: buildFreshTemplateData(),
209
+ isFullMode: true,
210
+ onContentValidityChange,
211
+ });
212
+ const inputBox = await screen.findAllByPlaceholderText(
213
+ 'Add labels or text or combination of both',
214
+ );
215
+ // Initial values in the mock data are blank, so the first report should be "empty".
216
+ expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
217
+ onContentValidityChange.mockClear();
218
+
219
+ fireEvent.change(inputBox[0], { target: { value: 'Hello, welcome' } });
220
+ expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: false });
221
+ });
222
+
223
+ it('does not re-report validity when it has not changed (dedup guard)', async () => {
224
+ const onContentValidityChange = jest.fn();
225
+ renderComponent({
226
+ actions: mockActions,
227
+ globalActions: mockGlobalActions,
228
+ editData: buildFreshEditData(),
229
+ metaEntities,
230
+ location,
231
+ getDefaultTags,
232
+ injectedTags,
233
+ accountData,
234
+ templateData: buildFreshTemplateData(),
235
+ isFullMode: true,
236
+ onContentValidityChange,
237
+ });
238
+ const inputBox = await screen.findAllByPlaceholderText(
239
+ 'Add labels or text or combination of both',
240
+ );
241
+ onContentValidityChange.mockClear();
242
+
243
+ fireEvent.change(inputBox[0], { target: { value: 'Hello' } });
244
+ expect(onContentValidityChange).toHaveBeenCalledTimes(1);
245
+ expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: false });
246
+ onContentValidityChange.mockClear();
247
+
248
+ // Changing to another non-empty value keeps isContentEmpty === false, so no new report.
249
+ fireEvent.change(inputBox[0], { target: { value: 'Hello again' } });
250
+ expect(onContentValidityChange).not.toHaveBeenCalled();
251
+ });
153
252
  });
@@ -1111,6 +1111,31 @@ export default {
1111
1111
  },
1112
1112
  accountName: "WhatsappAccount",
1113
1113
  },
1114
+ // CCS sends an explicit `cards: null` (not omitted) for a non-carousel WhatsApp
1115
+ // template — regression fixture for a crash reading `.map` off null.
1116
+ whatsappGetTemplateDataNullCards: {
1117
+ channel: "WHATSAPP",
1118
+ storeType: "REGISTERED_STORE",
1119
+ accountId: 12721,
1120
+ messagePartsCount: 1,
1121
+ messageBody: "Hey test, this is a plain WhatsApp template with no carousel.",
1122
+ templateConfigs: {
1123
+ name: "creatives_whatsapp6",
1124
+ language: "en",
1125
+ varMapped: {
1126
+ "{{1}}_1": "test",
1127
+ },
1128
+ template: "Hey {{1}}, this is a plain WhatsApp template with no carousel.",
1129
+ id: "creatives_whatsapp6",
1130
+ category: "MARKETING",
1131
+ buttonType: "NONE",
1132
+ buttons: null,
1133
+ mediaType: "TEXT",
1134
+ whatsappMedia: null,
1135
+ cards: null,
1136
+ },
1137
+ accountName: "WhatsappAccount",
1138
+ },
1114
1139
  whatsappGetCreativeData1: {
1115
1140
  value: {
1116
1141
  name: "creatives_whatsapp6",