@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
|
@@ -33,6 +33,7 @@ const WebPushPreview = ({
|
|
|
33
33
|
brandIconSrc,
|
|
34
34
|
buttons,
|
|
35
35
|
onClickShowInAllDevices,
|
|
36
|
+
showPreviewInAllDevices,
|
|
36
37
|
}) => {
|
|
37
38
|
const [selectedOS, setSelectedOS] = useState(DEFAULT_OS);
|
|
38
39
|
const [selectedBrowser, setSelectedBrowser] = useState(DEFAULT_BROWSER);
|
|
@@ -93,18 +94,20 @@ const WebPushPreview = ({
|
|
|
93
94
|
buttons={buttons}
|
|
94
95
|
/>
|
|
95
96
|
<PreviewDisclaimer />
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
{showPreviewInAllDevices && (
|
|
98
|
+
<CapRow className="preview-all-devices-button">
|
|
99
|
+
<CapColumn span={24}>
|
|
100
|
+
<CapButton
|
|
101
|
+
type="link"
|
|
102
|
+
onClick={handleOpenPreviewSlideBox}
|
|
103
|
+
className="preview-all-devices-trigger"
|
|
104
|
+
data-test-id="webpush-preview-toggle"
|
|
105
|
+
>
|
|
106
|
+
<FormattedMessage {...messages.previewInAllDevices} />
|
|
107
|
+
</CapButton>
|
|
108
|
+
</CapColumn>
|
|
109
|
+
</CapRow>
|
|
110
|
+
)}
|
|
108
111
|
</CapColumn>
|
|
109
112
|
<CapSlideBox
|
|
110
113
|
header={<FormattedMessage {...messages.webPushNotificationPreview} />}
|
|
@@ -141,6 +144,7 @@ WebPushPreview.propTypes = {
|
|
|
141
144
|
})
|
|
142
145
|
),
|
|
143
146
|
onClickShowInAllDevices: PropTypes.func,
|
|
147
|
+
showPreviewInAllDevices: PropTypes.bool,
|
|
144
148
|
};
|
|
145
149
|
|
|
146
150
|
WebPushPreview.defaultProps = {
|
|
@@ -150,6 +154,7 @@ WebPushPreview.defaultProps = {
|
|
|
150
154
|
imageSrc: '',
|
|
151
155
|
brandIconSrc: '',
|
|
152
156
|
buttons: [],
|
|
157
|
+
showPreviewInAllDevices: true,
|
|
153
158
|
};
|
|
154
159
|
|
|
155
160
|
export default WebPushPreview;
|
|
@@ -109,6 +109,11 @@ describe('WebPushPreview', () => {
|
|
|
109
109
|
expect(button.hasClass('preview-all-devices-trigger')).toBe(true);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
it('should hide "Preview in all devices" button when showPreviewInAllDevices is false', () => {
|
|
113
|
+
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} showPreviewInAllDevices={false} />);
|
|
114
|
+
expect(wrapper.find('.preview-all-devices-button').exists()).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
112
117
|
it('should render CapSlideBox component', () => {
|
|
113
118
|
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
114
119
|
const allSlideBoxes = wrapper.find(CapSlideBox);
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the onContentValidityChange reporting wired into WebPushCreate.
|
|
3
|
+
*
|
|
4
|
+
* WebPushCreate is a functional component (not a class like Sms/Create and Sms/Edit), so the
|
|
5
|
+
* dedup pattern is implemented as a useEffect + useRef instead of componentDidUpdate + an
|
|
6
|
+
* instance field, but the contract with the parent (CreativesContainer) is identical:
|
|
7
|
+
* onContentValidityChange({ isContentEmpty }) is called only when isContentEmpty actually
|
|
8
|
+
* changes since the last report, with title AND message both required for WebPush.
|
|
9
|
+
*
|
|
10
|
+
* Note: all relative mock paths are resolved from THIS test file's location
|
|
11
|
+
* (app/v2Containers/WebPush/Create/tests/), not from the component — mirrors
|
|
12
|
+
* testAndPreviewIntegration.test.js in this same directory.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
17
|
+
import '@testing-library/jest-dom';
|
|
18
|
+
import { IntlProvider } from 'react-intl';
|
|
19
|
+
|
|
20
|
+
// ── Infrastructure / HOC mocks ──────────────────────────────────────────────
|
|
21
|
+
jest.mock('../../../../hoc/withCreatives', () => ({
|
|
22
|
+
__esModule: true,
|
|
23
|
+
default: ({ WrappedComponent }) => WrappedComponent,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
jest.mock('@capillarytech/vulcan-react-sdk/utils', () => ({
|
|
27
|
+
injectReducer: () => (Component) => Component,
|
|
28
|
+
injectSaga: () => (Component) => Component,
|
|
29
|
+
}));
|
|
30
|
+
jest.mock('@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes', () => ({
|
|
31
|
+
DAEMON: 'DAEMON',
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// ── Redux slices (paths relative to this test file) ─────────────────────────
|
|
35
|
+
jest.mock('../../../Templates/selectors', () => ({
|
|
36
|
+
makeSelectTemplates: () => () => {},
|
|
37
|
+
}));
|
|
38
|
+
jest.mock('../../../Templates/actions', () => ({}));
|
|
39
|
+
jest.mock('../../selectors', () => ({
|
|
40
|
+
makeSelectWebPush: () => () => ({}),
|
|
41
|
+
makeSelectCreateError: () => () => null,
|
|
42
|
+
makeSelectCreateTemplateInProgress: () => () => false,
|
|
43
|
+
makeSelectEditTemplateInProgress: () => () => false,
|
|
44
|
+
makeSelectEditError: () => () => null,
|
|
45
|
+
}));
|
|
46
|
+
jest.mock('../../actions', () => ({}));
|
|
47
|
+
jest.mock('../../reducer', () => () => ({}));
|
|
48
|
+
jest.mock('../../sagas', () => ({}));
|
|
49
|
+
jest.mock('../../../Templates/sagas', () => ({ v2TemplateSaga: {} }));
|
|
50
|
+
jest.mock('../../../Cap/selectors', () => ({
|
|
51
|
+
makeSelectMetaEntities: () => () => [],
|
|
52
|
+
setInjectedTags: () => () => [],
|
|
53
|
+
}));
|
|
54
|
+
jest.mock('../../../CreativesContainer/constants', () => ({
|
|
55
|
+
WEBPUSH: 'WEBPUSH',
|
|
56
|
+
}));
|
|
57
|
+
jest.mock('../../../CreativesContainer/messages', () => ({
|
|
58
|
+
testAndPreview: { id: 'testAndPreview', defaultMessage: 'Test & Preview' },
|
|
59
|
+
}));
|
|
60
|
+
jest.mock('../../../../utils/common', () => ({
|
|
61
|
+
isAiContentBotDisabled: () => false,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
// ── Form section components ──────────────────────────────────────────────────
|
|
65
|
+
// TemplateNameSection stays a stub — it is only relevant in isFullMode, which these tests
|
|
66
|
+
// keep off (mirrors baseProps below) to focus purely on title/message emptiness.
|
|
67
|
+
jest.mock('../components/TemplateNameSection', () => ({
|
|
68
|
+
__esModule: true,
|
|
69
|
+
default: () => <div data-testid="template-name-section" />,
|
|
70
|
+
}));
|
|
71
|
+
// NotificationTitleSection and MessageSection are given lightweight controlled-input stand-ins
|
|
72
|
+
// so tests can drive real onChange handlers (handleNotificationTitleChange / handleMessageChange)
|
|
73
|
+
// inside WebPushCreate, exercising the actual state that feeds the isContentEmpty computation.
|
|
74
|
+
jest.mock('../components/NotificationTitleSection', () => ({
|
|
75
|
+
__esModule: true,
|
|
76
|
+
default: ({ value, onChange }) => (
|
|
77
|
+
<input data-testid="notification-title-input" value={value} onChange={onChange} />
|
|
78
|
+
),
|
|
79
|
+
}));
|
|
80
|
+
jest.mock('../components/MessageSection', () => ({
|
|
81
|
+
__esModule: true,
|
|
82
|
+
default: ({ value, onChange }) => (
|
|
83
|
+
<textarea data-testid="message-input" value={value} onChange={onChange} />
|
|
84
|
+
),
|
|
85
|
+
}));
|
|
86
|
+
jest.mock('../components/MediaSection', () => ({
|
|
87
|
+
__esModule: true,
|
|
88
|
+
default: () => <div data-testid="media-section" />,
|
|
89
|
+
}));
|
|
90
|
+
jest.mock('../components/BrandIconSection', () => ({
|
|
91
|
+
__esModule: true,
|
|
92
|
+
default: () => <div data-testid="brand-icon-section" />,
|
|
93
|
+
}));
|
|
94
|
+
jest.mock('../components/ButtonsLinksSection', () => ({
|
|
95
|
+
__esModule: true,
|
|
96
|
+
default: () => <div data-testid="buttons-links-section" />,
|
|
97
|
+
}));
|
|
98
|
+
jest.mock('../components/FormActions', () => ({
|
|
99
|
+
__esModule: true,
|
|
100
|
+
default: () => <div data-testid="form-actions" />,
|
|
101
|
+
}));
|
|
102
|
+
jest.mock('../preview/WebPushPreview', () => ({
|
|
103
|
+
__esModule: true,
|
|
104
|
+
default: () => <div data-testid="webpush-preview" />,
|
|
105
|
+
}));
|
|
106
|
+
jest.mock('../../../TagList', () => ({
|
|
107
|
+
__esModule: true,
|
|
108
|
+
default: () => <div data-testid="tag-list" />,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
// ── Hooks ────────────────────────────────────────────────────────────────────
|
|
112
|
+
jest.mock('../hooks/useCharacterCount', () => ({
|
|
113
|
+
useCharacterCount: () => ({ updateCharacterCount: jest.fn() }),
|
|
114
|
+
}));
|
|
115
|
+
jest.mock('../hooks/useButtonManagement', () => ({
|
|
116
|
+
useButtonManagement: () => ({ buttons: [], setButtons: jest.fn(), isAddingButton: false }),
|
|
117
|
+
}));
|
|
118
|
+
jest.mock('../hooks/useImageUpload', () => ({
|
|
119
|
+
useImageUpload: () => ({
|
|
120
|
+
imageSrc: '',
|
|
121
|
+
imageUrl: '',
|
|
122
|
+
isValidating: false,
|
|
123
|
+
isUploading: false,
|
|
124
|
+
setImageSrc: jest.fn(),
|
|
125
|
+
setImageUrl: jest.fn(),
|
|
126
|
+
uploadMethod: 'UPLOAD_IMAGE',
|
|
127
|
+
}),
|
|
128
|
+
}));
|
|
129
|
+
jest.mock('../hooks/useTagManagement', () => ({
|
|
130
|
+
useTagManagement: () => ({ tags: [], extractTags: jest.fn(), handleOnTagsContextChange: jest.fn(), validationConfig: {} }),
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
jest.mock('../../../../v2Components/TestAndPreviewSlidebox', () => ({
|
|
134
|
+
__esModule: true,
|
|
135
|
+
default: () => <div data-testid="test-and-preview-slidebox" />,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
// ── CapUI library ─────────────────────────────────────────────────────────────
|
|
139
|
+
jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({
|
|
140
|
+
__esModule: true,
|
|
141
|
+
default: ({ children, className }) => <div className={className}>{children}</div>,
|
|
142
|
+
}));
|
|
143
|
+
jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({
|
|
144
|
+
__esModule: true,
|
|
145
|
+
default: ({ children, className }) => <div className={className}>{children}</div>,
|
|
146
|
+
}));
|
|
147
|
+
jest.mock('@capillarytech/cap-ui-library/CapButton', () => ({
|
|
148
|
+
__esModule: true,
|
|
149
|
+
default: ({ children, onClick, className, type }) => (
|
|
150
|
+
<button className={className} onClick={onClick} data-type={type}>
|
|
151
|
+
{children}
|
|
152
|
+
</button>
|
|
153
|
+
),
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
// ── Import component AFTER mocks ─────────────────────────────────────────────
|
|
157
|
+
// eslint-disable-next-line import/first
|
|
158
|
+
import WebPushCreateDefault from '../index';
|
|
159
|
+
|
|
160
|
+
const baseProps = () => ({
|
|
161
|
+
isFullMode: false,
|
|
162
|
+
handleClose: jest.fn(),
|
|
163
|
+
webPushActions: {
|
|
164
|
+
createTemplate: jest.fn(),
|
|
165
|
+
editTemplate: jest.fn(),
|
|
166
|
+
uploadImage: jest.fn(),
|
|
167
|
+
clearCreateError: jest.fn(),
|
|
168
|
+
clearEditError: jest.fn(),
|
|
169
|
+
clearCreateResponse: jest.fn(),
|
|
170
|
+
clearEditResponse: jest.fn(),
|
|
171
|
+
setSelectedAccount: jest.fn(),
|
|
172
|
+
clearState: jest.fn(),
|
|
173
|
+
},
|
|
174
|
+
createTemplateInProgress: false,
|
|
175
|
+
createTemplateError: null,
|
|
176
|
+
editTemplateInProgress: false,
|
|
177
|
+
editTemplateError: null,
|
|
178
|
+
accountData: null,
|
|
179
|
+
webPush: {},
|
|
180
|
+
onCreateComplete: jest.fn(),
|
|
181
|
+
getFormData: jest.fn(),
|
|
182
|
+
isGetFormData: false,
|
|
183
|
+
templateData: null,
|
|
184
|
+
creativesMode: 'create',
|
|
185
|
+
params: {},
|
|
186
|
+
globalActions: { getMetaEntities: jest.fn() },
|
|
187
|
+
location: { pathname: '/webpush', query: {}, search: '', state: {} },
|
|
188
|
+
metaEntities: [],
|
|
189
|
+
injectedTags: [],
|
|
190
|
+
getDefaultTags: jest.fn(),
|
|
191
|
+
supportedTags: [],
|
|
192
|
+
forwardedTags: [],
|
|
193
|
+
selectedOfferDetails: [],
|
|
194
|
+
eventContextTags: [],
|
|
195
|
+
waitEventContextTags: {},
|
|
196
|
+
templateActions: {},
|
|
197
|
+
Templates: { templates: [] },
|
|
198
|
+
restrictPersonalization: false,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const TestWrapper = ({ children }) => (
|
|
202
|
+
<IntlProvider locale="en" messages={{ testAndPreview: 'Test & Preview' }}>
|
|
203
|
+
{children}
|
|
204
|
+
</IntlProvider>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
describe('WebPushCreate — onContentValidityChange reporting', () => {
|
|
208
|
+
it('reports isContentEmpty: true on mount when title and message are both empty', () => {
|
|
209
|
+
const onContentValidityChange = jest.fn();
|
|
210
|
+
render(
|
|
211
|
+
<TestWrapper>
|
|
212
|
+
<WebPushCreateDefault {...baseProps()} onContentValidityChange={onContentValidityChange} />
|
|
213
|
+
</TestWrapper>,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('reports isContentEmpty: false once both title and message have content', () => {
|
|
220
|
+
const onContentValidityChange = jest.fn();
|
|
221
|
+
render(
|
|
222
|
+
<TestWrapper>
|
|
223
|
+
<WebPushCreateDefault {...baseProps()} onContentValidityChange={onContentValidityChange} />
|
|
224
|
+
</TestWrapper>,
|
|
225
|
+
);
|
|
226
|
+
onContentValidityChange.mockClear();
|
|
227
|
+
|
|
228
|
+
fireEvent.change(screen.getByTestId('notification-title-input'), { target: { value: 'Hello' } });
|
|
229
|
+
fireEvent.change(screen.getByTestId('message-input'), { target: { value: 'World' } });
|
|
230
|
+
|
|
231
|
+
expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: false });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('does not re-report (still empty) if only the title is filled in while message stays empty', () => {
|
|
235
|
+
const onContentValidityChange = jest.fn();
|
|
236
|
+
render(
|
|
237
|
+
<TestWrapper>
|
|
238
|
+
<WebPushCreateDefault {...baseProps()} onContentValidityChange={onContentValidityChange} />
|
|
239
|
+
</TestWrapper>,
|
|
240
|
+
);
|
|
241
|
+
// Mount already reported isContentEmpty: true (both fields blank).
|
|
242
|
+
onContentValidityChange.mockClear();
|
|
243
|
+
|
|
244
|
+
fireEvent.change(screen.getByTestId('notification-title-input'), { target: { value: 'Hello' } });
|
|
245
|
+
|
|
246
|
+
// isContentEmpty is still true (message is required too and remains blank), so the dedup
|
|
247
|
+
// guard must suppress a duplicate report.
|
|
248
|
+
expect(onContentValidityChange).not.toHaveBeenCalled();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('re-reports isContentEmpty: true when a previously-filled message is cleared out', () => {
|
|
252
|
+
const onContentValidityChange = jest.fn();
|
|
253
|
+
render(
|
|
254
|
+
<TestWrapper>
|
|
255
|
+
<WebPushCreateDefault {...baseProps()} onContentValidityChange={onContentValidityChange} />
|
|
256
|
+
</TestWrapper>,
|
|
257
|
+
);
|
|
258
|
+
fireEvent.change(screen.getByTestId('notification-title-input'), { target: { value: 'Hello' } });
|
|
259
|
+
fireEvent.change(screen.getByTestId('message-input'), { target: { value: 'World' } });
|
|
260
|
+
onContentValidityChange.mockClear();
|
|
261
|
+
|
|
262
|
+
fireEvent.change(screen.getByTestId('message-input'), { target: { value: '' } });
|
|
263
|
+
|
|
264
|
+
expect(onContentValidityChange).toHaveBeenCalledWith({ isContentEmpty: true });
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('does not throw and does not report when onContentValidityChange is not provided', () => {
|
|
268
|
+
expect(() => {
|
|
269
|
+
render(
|
|
270
|
+
<TestWrapper>
|
|
271
|
+
<WebPushCreateDefault {...baseProps()} />
|
|
272
|
+
</TestWrapper>,
|
|
273
|
+
);
|
|
274
|
+
fireEvent.change(screen.getByTestId('notification-title-input'), { target: { value: 'Hello' } });
|
|
275
|
+
}).not.toThrow();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('does not dispatch again on a re-render that does not change isContentEmpty (dedup guard)', () => {
|
|
279
|
+
const onContentValidityChange = jest.fn();
|
|
280
|
+
render(
|
|
281
|
+
<TestWrapper>
|
|
282
|
+
<WebPushCreateDefault {...baseProps()} onContentValidityChange={onContentValidityChange} />
|
|
283
|
+
</TestWrapper>,
|
|
284
|
+
);
|
|
285
|
+
fireEvent.change(screen.getByTestId('notification-title-input'), { target: { value: 'Hello' } });
|
|
286
|
+
fireEvent.change(screen.getByTestId('message-input'), { target: { value: 'World' } });
|
|
287
|
+
onContentValidityChange.mockClear();
|
|
288
|
+
|
|
289
|
+
// Further edits that keep both fields non-empty must not re-trigger the callback.
|
|
290
|
+
fireEvent.change(screen.getByTestId('message-input'), { target: { value: 'World!' } });
|
|
291
|
+
|
|
292
|
+
expect(onContentValidityChange).not.toHaveBeenCalled();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react/jsx-boolean-value */
|
|
2
2
|
/* eslint-disable camelcase */
|
|
3
3
|
/* eslint-disable no-undef */
|
|
4
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
5
5
|
import { bindActionCreators } from 'redux';
|
|
6
6
|
import { createStructuredSelector } from 'reselect';
|
|
7
7
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
@@ -119,7 +119,7 @@ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
|
|
|
119
119
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
120
120
|
import TagList from '../TagList';
|
|
121
121
|
import { validateTags } from '../../utils/tagValidations';
|
|
122
|
-
import { splitContentByOrderedVarTokens } from '../../utils/templateVarUtils';
|
|
122
|
+
import { splitContentByOrderedVarTokens, reconcileVarMapToSlotFormat } from '../../utils/templateVarUtils';
|
|
123
123
|
import { capitalizeString } from '../../utils/Formatter';
|
|
124
124
|
import CapWhatsappCTA from '../../v2Components/CapWhatsappCTA';
|
|
125
125
|
import {
|
|
@@ -188,6 +188,7 @@ export const Whatsapp = (props) => {
|
|
|
188
188
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
189
189
|
handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
|
|
190
190
|
isTestAndPreviewMode: propsIsTestAndPreviewMode,
|
|
191
|
+
onContentValidityChange,
|
|
191
192
|
} = props || {};
|
|
192
193
|
const { formatMessage } = intl;
|
|
193
194
|
const { TextArea } = CapInput;
|
|
@@ -504,7 +505,8 @@ export const Whatsapp = (props) => {
|
|
|
504
505
|
if (templateHeaderArray?.length !== 0) {
|
|
505
506
|
let clonedVarMap = {};
|
|
506
507
|
if (!isEmpty(varMap)) {
|
|
507
|
-
|
|
508
|
+
// Reconcile CCS sequential varMapped indices to the UI’s `${token}_${index}` slot keys so values map to the correct segments.
|
|
509
|
+
clonedVarMap = reconcileVarMapToSlotFormat(varMap, templateHeaderArray, regex);
|
|
508
510
|
} else {
|
|
509
511
|
templateHeaderArray?.forEach((headerValue, i) => {
|
|
510
512
|
if (headerValue?.match(regex)?.length > 0) {
|
|
@@ -562,7 +564,9 @@ export const Whatsapp = (props) => {
|
|
|
562
564
|
if (tempMsgArray.length !== 0) {
|
|
563
565
|
const { varMapped = {} } = editContent;
|
|
564
566
|
if (!isEmpty(varMapped)) {
|
|
565
|
-
|
|
567
|
+
// CCS's varMapped can use plain sequential occurrence indices ("0","1",...)
|
|
568
|
+
// rather than this UI's own `${token}_${index}` slot keys
|
|
569
|
+
varMap = reconcileVarMapToSlotFormat(varMapped, tempMsgArray, validVarRegex);
|
|
566
570
|
} else {
|
|
567
571
|
//computing and setting varMap for first edit
|
|
568
572
|
for (let i = 0; i < tempMsgArray.length; i += 1) {
|
|
@@ -701,6 +705,23 @@ export const Whatsapp = (props) => {
|
|
|
701
705
|
}
|
|
702
706
|
}, [carouselValidateTag, tags])
|
|
703
707
|
|
|
708
|
+
// Reports live "is content empty" validity to the parent slidebox so it can
|
|
709
|
+
// disable the Done/Preview-and-test buttons whenever the template message
|
|
710
|
+
// (the one field that is always required, regardless of template type) is
|
|
711
|
+
// cleared out. Mirrors the Sms Create/Edit componentDidUpdate pattern; the
|
|
712
|
+
// ref-based dedup avoids redundant parent setState calls / render loops.
|
|
713
|
+
const lastReportedTemplateMessageEmptyRef = useRef(undefined);
|
|
714
|
+
useEffect(() => {
|
|
715
|
+
if (typeof onContentValidityChange !== 'function') {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const isContentEmpty = !templateMessage?.trim();
|
|
719
|
+
if (lastReportedTemplateMessageEmptyRef.current !== isContentEmpty) {
|
|
720
|
+
lastReportedTemplateMessageEmptyRef.current = isContentEmpty;
|
|
721
|
+
onContentValidityChange({ isContentEmpty });
|
|
722
|
+
}
|
|
723
|
+
}, [templateMessage, onContentValidityChange]);
|
|
724
|
+
|
|
704
725
|
const handleOnTagsContextChange = (data) => {
|
|
705
726
|
const { type } = location.query || {};
|
|
706
727
|
const isEmbedded = type === EMBEDDED;
|