@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.
- package/constants/unified.js +7 -1
- package/package.json +1 -1
- package/services/api.js +25 -4
- package/utils/common.js +0 -4
- package/utils/templateVarUtils.js +16 -0
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CapActionButton/index.js +73 -60
- package/v2Components/CapActionButton/index.scss +44 -28
- package/v2Components/CapActionButton/messages.js +7 -3
- package/v2Components/CapActionButton/tests/index.test.js +17 -1
- package/v2Components/CapWhatsappCTA/messages.js +4 -0
- package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
- package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
- package/v2Components/CommonTestAndPreview/actions.js +13 -0
- package/v2Components/CommonTestAndPreview/constants.js +8 -0
- package/v2Components/CommonTestAndPreview/index.js +55 -3
- package/v2Components/CommonTestAndPreview/reducer.js +20 -0
- package/v2Components/CommonTestAndPreview/sagas.js +28 -0
- package/v2Components/CommonTestAndPreview/selectors.js +6 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +36 -22
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +5 -13
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +6 -4
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +13 -14
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +15 -8
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +31 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +13 -0
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +26 -0
- package/v2Components/FormBuilder/_formBuilder.scss +8 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
- package/v2Containers/CommunicationFlow/constants.js +65 -4
- package/v2Containers/CommunicationFlow/index.js +16 -23
- package/v2Containers/CommunicationFlow/messages.js +11 -3
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
- package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
- package/v2Containers/CreativesContainer/index.js +29 -3
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
- package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
- package/v2Containers/MobilePush/Create/index.js +18 -1
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
- package/v2Containers/MobilePush/Edit/index.js +18 -1
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
- package/v2Containers/MobilePush/commonMethods.js +49 -1
- package/v2Containers/MobilePushNew/index.js +29 -4
- package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
- package/v2Containers/MobilePushNew/utils.js +34 -1
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +34 -0
- package/v2Containers/Rcs/index.scss +15 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
- package/v2Containers/Rcs/tests/index.test.js +67 -0
- package/v2Containers/Sms/Create/index.js +3 -1
- package/v2Containers/Sms/Edit/index.js +25 -0
- package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
- package/v2Containers/Viber/index.js +24 -1
- package/v2Containers/Viber/tests/index.test.js +103 -0
- package/v2Containers/WebPush/Create/index.js +19 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
- package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
- package/v2Containers/Whatsapp/index.js +25 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
- package/v2Containers/Whatsapp/tests/index.test.js +115 -0
- package/v2Containers/Zalo/index.js +28 -1
- package/v2Containers/Zalo/tests/index.test.js +99 -0
- 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
|
});
|
package/v2Containers/mockdata.js
CHANGED
|
@@ -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",
|