@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.56
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 +9 -4
- package/package.json +1 -1
- package/services/api.js +25 -4
- package/utils/common.js +0 -12
- 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 +46 -320
- package/v2Components/FormBuilder/Functional/channels/registry.js +0 -2
- package/v2Components/FormBuilder/Functional/constants.js +8 -43
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +28 -175
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +6 -75
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +20 -10
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +1 -9
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +3 -20
- package/v2Components/FormBuilder/Functional/layout/Section.js +1 -6
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +2 -9
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -17
- package/v2Components/FormBuilder/index.js +12 -34
- package/v2Components/FormBuilder/tests/entryGate.test.js +7 -94
- 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
- package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +0 -10
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +0 -82
- package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +0 -74
- package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +0 -28
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +0 -19
- package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +0 -45
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +0 -162
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +0 -37
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +0 -70
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +0 -274
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +0 -251
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +0 -89
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +0 -440
- package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +0 -430
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +0 -282
- package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +0 -141
- package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +0 -306
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +0 -151
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +0 -342
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +0 -110
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +0 -459
|
@@ -158,6 +158,7 @@ const renderHelper = (args) => {
|
|
|
158
158
|
{...(args.omitGetDefaultTags ? {} : { getDefaultTags: true })}
|
|
159
159
|
isDltEnabled={args.isDltEnabled || false}
|
|
160
160
|
smsRegister={'DLT'}
|
|
161
|
+
onContentValidityChange={args.onContentValidityChange}
|
|
161
162
|
/>
|
|
162
163
|
</Provider>,
|
|
163
164
|
);
|
|
@@ -1923,6 +1924,72 @@ describe('Character Counting Functions', () => {
|
|
|
1923
1924
|
});
|
|
1924
1925
|
});
|
|
1925
1926
|
|
|
1927
|
+
describe('Rcs — onContentValidityChange reporting', () => {
|
|
1928
|
+
// Full-mode create with a fresh (non-hydrating) rcsData: templateDesc starts out empty,
|
|
1929
|
+
// same as the pre-existing "template title change" test's initial-state assumption.
|
|
1930
|
+
it('reports isContentEmpty: true on mount when the (default text_message) description is empty', () => {
|
|
1931
|
+
const onContentValidityChange = jest.fn();
|
|
1932
|
+
renderHelper({ onContentValidityChange });
|
|
1933
|
+
|
|
1934
|
+
expect(onContentValidityChange).toHaveBeenCalledWith(
|
|
1935
|
+
expect.objectContaining({ isContentEmpty: true }),
|
|
1936
|
+
);
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
it('reports isContentEmpty: false once the description has content, then true again once cleared', () => {
|
|
1940
|
+
const onContentValidityChange = jest.fn();
|
|
1941
|
+
renderHelper({ onContentValidityChange });
|
|
1942
|
+
onContentValidityChange.mockClear();
|
|
1943
|
+
|
|
1944
|
+
const descTextArea = renderedComponent.find('CapInputTextArea#rcs_template_message_textarea').at(0);
|
|
1945
|
+
act(() => {
|
|
1946
|
+
descTextArea.props().onChange({ target: { value: 'Hello there' } });
|
|
1947
|
+
});
|
|
1948
|
+
renderedComponent.update();
|
|
1949
|
+
|
|
1950
|
+
expect(onContentValidityChange).toHaveBeenCalledWith(
|
|
1951
|
+
expect.objectContaining({ isContentEmpty: false }),
|
|
1952
|
+
);
|
|
1953
|
+
onContentValidityChange.mockClear();
|
|
1954
|
+
|
|
1955
|
+
const descTextAreaAfter = renderedComponent.find('CapInputTextArea#rcs_template_message_textarea').at(0);
|
|
1956
|
+
act(() => {
|
|
1957
|
+
descTextAreaAfter.props().onChange({ target: { value: ' ' } });
|
|
1958
|
+
});
|
|
1959
|
+
renderedComponent.update();
|
|
1960
|
+
|
|
1961
|
+
expect(onContentValidityChange).toHaveBeenCalledWith(
|
|
1962
|
+
expect.objectContaining({ isContentEmpty: true }),
|
|
1963
|
+
);
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
it('does not throw and does not report when onContentValidityChange is not provided', () => {
|
|
1967
|
+
expect(() => {
|
|
1968
|
+
renderHelper({});
|
|
1969
|
+
const descTextArea = renderedComponent.find('CapInputTextArea#rcs_template_message_textarea').at(0);
|
|
1970
|
+
act(() => {
|
|
1971
|
+
descTextArea.props().onChange({ target: { value: 'abc' } });
|
|
1972
|
+
});
|
|
1973
|
+
renderedComponent.update();
|
|
1974
|
+
}).not.toThrow();
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
it('does not dispatch again on a re-render that does not change validity (dedup guard)', () => {
|
|
1978
|
+
const onContentValidityChange = jest.fn();
|
|
1979
|
+
renderHelper({ onContentValidityChange });
|
|
1980
|
+
onContentValidityChange.mockClear();
|
|
1981
|
+
|
|
1982
|
+
const descTextArea = renderedComponent.find('CapInputTextArea#rcs_template_message_textarea').at(0);
|
|
1983
|
+
act(() => {
|
|
1984
|
+
// Still empty (still whitespace-only) — validity does not actually change.
|
|
1985
|
+
descTextArea.props().onChange({ target: { value: ' ' } });
|
|
1986
|
+
});
|
|
1987
|
+
renderedComponent.update();
|
|
1988
|
+
|
|
1989
|
+
expect(onContentValidityChange).not.toHaveBeenCalled();
|
|
1990
|
+
});
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1926
1993
|
describe('CapActionButton (mocked within RCS tests)', () => {
|
|
1927
1994
|
it('should render the mocked CapActionButton placeholder', () => {
|
|
1928
1995
|
const wrapper = mountWithIntl(
|
|
@@ -185,7 +185,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
componentDidUpdate() {
|
|
188
|
-
|
|
188
|
+
// Reporting itself is opt-in via the callback prop — not tied to embeddedSmsFallback,
|
|
189
|
+
// which is a separate, unrelated flag (tag-popover styling for the SMS-fallback surface).
|
|
190
|
+
if (typeof this.props.onEmbeddedSmsFooterValidity !== 'function') {
|
|
189
191
|
return;
|
|
190
192
|
}
|
|
191
193
|
const validity = getSmsEmbeddedFooterValidity(this.state.formData, this.state.tabCount);
|
|
@@ -34,6 +34,7 @@ import v2EditSmsReducer from './reducer';
|
|
|
34
34
|
import { v2SmsEditSagas } from './sagas';
|
|
35
35
|
import * as globalActions from '../../Cap/actions';
|
|
36
36
|
import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
|
|
37
|
+
import { getSmsEmbeddedFooterValidity } from '../smsFormDataHelpers';
|
|
37
38
|
|
|
38
39
|
export class Edit extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
39
40
|
constructor(props) {
|
|
@@ -54,6 +55,11 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
54
55
|
isTestAndPreviewMode: false,
|
|
55
56
|
pendingGetFormData: false,
|
|
56
57
|
};
|
|
58
|
+
// Tracks the last validity value reported to the parent so componentDidUpdate does not
|
|
59
|
+
// dispatch on every render. Intentionally undefined (not true) so the first render
|
|
60
|
+
// always reports the real validity rather than assuming the form starts invalid.
|
|
61
|
+
this._lastReportedSmsFooterTemplateNameEmpty = undefined;
|
|
62
|
+
this._lastReportedSmsFooterMessageEmpty = undefined;
|
|
57
63
|
this.saveFormData = this.saveFormData.bind(this);
|
|
58
64
|
this.onFormDataChange = this.onFormDataChange.bind(this);
|
|
59
65
|
this.onTemplateNameChange = this.onTemplateNameChange.bind(this);
|
|
@@ -189,6 +195,24 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
189
195
|
}
|
|
190
196
|
}
|
|
191
197
|
|
|
198
|
+
componentDidUpdate() {
|
|
199
|
+
// Reports live form validity so the host (CreativesContainer) can disable Done/Preview
|
|
200
|
+
// and Test as soon as the message body is cleared — mirrors Sms/Create's own reporting.
|
|
201
|
+
if (typeof this.props.onEmbeddedSmsFooterValidity !== 'function') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const validity = getSmsEmbeddedFooterValidity(this.state.formData, this.state.tabCount);
|
|
205
|
+
const isTemplateNameEmpty = !!validity.isTemplateNameEmpty;
|
|
206
|
+
const isMessageEmpty = !!validity.isMessageEmpty;
|
|
207
|
+
if (
|
|
208
|
+
this._lastReportedSmsFooterTemplateNameEmpty === isTemplateNameEmpty &&
|
|
209
|
+
this._lastReportedSmsFooterMessageEmpty === isMessageEmpty
|
|
210
|
+
) return;
|
|
211
|
+
this._lastReportedSmsFooterTemplateNameEmpty = isTemplateNameEmpty;
|
|
212
|
+
this._lastReportedSmsFooterMessageEmpty = isMessageEmpty;
|
|
213
|
+
this.props.onEmbeddedSmsFooterValidity(validity);
|
|
214
|
+
}
|
|
215
|
+
|
|
192
216
|
componentWillUnmount() {
|
|
193
217
|
if (this.pendingGetFormDataTimeout) {
|
|
194
218
|
clearTimeout(this.pendingGetFormDataTimeout);
|
|
@@ -1138,6 +1162,7 @@ Edit.propTypes = {
|
|
|
1138
1162
|
handleTestAndPreview: PropTypes.func,
|
|
1139
1163
|
handleCloseTestAndPreview: PropTypes.func,
|
|
1140
1164
|
isTestAndPreviewMode: PropTypes.bool,
|
|
1165
|
+
onEmbeddedSmsFooterValidity: PropTypes.func,
|
|
1141
1166
|
};
|
|
1142
1167
|
|
|
1143
1168
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from 'enzyme';
|
|
3
|
+
import { Edit } from '../index';
|
|
4
|
+
|
|
5
|
+
// Shallow-render the plain class (bypassing the withCreatives/Redux/saga HOC) so this test
|
|
6
|
+
// only exercises the componentDidUpdate → onEmbeddedSmsFooterValidity wiring in isolation.
|
|
7
|
+
|
|
8
|
+
const baseProps = () => ({
|
|
9
|
+
actions: {
|
|
10
|
+
getTemplateDetails: jest.fn(),
|
|
11
|
+
setTemplateDetails: jest.fn(),
|
|
12
|
+
clearEditResponse: jest.fn(),
|
|
13
|
+
resetEditTemplate: jest.fn(),
|
|
14
|
+
editTemplate: jest.fn(),
|
|
15
|
+
},
|
|
16
|
+
globalActions: {
|
|
17
|
+
fetchSchemaForEntity: jest.fn(),
|
|
18
|
+
setInjectedTags: jest.fn(),
|
|
19
|
+
addMessageToQueue: jest.fn(),
|
|
20
|
+
},
|
|
21
|
+
Edit: {},
|
|
22
|
+
params: { id: 'tpl-1' },
|
|
23
|
+
location: { query: { type: 'embedded', module: 'library' } },
|
|
24
|
+
metaEntities: {},
|
|
25
|
+
intl: { formatMessage: (m) => m.defaultMessage || m.id },
|
|
26
|
+
isFullMode: false,
|
|
27
|
+
getFormSubscriptionData: jest.fn(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('Sms/Edit — onEmbeddedSmsFooterValidity reporting', () => {
|
|
31
|
+
it('reports isMessageEmpty: true on mount when the message body is empty', () => {
|
|
32
|
+
const onEmbeddedSmsFooterValidity = jest.fn();
|
|
33
|
+
const wrapper = shallow(<Edit {...baseProps()} onEmbeddedSmsFooterValidity={onEmbeddedSmsFooterValidity} />);
|
|
34
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': '' }, base: { 'sms-editor': '' } }, tabCount: 1 });
|
|
35
|
+
|
|
36
|
+
expect(onEmbeddedSmsFooterValidity).toHaveBeenCalledWith(
|
|
37
|
+
expect.objectContaining({ isMessageEmpty: true }),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('reports isMessageEmpty: false once the message body has content', () => {
|
|
42
|
+
const onEmbeddedSmsFooterValidity = jest.fn();
|
|
43
|
+
const wrapper = shallow(<Edit {...baseProps()} onEmbeddedSmsFooterValidity={onEmbeddedSmsFooterValidity} />);
|
|
44
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': '' }, base: { 'sms-editor': '' } }, tabCount: 1 });
|
|
45
|
+
onEmbeddedSmsFooterValidity.mockClear();
|
|
46
|
+
|
|
47
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': 'Hello there' }, base: { 'sms-editor': 'Hello there' } }, tabCount: 1 });
|
|
48
|
+
|
|
49
|
+
expect(onEmbeddedSmsFooterValidity).toHaveBeenCalledWith(
|
|
50
|
+
expect.objectContaining({ isMessageEmpty: false }),
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('re-reports empty when an existing (edit-loaded) message is cleared out', () => {
|
|
55
|
+
const onEmbeddedSmsFooterValidity = jest.fn();
|
|
56
|
+
const wrapper = shallow(<Edit {...baseProps()} onEmbeddedSmsFooterValidity={onEmbeddedSmsFooterValidity} />);
|
|
57
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': 'Existing content' }, base: { 'sms-editor': 'Existing content' } }, tabCount: 1 });
|
|
58
|
+
onEmbeddedSmsFooterValidity.mockClear();
|
|
59
|
+
|
|
60
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': '' }, base: { 'sms-editor': '' } }, tabCount: 1 });
|
|
61
|
+
|
|
62
|
+
expect(onEmbeddedSmsFooterValidity).toHaveBeenCalledWith(
|
|
63
|
+
expect.objectContaining({ isMessageEmpty: true }),
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('does not throw and does not report when onEmbeddedSmsFooterValidity is not provided', () => {
|
|
68
|
+
const wrapper = shallow(<Edit {...baseProps()} />);
|
|
69
|
+
expect(() => {
|
|
70
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': '' } }, tabCount: 1 });
|
|
71
|
+
}).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not dispatch again on a re-render that does not change validity (dedup guard)', () => {
|
|
75
|
+
const onEmbeddedSmsFooterValidity = jest.fn();
|
|
76
|
+
const wrapper = shallow(<Edit {...baseProps()} onEmbeddedSmsFooterValidity={onEmbeddedSmsFooterValidity} />);
|
|
77
|
+
wrapper.setState({ formData: { 0: { 'sms-editor': '' } }, tabCount: 1 });
|
|
78
|
+
onEmbeddedSmsFooterValidity.mockClear();
|
|
79
|
+
|
|
80
|
+
// Unrelated state change — validity tuple (isTemplateNameEmpty/isMessageEmpty) is unchanged.
|
|
81
|
+
wrapper.setState({ currentTab: 1 });
|
|
82
|
+
|
|
83
|
+
expect(onEmbeddedSmsFooterValidity).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -12707,6 +12707,7 @@ FREE GIFTS-
|
|
|
12707
12707
|
"getTestGroupsRequested": [Function],
|
|
12708
12708
|
"getWeCrmAccountsRequested": [Function],
|
|
12709
12709
|
"searchCustomersRequested": [Function],
|
|
12710
|
+
"sendCcsTestMessageRequested": [Function],
|
|
12710
12711
|
"sendTestMessageRequested": [Function],
|
|
12711
12712
|
"updatePreviewRequested": [Function],
|
|
12712
12713
|
}
|
|
@@ -12752,6 +12753,7 @@ FREE GIFTS-
|
|
|
12752
12753
|
isFetchingTestGroups={false}
|
|
12753
12754
|
isLoadingSenderDetails={false}
|
|
12754
12755
|
isSearchingCustomer={false}
|
|
12756
|
+
isSendingCcsTestMessage={false}
|
|
12755
12757
|
isSendingTestMessage={false}
|
|
12756
12758
|
isUpdatingPreview={false}
|
|
12757
12759
|
messageMetaConfigId={null}
|
|
@@ -12788,11 +12790,13 @@ FREE GIFTS-
|
|
|
12788
12790
|
"getTestGroupsRequested": [Function],
|
|
12789
12791
|
"getWeCrmAccountsRequested": [Function],
|
|
12790
12792
|
"searchCustomersRequested": [Function],
|
|
12793
|
+
"sendCcsTestMessageRequested": [Function],
|
|
12791
12794
|
"sendTestMessageRequested": [Function],
|
|
12792
12795
|
"updatePreviewRequested": [Function],
|
|
12793
12796
|
}
|
|
12794
12797
|
}
|
|
12795
12798
|
beeInstance={null}
|
|
12799
|
+
ccsSendTransaction={null}
|
|
12796
12800
|
channel={null}
|
|
12797
12801
|
content="LATEST FASHION@VISHAL
|
|
12798
12802
|
{#var#}
|
|
@@ -15081,6 +15085,7 @@ new message content.",
|
|
|
15081
15085
|
isFetchingTestGroups={false}
|
|
15082
15086
|
isLoadingSenderDetails={false}
|
|
15083
15087
|
isSearchingCustomer={false}
|
|
15088
|
+
isSendingCcsTestMessage={false}
|
|
15084
15089
|
isSendingTestMessage={false}
|
|
15085
15090
|
isUpdatingPreview={false}
|
|
15086
15091
|
messageMetaConfigId={null}
|
|
@@ -15119,11 +15124,13 @@ new message content.",
|
|
|
15119
15124
|
"getTestGroupsRequested": [Function],
|
|
15120
15125
|
"getWeCrmAccountsRequested": [Function],
|
|
15121
15126
|
"searchCustomersRequested": [Function],
|
|
15127
|
+
"sendCcsTestMessageRequested": [Function],
|
|
15122
15128
|
"sendTestMessageRequested": [Function],
|
|
15123
15129
|
"updatePreviewRequested": [Function],
|
|
15124
15130
|
}
|
|
15125
15131
|
}
|
|
15126
15132
|
beeInstance={null}
|
|
15133
|
+
ccsSendTransaction={null}
|
|
15127
15134
|
channel="SMS"
|
|
15128
15135
|
config={
|
|
15129
15136
|
Object {
|
|
@@ -17419,6 +17426,7 @@ new message content.",
|
|
|
17419
17426
|
isFetchingTestGroups={false}
|
|
17420
17427
|
isLoadingSenderDetails={false}
|
|
17421
17428
|
isSearchingCustomer={false}
|
|
17429
|
+
isSendingCcsTestMessage={false}
|
|
17422
17430
|
isSendingTestMessage={false}
|
|
17423
17431
|
isUpdatingPreview={false}
|
|
17424
17432
|
messageMetaConfigId={null}
|
|
@@ -17638,7 +17646,7 @@ FREE GIFTS-
|
|
|
17638
17646
|
</CapRow>
|
|
17639
17647
|
}
|
|
17640
17648
|
show={false}
|
|
17641
|
-
size="size-
|
|
17649
|
+
size="size-l"
|
|
17642
17650
|
/>
|
|
17643
17651
|
</CommonTestAndPreview>
|
|
17644
17652
|
</TestAndPreviewSlidebox>
|
|
@@ -31827,6 +31835,7 @@ FREE GIFTS-
|
|
|
31827
31835
|
"getTestGroupsRequested": [Function],
|
|
31828
31836
|
"getWeCrmAccountsRequested": [Function],
|
|
31829
31837
|
"searchCustomersRequested": [Function],
|
|
31838
|
+
"sendCcsTestMessageRequested": [Function],
|
|
31830
31839
|
"sendTestMessageRequested": [Function],
|
|
31831
31840
|
"updatePreviewRequested": [Function],
|
|
31832
31841
|
}
|
|
@@ -31872,6 +31881,7 @@ FREE GIFTS-
|
|
|
31872
31881
|
isFetchingTestGroups={false}
|
|
31873
31882
|
isLoadingSenderDetails={false}
|
|
31874
31883
|
isSearchingCustomer={false}
|
|
31884
|
+
isSendingCcsTestMessage={false}
|
|
31875
31885
|
isSendingTestMessage={false}
|
|
31876
31886
|
isUpdatingPreview={false}
|
|
31877
31887
|
messageMetaConfigId={null}
|
|
@@ -31908,11 +31918,13 @@ FREE GIFTS-
|
|
|
31908
31918
|
"getTestGroupsRequested": [Function],
|
|
31909
31919
|
"getWeCrmAccountsRequested": [Function],
|
|
31910
31920
|
"searchCustomersRequested": [Function],
|
|
31921
|
+
"sendCcsTestMessageRequested": [Function],
|
|
31911
31922
|
"sendTestMessageRequested": [Function],
|
|
31912
31923
|
"updatePreviewRequested": [Function],
|
|
31913
31924
|
}
|
|
31914
31925
|
}
|
|
31915
31926
|
beeInstance={null}
|
|
31927
|
+
ccsSendTransaction={null}
|
|
31916
31928
|
channel={null}
|
|
31917
31929
|
content="LATEST FASHION@VISHAL
|
|
31918
31930
|
test1
|
|
@@ -34201,6 +34213,7 @@ new message content.",
|
|
|
34201
34213
|
isFetchingTestGroups={false}
|
|
34202
34214
|
isLoadingSenderDetails={false}
|
|
34203
34215
|
isSearchingCustomer={false}
|
|
34216
|
+
isSendingCcsTestMessage={false}
|
|
34204
34217
|
isSendingTestMessage={false}
|
|
34205
34218
|
isUpdatingPreview={false}
|
|
34206
34219
|
messageMetaConfigId={null}
|
|
@@ -34239,11 +34252,13 @@ new message content.",
|
|
|
34239
34252
|
"getTestGroupsRequested": [Function],
|
|
34240
34253
|
"getWeCrmAccountsRequested": [Function],
|
|
34241
34254
|
"searchCustomersRequested": [Function],
|
|
34255
|
+
"sendCcsTestMessageRequested": [Function],
|
|
34242
34256
|
"sendTestMessageRequested": [Function],
|
|
34243
34257
|
"updatePreviewRequested": [Function],
|
|
34244
34258
|
}
|
|
34245
34259
|
}
|
|
34246
34260
|
beeInstance={null}
|
|
34261
|
+
ccsSendTransaction={null}
|
|
34247
34262
|
channel="SMS"
|
|
34248
34263
|
config={
|
|
34249
34264
|
Object {
|
|
@@ -36539,6 +36554,7 @@ new message content.",
|
|
|
36539
36554
|
isFetchingTestGroups={false}
|
|
36540
36555
|
isLoadingSenderDetails={false}
|
|
36541
36556
|
isSearchingCustomer={false}
|
|
36557
|
+
isSendingCcsTestMessage={false}
|
|
36542
36558
|
isSendingTestMessage={false}
|
|
36543
36559
|
isUpdatingPreview={false}
|
|
36544
36560
|
messageMetaConfigId={null}
|
|
@@ -36758,7 +36774,7 @@ FREE GIFTS-
|
|
|
36758
36774
|
</CapRow>
|
|
36759
36775
|
}
|
|
36760
36776
|
show={false}
|
|
36761
|
-
size="size-
|
|
36777
|
+
size="size-l"
|
|
36762
36778
|
/>
|
|
36763
36779
|
</CommonTestAndPreview>
|
|
36764
36780
|
</TestAndPreviewSlidebox>
|
|
@@ -49483,6 +49499,7 @@ FREE GIFTS-
|
|
|
49483
49499
|
"getTestGroupsRequested": [Function],
|
|
49484
49500
|
"getWeCrmAccountsRequested": [Function],
|
|
49485
49501
|
"searchCustomersRequested": [Function],
|
|
49502
|
+
"sendCcsTestMessageRequested": [Function],
|
|
49486
49503
|
"sendTestMessageRequested": [Function],
|
|
49487
49504
|
"updatePreviewRequested": [Function],
|
|
49488
49505
|
}
|
|
@@ -49528,6 +49545,7 @@ FREE GIFTS-
|
|
|
49528
49545
|
isFetchingTestGroups={false}
|
|
49529
49546
|
isLoadingSenderDetails={false}
|
|
49530
49547
|
isSearchingCustomer={false}
|
|
49548
|
+
isSendingCcsTestMessage={false}
|
|
49531
49549
|
isSendingTestMessage={false}
|
|
49532
49550
|
isUpdatingPreview={false}
|
|
49533
49551
|
messageMetaConfigId={null}
|
|
@@ -49564,11 +49582,13 @@ FREE GIFTS-
|
|
|
49564
49582
|
"getTestGroupsRequested": [Function],
|
|
49565
49583
|
"getWeCrmAccountsRequested": [Function],
|
|
49566
49584
|
"searchCustomersRequested": [Function],
|
|
49585
|
+
"sendCcsTestMessageRequested": [Function],
|
|
49567
49586
|
"sendTestMessageRequested": [Function],
|
|
49568
49587
|
"updatePreviewRequested": [Function],
|
|
49569
49588
|
}
|
|
49570
49589
|
}
|
|
49571
49590
|
beeInstance={null}
|
|
49591
|
+
ccsSendTransaction={null}
|
|
49572
49592
|
channel={null}
|
|
49573
49593
|
content="LATEST FASHION@VISHAL
|
|
49574
49594
|
{#var#}
|
|
@@ -51857,6 +51877,7 @@ new message content.",
|
|
|
51857
51877
|
isFetchingTestGroups={false}
|
|
51858
51878
|
isLoadingSenderDetails={false}
|
|
51859
51879
|
isSearchingCustomer={false}
|
|
51880
|
+
isSendingCcsTestMessage={false}
|
|
51860
51881
|
isSendingTestMessage={false}
|
|
51861
51882
|
isUpdatingPreview={false}
|
|
51862
51883
|
messageMetaConfigId={null}
|
|
@@ -51895,11 +51916,13 @@ new message content.",
|
|
|
51895
51916
|
"getTestGroupsRequested": [Function],
|
|
51896
51917
|
"getWeCrmAccountsRequested": [Function],
|
|
51897
51918
|
"searchCustomersRequested": [Function],
|
|
51919
|
+
"sendCcsTestMessageRequested": [Function],
|
|
51898
51920
|
"sendTestMessageRequested": [Function],
|
|
51899
51921
|
"updatePreviewRequested": [Function],
|
|
51900
51922
|
}
|
|
51901
51923
|
}
|
|
51902
51924
|
beeInstance={null}
|
|
51925
|
+
ccsSendTransaction={null}
|
|
51903
51926
|
channel="SMS"
|
|
51904
51927
|
config={
|
|
51905
51928
|
Object {
|
|
@@ -54195,6 +54218,7 @@ new message content.",
|
|
|
54195
54218
|
isFetchingTestGroups={false}
|
|
54196
54219
|
isLoadingSenderDetails={false}
|
|
54197
54220
|
isSearchingCustomer={false}
|
|
54221
|
+
isSendingCcsTestMessage={false}
|
|
54198
54222
|
isSendingTestMessage={false}
|
|
54199
54223
|
isUpdatingPreview={false}
|
|
54200
54224
|
messageMetaConfigId={null}
|
|
@@ -54414,7 +54438,7 @@ FREE GIFTS-
|
|
|
54414
54438
|
</CapRow>
|
|
54415
54439
|
}
|
|
54416
54440
|
show={false}
|
|
54417
|
-
size="size-
|
|
54441
|
+
size="size-l"
|
|
54418
54442
|
/>
|
|
54419
54443
|
</CommonTestAndPreview>
|
|
54420
54444
|
</TestAndPreviewSlidebox>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useState, useEffect, useCallback, useRef,
|
|
3
|
+
} from 'react';
|
|
2
4
|
import { bindActionCreators } from 'redux';
|
|
3
5
|
import { createStructuredSelector } from 'reselect';
|
|
4
6
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
@@ -139,6 +141,7 @@ export const Viber = (props) => {
|
|
|
139
141
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
140
142
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
141
143
|
handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
|
|
144
|
+
onContentValidityChange,
|
|
142
145
|
} = props || {};
|
|
143
146
|
|
|
144
147
|
const { formatMessage } = intl;
|
|
@@ -257,6 +260,26 @@ export const Viber = (props) => {
|
|
|
257
260
|
}
|
|
258
261
|
}, [viber.templateDetails || templateData]);
|
|
259
262
|
|
|
263
|
+
// Reports live message-content validity to a parent (e.g. CreativesContainer's
|
|
264
|
+
// slidebox) so it can disable its own Done/Preview-and-test buttons whenever the
|
|
265
|
+
// Viber message body is empty/whitespace-only. Mirrors the Sms Create/Edit
|
|
266
|
+
// componentDidUpdate pattern, using a ref (instead of an instance field) as the
|
|
267
|
+
// dedup guard so the callback only fires when the reported value actually changes -
|
|
268
|
+
// calling it unconditionally on every render risks an infinite update loop when the
|
|
269
|
+
// parent's setState triggers a re-render of this component.
|
|
270
|
+
const lastReportedContentEmptyRef = useRef(undefined);
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (typeof onContentValidityChange !== 'function') {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const isContentEmpty = (messageContent || '').trim() === '';
|
|
276
|
+
if (lastReportedContentEmptyRef.current === isContentEmpty) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
lastReportedContentEmptyRef.current = isContentEmpty;
|
|
280
|
+
onContentValidityChange({ isContentEmpty });
|
|
281
|
+
}, [messageContent, onContentValidityChange]);
|
|
282
|
+
|
|
260
283
|
// Text area Code start here
|
|
261
284
|
|
|
262
285
|
// Tags Code start from here
|
|
@@ -469,4 +469,107 @@ describe('Test Viber container', () => {
|
|
|
469
469
|
|
|
470
470
|
expect(await screen.findByText("URL can't be empty")).toBeInTheDocument();
|
|
471
471
|
});
|
|
472
|
+
|
|
473
|
+
describe('onContentValidityChange reporting (Done/Preview button gating in the slidebox)', () => {
|
|
474
|
+
it('reports isContentEmpty: true on mount for an empty create flow, then false once a message is typed', async () => {
|
|
475
|
+
const onContentValidityChange = jest.fn();
|
|
476
|
+
renderComponent({
|
|
477
|
+
actions: mockActions,
|
|
478
|
+
globalActions: mockGlobalActions,
|
|
479
|
+
templateData: { mode: 'create' },
|
|
480
|
+
viber: {
|
|
481
|
+
uploadedAssetData: {},
|
|
482
|
+
createTemplateInProgress: false,
|
|
483
|
+
},
|
|
484
|
+
location: {
|
|
485
|
+
pathname: '/sms/edit',
|
|
486
|
+
query: { type: false, module: 'default' },
|
|
487
|
+
search: '',
|
|
488
|
+
},
|
|
489
|
+
isFullMode: true,
|
|
490
|
+
handleClose: jest.fn(),
|
|
491
|
+
onContentValidityChange,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
await waitFor(() => {
|
|
495
|
+
expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const msgBox = screen.getAllByPlaceholderText(/Enter message/i)[0];
|
|
499
|
+
fireEvent.change(msgBox, { target: { value: 'hello there' } });
|
|
500
|
+
|
|
501
|
+
await waitFor(() => {
|
|
502
|
+
expect(onContentValidityChange).toHaveBeenLastCalledWith({ isContentEmpty: false });
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('does not re-report the same validity value (dedup guard)', async () => {
|
|
507
|
+
const onContentValidityChange = jest.fn();
|
|
508
|
+
renderComponent({
|
|
509
|
+
actions: mockActions,
|
|
510
|
+
globalActions: mockGlobalActions,
|
|
511
|
+
templateData: { mode: 'create' },
|
|
512
|
+
viber: {
|
|
513
|
+
uploadedAssetData: {},
|
|
514
|
+
createTemplateInProgress: false,
|
|
515
|
+
},
|
|
516
|
+
location: {
|
|
517
|
+
pathname: '/sms/edit',
|
|
518
|
+
query: { type: false, module: 'default' },
|
|
519
|
+
search: '',
|
|
520
|
+
},
|
|
521
|
+
isFullMode: true,
|
|
522
|
+
handleClose: jest.fn(),
|
|
523
|
+
onContentValidityChange,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
await waitFor(() => {
|
|
527
|
+
expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
|
|
528
|
+
});
|
|
529
|
+
const callsAfterMount = onContentValidityChange.mock.calls.length;
|
|
530
|
+
|
|
531
|
+
// Whitespace-only change: still empty, should not trigger another call.
|
|
532
|
+
const msgBox = screen.getAllByPlaceholderText(/Enter message/i)[0];
|
|
533
|
+
fireEvent.change(msgBox, { target: { value: ' ' } });
|
|
534
|
+
|
|
535
|
+
expect(onContentValidityChange).toHaveBeenCalledTimes(callsAfterMount);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('reports isContentEmpty: false for an edit flow pre-filled with message content, then true once cleared', async () => {
|
|
539
|
+
const onContentValidityChange = jest.fn();
|
|
540
|
+
renderComponent({
|
|
541
|
+
actions: mockActions,
|
|
542
|
+
globalActions: mockGlobalActions,
|
|
543
|
+
templateData: { mode: 'create' },
|
|
544
|
+
viber: {
|
|
545
|
+
uploadedAssetData: {},
|
|
546
|
+
createTemplateInProgress: false,
|
|
547
|
+
templateDetails: templateDetailsText,
|
|
548
|
+
},
|
|
549
|
+
location: {
|
|
550
|
+
pathname: '/sms/edit',
|
|
551
|
+
query: { type: false, module: 'default' },
|
|
552
|
+
search: '',
|
|
553
|
+
},
|
|
554
|
+
isFullMode: true,
|
|
555
|
+
params: { id: 'test' },
|
|
556
|
+
handleClose: jest.fn(),
|
|
557
|
+
metaEntities,
|
|
558
|
+
getDefaultTags,
|
|
559
|
+
injectedTags,
|
|
560
|
+
onContentValidityChange,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
await waitFor(() => {
|
|
564
|
+
expect(onContentValidityChange).toHaveBeenLastCalledWith({ isContentEmpty: false });
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
const msgBox = screen.getAllByPlaceholderText(/Enter message/i)[0];
|
|
568
|
+
fireEvent.change(msgBox, { target: { value: '' } });
|
|
569
|
+
|
|
570
|
+
await waitFor(() => {
|
|
571
|
+
expect(onContentValidityChange).toHaveBeenLastCalledWith({ isContentEmpty: true });
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
});
|
|
472
575
|
});
|
|
@@ -164,6 +164,7 @@ const WebPushCreate = ({
|
|
|
164
164
|
templateActions: templateActionsProps,
|
|
165
165
|
Templates,
|
|
166
166
|
restrictPersonalization = false,
|
|
167
|
+
onContentValidityChange,
|
|
167
168
|
}) => {
|
|
168
169
|
const { formatMessage } = intl;
|
|
169
170
|
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
@@ -194,6 +195,10 @@ const WebPushCreate = ({
|
|
|
194
195
|
const messageCountRef = useRef(null);
|
|
195
196
|
const saveInitiatedRef = useRef(false);
|
|
196
197
|
const messageTextAreaRef = useRef(null);
|
|
198
|
+
// Tracks the last isContentEmpty value reported to the parent (CreativesContainer) so the
|
|
199
|
+
// effect below does not dispatch on every render. Intentionally undefined (not a boolean)
|
|
200
|
+
// so the first render always reports the real validity rather than assuming a starting value.
|
|
201
|
+
const lastReportedContentEmptyRef = useRef(undefined);
|
|
197
202
|
|
|
198
203
|
// Custom hooks
|
|
199
204
|
const { updateCharacterCount } = useCharacterCount(formatMessage, messages);
|
|
@@ -566,6 +571,19 @@ const WebPushCreate = ({
|
|
|
566
571
|
});
|
|
567
572
|
}, [isFullMode, templateName]);
|
|
568
573
|
|
|
574
|
+
// Dedup guard (lastReportedContentEmptyRef) avoids re-triggering the parent's setState loop.
|
|
575
|
+
useEffect(() => {
|
|
576
|
+
if (typeof onContentValidityChange !== 'function') {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const isContentEmpty = !notificationTitle.trim() || !message.trim();
|
|
580
|
+
if (lastReportedContentEmptyRef.current === isContentEmpty) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
lastReportedContentEmptyRef.current = isContentEmpty;
|
|
584
|
+
onContentValidityChange({ isContentEmpty });
|
|
585
|
+
}, [notificationTitle, message, onContentValidityChange]);
|
|
586
|
+
|
|
569
587
|
// Pure validator that returns boolean without setting error state
|
|
570
588
|
const validateFormSilent = () => {
|
|
571
589
|
const templateNameInvalid = isFullMode && validateTemplateName(templateName);
|
|
@@ -1175,6 +1193,7 @@ WebPushCreate.propTypes = {
|
|
|
1175
1193
|
waitEventContextTags: PropTypes.object,
|
|
1176
1194
|
templateActions: PropTypes.object,
|
|
1177
1195
|
restrictPersonalization: PropTypes.bool,
|
|
1196
|
+
onContentValidityChange: PropTypes.func,
|
|
1178
1197
|
};
|
|
1179
1198
|
|
|
1180
1199
|
WebPushCreate.defaultProps = {
|